Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mcp-child-env-appdata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openwiki": patch
---

Pass Windows `APPDATA` and `LOCALAPPDATA` into stdio MCP child environments so local MCP servers can resolve their config and cache directories.
9 changes: 6 additions & 3 deletions src/connectors/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,15 +733,18 @@ function resolveChildEnv(
}

// Base environment variables an MCP subprocess may legitimately need to run
// (locating binaries, resolving the home dir, temp paths, terminal behavior).
// Deliberately excludes OpenWiki credentials so a spawned MCP server command
// cannot read the user's API keys and OAuth refresh tokens out of process.env.
// (locating binaries, resolving the home dir, AppData caches on Windows, temp
// paths, terminal behavior). Deliberately excludes OpenWiki credentials so a
// spawned MCP server command cannot read the user's API keys and OAuth refresh
// tokens out of process.env.
const CHILD_ENV_ALLOWLIST = [
"PATH",
"HOME",
"HOMEPATH",
"HOMEDRIVE",
"USERPROFILE",
"APPDATA",
"LOCALAPPDATA",
"TMPDIR",
"TEMP",
"TMP",
Expand Down
16 changes: 15 additions & 1 deletion test/mcp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@ describe("buildChildEnv", () => {
const saved: Record<string, string | undefined> = {};

beforeEach(() => {
for (const key of [...SECRET_KEYS, "PATH", "MCP_SERVER_TOKEN"]) {
for (const key of [
...SECRET_KEYS,
"PATH",
"APPDATA",
"LOCALAPPDATA",
"MCP_SERVER_TOKEN",
]) {
saved[key] = process.env[key];
}
for (const key of SECRET_KEYS) {
process.env[key] = `secret-value-for-${key}`;
}
process.env.PATH = "/usr/bin:/bin";
process.env.APPDATA = "C:\\Users\\example\\AppData\\Roaming";
process.env.LOCALAPPDATA = "C:\\Users\\example\\AppData\\Local";
process.env.MCP_SERVER_TOKEN = "declared-token-123";
});

Expand Down Expand Up @@ -49,6 +57,12 @@ describe("buildChildEnv", () => {
expect(childEnv.PATH).toBe("/usr/bin:/bin");
});

test("passes through Windows AppData paths used by many MCP servers", () => {
const childEnv = buildChildEnv({});
expect(childEnv.APPDATA).toBe("C:\\Users\\example\\AppData\\Roaming");
expect(childEnv.LOCALAPPDATA).toBe("C:\\Users\\example\\AppData\\Local");
});

test("resolves only the credentials the transport explicitly declares", () => {
const childEnv = buildChildEnv({ MCP_TOKEN: "${MCP_SERVER_TOKEN}" });
expect(childEnv.MCP_TOKEN).toBe("declared-token-123");
Expand Down