[BUG] `claude mcp get` prints full env values in plaintext (secret leak)
Summary
claude mcp get <name> prints the full Environment: block verbatim — including secret values like database DSNs, API tokens, or any other credentials passed via --env KEY=VALUE when the server was registered. There is no masking and no opt-in for plaintext display. Running it for routine diagnostic purposes (e.g. "is this server still configured?") leaks the secret to wherever stdout is captured: terminal scrollback, screen-shares, log files, agent chat transcripts, paste-board buffers.
This forced a credential rotation in our environment today when a routine claude mcp get postgres-bdcscope-readonly echoed the full Postgres DSN (user + password + host + database) into an agent transcript.
Reproduction
claude mcp add my-server --scope local \
--env DATABASE_URI=postgresql://alice:s3cret@db.example.com/prod \
-- some-mcp-binary --flag
claude mcp get my-server
Output includes:
my-server:
Scope: Local config (private to you in this project)
Status: ✓ Connected
Type: stdio
Command: some-mcp-binary
Args: --flag
Environment:
DATABASE_URI=postgresql://alice:s3cret@db.example.com/prod
The password is in plaintext. No flag was passed to opt into seeing it.
Why this matters
- Agent / automation contexts: any Claude Code agent that runs
mcp getfor diagnostic purposes will write the secret into its conversation transcript, which is often persisted indefinitely (Anthropic logs, local JSONL, harness storage). - Screen sharing / pair programming: running
mcp getto confirm a server is configured is exactly the kind of "show, don't tell" moment where the operator forgets the output contains secrets. - Log capture: terminal multiplexers (
tmux,screen), shell history with timestamps, and CI loggers all routinely capture stdout. A singlemcp getpoisons all of them. - No warning, no opt-in:
mcp listcorrectly redacts (shows onlyCommand:line, no env).mcp getdoes the opposite without warning. The asymmetry surprises operators.
Proposed fix
Mask by default; require an explicit flag to reveal.
Suggested behavior for claude mcp get <name>:
Environment:
DATABASE_URI=<redacted, 53 chars> (use --reveal-env to show)
LOG_LEVEL=info
PORT=3000
Heuristic for "looks like a secret" (any one of):
- Key name matches
/(_TOKEN|_KEY|_SECRET|_PASSWORD|_DSN|_URI|_URL|API_)/i - Value matches a known credential shape:
postgres://,postgresql://,mysql://, JWT (eyJ...), 32+ char hex, 24+ char base64, etc. - Value length > 24 chars and not in a small whitelist of obviously-non-secret tokens (
http://,https://localhost, etc.)
Add --reveal-env (or --show-secrets) to opt back into the current behavior. Match the convention gh auth status already uses (Token: gho_*****...).
Workaround until this lands
For our team, we now require any MCP re-registration to go through a Python wrapper that scrubs DSN-shaped content from subprocess.run captured output before printing. The claude mcp get command is effectively banned from agent and screen-share contexts. Both are friction we'd rather not carry.
Environment
- claude-code version: 2.x (latest as of 2026-05-30)
- OS: Windows 11
- Stdio MCP server registered with
--env DATABASE_URI=postgresql://...
Happy to provide a PR for the masking heuristic if you'd like — the logic is straightforward and could ship behind a feature flag for a release before becoming default.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗