[BUG] claude mcp remove reformats .mcp.json to reveal secrets
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Closely related to #18692.
This bug is still reproducing in Claude Code 2.1.133, and the broken code path is broader than just claude mcp add — claude mcp remove exhibits identical behavior. Suggest reopening this issue rather than tracking it separately, since the underlying defect is the same.
New finding: claude mcp remove also expands ${VAR} placeholders
Running claude mcp remove <name> against a project-scoped .mcp.json rewrites the file with all unrelated ${VAR} placeholders expanded into their literal env values.
Implication for the fix
Two unrelated config-mutating subcommands (add from the older issue, remove from my repro) exhibit identical expand-and-persist behavior. That puts the defect in the shared read-mutate-write path for .mcp.json, not in either subcommand's arg-parsing surface. The shell-quoting workaround mentioned earlier in this thread ('API_KEY=$MY_SECRET') doesn't apply to mcp remove — no env-var args are passed in.
Suggested fixes (either is sufficient, both close the class):
- Preserve raw on-disk text for unmodified entries — only modify the targeted block during write-back.
- Defer all
${VAR}resolution to MCP-launch time; never persist resolved values back to the config file.
Worth auditing the entire claude mcp * subcommand surface for the same pattern (set, get, scope changes, etc.).
What Should Happen?
No leakage.
Error Messages/Logs
Steps to Reproduce
Repro: a project .mcp.json containing three entries, two with ${VAR} placeholders in env and one with none (the removal target). After claude mcp remove linear, the linear block is removed correctly, but the OTHER entries' secrets get baked in:
"env": {
- "LANGSMITH_API_KEY": "${LANGSMITH_API_KEY}",
- "LANGSMITH_WORKSPACE_ID": "${LANGSMITH_WORKSPACE_ID}",
+ "LANGSMITH_API_KEY": "lsv2_pt_<REDACTED-32-hex>_<REDACTED-10-hex>",
+ "LANGSMITH_WORKSPACE_ID": "<REDACTED-uuid>",
"LANGSMITH_ENDPOINT": "https://api.smith.langchain.com"
}
},
"mcp-atlassian": {
...
"env": {
- "JIRA_URL": "${JIRA_URL}",
- "JIRA_USERNAME": "${JIRA_USERNAME}",
- "JIRA_API_TOKEN": "${JIRA_API_TOKEN}"
+ "JIRA_URL": "https://<REDACTED-org>.atlassian.net",
+ "JIRA_USERNAME": "<REDACTED-email>",
+ "JIRA_API_TOKEN": "ATATT3xFfGF0<REDACTED-full-jira-pat>=<REDACTED-suffix>"
}
},
- "linear": { ... },
The <REDACTED-...> placeholders correspond to real production secrets in my repro — a LangSmith API key and a full Jira API token. I caught them in git diff before committing. Anyone running git commit -am ... after a routine mcp remove would have leaked them.
Self-contained reproducer
Bundle with before.mcp.json, after.mcp.json, full unified diff, synthetic env values, and a steps.sh reproducer script:
claude-mcp-remove-bug-report.zip
The bundle uses synthetic env values that preserve the character pattern of real secrets but have no value to a third party — running steps.sh produces an identical-shape diff to the redacted one above, with no real credentials involved.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.133
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Cursor
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗