`claude mcp remove` expands `${VAR}` env-var references inline in `.mcp.json`, leaking secrets

Resolved 💬 3 comments Opened May 13, 2026 by charliestoner1 Closed Jun 12, 2026

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

  1. figma is deleted (correct).
  2. ${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..."

```

  1. 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 remove against a .mcp.json containing ${VAR} secret references will leak those secrets into the file on disk. Users who commit .mcp.json after 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗