Summary
Plugin manifest mcpServers do not expand ${env:*} or ${userHome}, while user/project mcp.json does. The same MCP server contract therefore behaves differently depending on whether it is declared by a plugin or by user config.
Why it matters
Plugin authors will naturally reuse the placeholder syntax supported by normal MCP config, for example cwd: "${userHome}/x", env.TOKEN: "${env:TOKEN}", or HTTP headers containing tokens. Plugin MCP servers are merged into the same runtime as user MCP servers, but plugin entries keep these placeholders as literal strings. This can break cwd resolution, stdio environment variables, HTTP URLs, or authentication headers.
Evidence
src/mcp/config/loadMcpServerConfig.ts:66 returns user/project mcpServers after expandConfig.
src/mcp/config/loadMcpServerConfig.ts:69 to src/mcp/config/loadMcpServerConfig.ts:79 recursively expands strings, arrays, and objects.
src/mcp/config/loadMcpServerConfig.ts:82 to src/mcp/config/loadMcpServerConfig.ts:85 expands ${env:*} and ${userHome}.
src/extension/plugins/protocol/manifest.ts:11 allows plugin manifests to declare mcpServers.
src/extension/plugins/config/parsePluginManifest.ts:19 accepts raw.mcpServers directly without placeholder expansion.
src/extension/plugins/runtime/PluginRuntime.ts:85 to src/extension/plugins/runtime/PluginRuntime.ts:86 aggregates plugin mcpServers.
src/cli/createLocalGateway.ts:684 to src/cli/createLocalGateway.ts:688 merges plugin MCP servers with user config servers before parsing.
src/mcp/runtime/parsePluginMcpServers.ts:16 to src/mcp/runtime/parsePluginMcpServers.ts:20 only defines ~ expansion.
src/mcp/runtime/parsePluginMcpServers.ts:47 to src/mcp/runtime/parsePluginMcpServers.ts:51 applies that expansion only to stdio args; env and cwd are preserved as-is.
src/mcp/runtime/parsePluginMcpServers.ts:56 to src/mcp/runtime/parsePluginMcpServers.ts:62 preserves HTTP url and headers as-is.
src/mcp/client/McpClient.ts:171 to src/mcp/client/McpClient.ts:180 passes the parsed stdio/http spec directly into MCP transports.
Validation
Validation level: dynamic parser reproduction.
Repro approach: call parsePluginMcpServers with plugin MCP entries containing args: ["~/server.js"], cwd: "${userHome}/x", env.TOKEN: "${env:PILOTDECK_TEST_TOKEN}", url: "${env:MCP_URL}", and headers.Authorization: "Bearer ${env:TOKEN}".
Key output: args expanded ~/server.js to the user home path, but cwd, env.TOKEN, url, and headers.Authorization remained literal placeholder strings.
Boundary: this did not start a real MCP server. The gateway merge and McpClient transport construction paths show those literal values would be passed to the actual stdio or HTTP transport.
Expected behavior
Plugin manifest MCP server declarations should support the same placeholder semantics as user/project mcp.json, or the plugin manifest schema should explicitly reject unsupported placeholders before runtime.
Existing coverage checked
Coverage review searched for parsePluginMcpServers, mcpServers ${env, ${userHome}, and streamable_http headers.
Coverage label: not covered. PR #84 covers ${projectRoot} support in the user MCP config loader, not plugin manifest parsing. PR #191 changes plugin storage path handling, not MCP placeholder expansion. Issue #210 concerns Windows MCP config behavior and does not cover this plugin manifest root cause.
Suggested fix
Reuse the user MCP config expansion logic for plugin manifest mcpServers, or extract a shared MCP placeholder expansion helper. At minimum, expand stdio args, cwd, and env, plus streamable HTTP url and headers.
Suggested tests
${env:*} expansion in stdio env.
${userHome} expansion in stdio cwd.
~ expansion remains supported in stdio args.
${env:*} expansion in streamable HTTP url.
${env:*} expansion in streamable HTTP headers.
- User/project
mcp.json and plugin manifest MCP entries resolve placeholders consistently.
Submitted with Codex.
Summary
Plugin manifest
mcpServersdo not expand${env:*}or${userHome}, while user/projectmcp.jsondoes. The same MCP server contract therefore behaves differently depending on whether it is declared by a plugin or by user config.Why it matters
Plugin authors will naturally reuse the placeholder syntax supported by normal MCP config, for example
cwd: "${userHome}/x",env.TOKEN: "${env:TOKEN}", or HTTP headers containing tokens. Plugin MCP servers are merged into the same runtime as user MCP servers, but plugin entries keep these placeholders as literal strings. This can break cwd resolution, stdio environment variables, HTTP URLs, or authentication headers.Evidence
src/mcp/config/loadMcpServerConfig.ts:66returns user/projectmcpServersafterexpandConfig.src/mcp/config/loadMcpServerConfig.ts:69tosrc/mcp/config/loadMcpServerConfig.ts:79recursively expands strings, arrays, and objects.src/mcp/config/loadMcpServerConfig.ts:82tosrc/mcp/config/loadMcpServerConfig.ts:85expands${env:*}and${userHome}.src/extension/plugins/protocol/manifest.ts:11allows plugin manifests to declaremcpServers.src/extension/plugins/config/parsePluginManifest.ts:19acceptsraw.mcpServersdirectly without placeholder expansion.src/extension/plugins/runtime/PluginRuntime.ts:85tosrc/extension/plugins/runtime/PluginRuntime.ts:86aggregates pluginmcpServers.src/cli/createLocalGateway.ts:684tosrc/cli/createLocalGateway.ts:688merges plugin MCP servers with user config servers before parsing.src/mcp/runtime/parsePluginMcpServers.ts:16tosrc/mcp/runtime/parsePluginMcpServers.ts:20only defines~expansion.src/mcp/runtime/parsePluginMcpServers.ts:47tosrc/mcp/runtime/parsePluginMcpServers.ts:51applies that expansion only to stdioargs;envandcwdare preserved as-is.src/mcp/runtime/parsePluginMcpServers.ts:56tosrc/mcp/runtime/parsePluginMcpServers.ts:62preserves HTTPurlandheadersas-is.src/mcp/client/McpClient.ts:171tosrc/mcp/client/McpClient.ts:180passes the parsed stdio/http spec directly into MCP transports.Validation
Validation level: dynamic parser reproduction.
Repro approach: call
parsePluginMcpServerswith plugin MCP entries containingargs: ["~/server.js"],cwd: "${userHome}/x",env.TOKEN: "${env:PILOTDECK_TEST_TOKEN}",url: "${env:MCP_URL}", andheaders.Authorization: "Bearer ${env:TOKEN}".Key output:
argsexpanded~/server.jsto the user home path, butcwd,env.TOKEN,url, andheaders.Authorizationremained literal placeholder strings.Boundary: this did not start a real MCP server. The gateway merge and
McpClienttransport construction paths show those literal values would be passed to the actual stdio or HTTP transport.Expected behavior
Plugin manifest MCP server declarations should support the same placeholder semantics as user/project
mcp.json, or the plugin manifest schema should explicitly reject unsupported placeholders before runtime.Existing coverage checked
Coverage review searched for
parsePluginMcpServers,mcpServers ${env,${userHome}, andstreamable_http headers.Coverage label: not covered. PR #84 covers
${projectRoot}support in the user MCP config loader, not plugin manifest parsing. PR #191 changes plugin storage path handling, not MCP placeholder expansion. Issue #210 concerns Windows MCP config behavior and does not cover this plugin manifest root cause.Suggested fix
Reuse the user MCP config expansion logic for plugin manifest
mcpServers, or extract a shared MCP placeholder expansion helper. At minimum, expand stdioargs,cwd, andenv, plus streamable HTTPurlandheaders.Suggested tests
${env:*}expansion in stdioenv.${userHome}expansion in stdiocwd.~expansion remains supported in stdioargs.${env:*}expansion in streamable HTTPurl.${env:*}expansion in streamable HTTPheaders.mcp.jsonand plugin manifest MCP entries resolve placeholders consistently.Submitted with Codex.