[BUG] `claude mcp get` prints full env values in plaintext (secret leak)

Resolved 💬 1 comment Opened May 30, 2026 by maxmk000 Closed Jul 3, 2026

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

  1. Agent / automation contexts: any Claude Code agent that runs mcp get for diagnostic purposes will write the secret into its conversation transcript, which is often persisted indefinitely (Anthropic logs, local JSONL, harness storage).
  2. Screen sharing / pair programming: running mcp get to confirm a server is configured is exactly the kind of "show, don't tell" moment where the operator forgets the output contains secrets.
  3. Log capture: terminal multiplexers (tmux, screen), shell history with timestamps, and CI loggers all routinely capture stdout. A single mcp get poisons all of them.
  4. No warning, no opt-in: mcp list correctly redacts (shows only Command: line, no env). mcp get does 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗