Skip to content

fix(claude): keep ${VAR} references in project .mcp.json - #3377

Open
Aaryan Dadu (Aaryan-Dadu) wants to merge 1 commit into
microsoft:mainfrom
Aaryan-Dadu:fix/3252-claude-mcp-placeholders
Open

Aaryan Dadu (Aaryan-Dadu) wants to merge 1 commit into
microsoft:mainfrom
Aaryan-Dadu:fix/3252-claude-mcp-placeholders

Conversation

@Aaryan-Dadu

Copy link
Copy Markdown
Contributor

Description

With --target claude, apm install resolved ${VAR} values at install time and wrote the real secrets into the project's .mcp.json, which is usually committed. Claude Code expands ${VAR} and ${VAR:-default} itself when it starts a server, so APM now keeps the references there.

How it works:

  • The project .mcp.json keeps ${VAR} and ${VAR:-default} in command, args, env, url and headers. ${env:VAR} and <VAR> become ${VAR}. There are no prompts, and no values end up in .mcp.json, apm.lock.yaml or the output.
  • Static values like LOG_LEVEL: debug stay as written. Registry servers get ${NAME} for their variables and secret arguments.
  • Missing variables are reported by name, once, for every server in the install. Defaulted ones are not reported.
  • Existing installs: reinstall replaces values an earlier release wrote when APM can confirm it wrote them. That means the recorded write hash matches, or the entry equals the declaration expanded from the current environment. It prints the variable names and a reminder to rotate committed credentials. Anything else is kept, and install names the fields to fix by hand.
  • User scope (~/.claude.json) is unchanged.
  • New docs page, MCP variable compatibility, covers every supported harness: native syntax, fields, defaults, unset behavior, approval, what APM writes, the evidence, and the gaps that remain (recorded, not changed).

I checked Claude Code's behavior with Claude Code 2.1.310: a recording stdio command and a local HTTP endpoint showed it expands references and defaults in all five fields, in both .mcp.json and ~/.claude.json, and leaves an unset variable as literal text with a warning.

Closes #3252

Before

After

Issue and approved scope

Issue: #3252

Human scope-approval comment: #3252 (comment)

Does this PR complete the issue, or what remains? It completes the Claude project repair. Gaps in other harnesses are recorded on the new page for separate decisions.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

New installed-CLI tests in tests/integration/test_claude_mcp_env_references_e2e.py cover set, unset and defaulted variables, a registry server, user scope, mixed targets, unrelated entries and Claude's approval file, and reinstall over old values (replaced, or kept when APM can't confirm it wrote them). 6 of the 7 fail on main; the user-scope one pins the unchanged behavior. There are also unit tests in test_claude_mcp.py and test_mcp_ownership.py.

I also installed the same declarations for all 12 harnesses and both scopes on main and on this branch. The only output that changes is Claude's project config.

Spec conformance (OpenAPM v0.1)

If this PR changes behaviour that an OpenAPM v0.1 req-XXX covers,
confirm the three-step ritual in the
development guide:

  • Spec edit: docs/src/content/docs/specs/openapm-v0.1.md updated
    (new/changed <a id="req-XXX"></a> anchor + prose + Appendix C
    row).
  • Manifest edit: docs/src/content/docs/specs/manifests/openapm-v0.1.requirements.yml
    updated.
  • Test edit: a @pytest.mark.req("req-XXX") test under
    tests/spec_conformance/ added or extended.
  • CONFORMANCE.{md,json} regenerated via
    uv run --extra dev python -m tests.spec_conformance.gen_statement
    and committed.
  • N/A -- this PR does not change OpenAPM-observable behaviour.

Claude Code expands ${VAR} and ${VAR:-default} in the project .mcp.json,
so apm install keeps the references there instead of prompting for and
writing the real values. Reinstall replaces values an earlier release
wrote when APM can confirm it wrote them; otherwise it keeps the entry
and names the fields to fix. User scope is unchanged.

Adds an MCP variable compatibility page covering every harness.

Closes microsoft#3252

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] --target claude writes resolved secrets as plaintext into project-scoped .mcp.json (VS Code/Copilot keep ${VAR} placeholders)

2 participants