Desktop app doesn't resolve project settings.local.json env vars for plugin MCP server subprocesses
Related prior reports (both closed, but bug not actually fixed)
- #70486 — closed by a maintainer in favor of tracking in #70485
- #70485 — later auto-closed by the stale-bot for inactivity ("Please open a
new issue if this is still relevant"), not because it was fixed
This report is fresh, independent confirmation that the bug is still present.
Description
When a plugin-provided MCP server's config references an environment variable
via ${VAR_NAME} templating, the Claude desktop app fails to resolve it from
the project's .claude/settings.local.json env block. The subprocess ends up
receiving the literal unexpanded string ${VAR_NAME} instead of the actual
value, causing auth failures against the downstream API.
Steps to reproduce
- Add a secret to
.claude/settings.local.json:
``json``
{ "env": { "MY_API_KEY": "<value>" } }
- Use a plugin MCP server whose config references
${MY_API_KEY}. - Call a tool from that MCP server.
- Observe a 401/auth error from the downstream API.
Diagnosis
- Confirmed the Claude Code session's own Bash-tool subprocess correctly
inherits MY_API_KEY from settings.local.json (env | grep MY_API_KEY
shows the real value).
- Inspected the running plugin MCP server subprocess directly with
ps eww <pid> and found it literally has:
````
MY_API_KEY=${MY_API_KEY}
i.e., the template was never substituted for that subprocess's environment.
- Fully quitting and relaunching the desktop app did not fix it — a freshly
spawned MCP server process (started after full app restart) still showed the
unexpanded placeholder.
- Workaround: running
launchctl setenv MY_API_KEY <value>before launching
the app makes the plugin MCP server pick up the correct value, implying the
substitution is reading from the app's own OS-level process environment
rather than from the project's settings.local.json.
Impact
- Project-scoped secrets in
settings.local.jsonare silently ignored for
plugin MCP servers, with no error surfaced beyond a generic downstream 401.
- The
launchctl setenvworkaround broadcasts the secret to every process in
the user's login session (not just the intended MCP server), which is a
meaningfully worse security posture than a scoped, per-project env var.
Environment
- Claude desktop app version: 1.30096.1 (macOS)
- Affected server: a plugin-provided MCP server, but the underlying
substitution mechanism appears to be general to plugin MCP config.
Expected behavior
Plugin MCP server subprocesses should have ${VAR} templates resolved from
the same settings sources (project/user settings.local.json) that the
Bash tool and other in-session subprocesses already use.