`claude mcp remove` expands `${VAR}` env-var references inline in `.mcp.json`, leaking secrets
Summary
claude mcp remove <name> correctly deletes the named server entry from .mcp.json, but as a side effect it expands every ${VAR} env-var placeholder elsewhere in the file to its current literal value. Secrets that were safely referenced via env var get baked into the file on disk. If the user commits .mcp.json, credentials leak to git history.
A secondary bug mangles nested/default-value placeholders.
Version
Claude Code 2.1.84 (Windows 11, PowerShell 5.1)
Repro
Start with .mcp.json containing env-var-referenced secrets and one server to remove. Example (sanitized):
{
"mcpServers": {
"github": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
}
},
"ruflo": {
"command": "npx",
"args": ["ruflo@latest", "mcp", "start"],
"env": {
"RUFLO_DATA_DIR": "${RUFLO_DATA_DIR:-${HOME}/.ruflo}",
"RUFLO_LOG_LEVEL": "warn"
}
},
"figma": {
"type": "http",
"url": "https://mcp.figma.com/mcp"
}
}
}
Have GITHUB_PERSONAL_ACCESS_TOKEN set in the shell environment. Run:
claude mcp remove figma -s project
Expected
Only the figma block is deleted from .mcp.json. All other entries, including ${VAR} references, are preserved verbatim.
Actual
figmais deleted (correct).${GITHUB_PERSONAL_ACCESS_TOKEN}is replaced with the literal token value:
```diff
- "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
- "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_REDACTED..."
```
- The nested-default placeholder
${RUFLO_DATA_DIR:-${HOME}/.ruflo}is mangled to${HOME/.ruflo}, which is invalid syntax and breaks the ruflo server on next start:
```diff
- "RUFLO_DATA_DIR": "${RUFLO_DATA_DIR:-${HOME}/.ruflo}",
- "RUFLO_DATA_DIR": "${HOME/.ruflo}",
```
Reproduced across six different env-var references in a single claude mcp remove invocation (GITHUB_PERSONAL_ACCESS_TOKEN, NEON_API_KEY, FAL_KEY, FIRECRAWL_API_KEY, BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID — every secret in the file was inlined).
Impact
- Security. Any
claude mcp removeagainst a.mcp.jsoncontaining${VAR}secret references will leak those secrets into the file on disk. Users who commit.mcp.jsonafter running the command leak credentials to git history. There is no warning. - Correctness. The
${VAR:-default}and${VAR_A${VAR_B}...}forms get destructively re-serialized, producing invalid env values that break servers.
Suggested fix
claude mcp remove (and presumably claude mcp add/update) should treat the JSON file as text-preserving: parse, mutate only the targeted block, and round-trip every other string value verbatim. Today it looks like the file is being read through an env-var-expanding parser and re-emitted, which is the wrong model for a file where those references are meant to stay as references.
Workaround
After any claude mcp command that writes .mcp.json, git diff .mcp.json and restore any ${VAR} placeholders by hand before committing.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗