From ec55fe64f12f3431e0349def955898dc2a72fbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Fri, 20 Mar 2026 10:39:15 -0600 Subject: [PATCH] fix: resolve ${VAR} env references in MCP client .mcp.json configs (#1609) The MCP client passed raw "${VAR}" strings to child processes instead of resolving them against process.env, breaking MCP servers that expect resolved environment variable values. Adds a resolveEnv() helper that interpolates ${VAR} patterns in env config values before passing them to StdioClientTransport. Closes #1599 Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/mcp-client/index.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/mcp-client/index.ts b/src/resources/extensions/mcp-client/index.ts index bdb60c2ed..904fbbcb4 100644 --- a/src/resources/extensions/mcp-client/index.ts +++ b/src/resources/extensions/mcp-client/index.ts @@ -114,6 +114,22 @@ function getServerConfig(name: string): McpServerConfig | undefined { return readConfigs().find((s) => s.name === name); } +/** Resolve ${VAR} references in env values against process.env. */ +function resolveEnv(env: Record): Record { + const resolved: Record = {}; + for (const [key, value] of Object.entries(env)) { + if (typeof value === "string") { + resolved[key] = value.replace( + /\$\{([^}]+)\}/g, + (_match, varName) => process.env[varName] ?? "", + ); + } else { + resolved[key] = value; + } + } + return resolved; +} + async function getOrConnect(name: string, signal?: AbortSignal): Promise { const existing = connections.get(name); if (existing) return existing.client; @@ -128,7 +144,7 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise transport = new StdioClientTransport({ command: config.command, args: config.args, - env: config.env ? { ...process.env, ...config.env } as Record : undefined, + env: config.env ? { ...process.env, ...resolveEnv(config.env) } as Record : undefined, cwd: config.cwd, stderr: "pipe", });