needs-auth cache (keyed by server name) poisoned by `claude mcp list` blocks a later `--mcp-config` run of a same-named server with valid static auth

Status Open
Reported on v2.1.206
Maintainer reply None cached
Activity 1 comment · opened Jul 23, 2026

Summary

~/.claude/mcp-needs-auth-cache.json is keyed by the server name and shared across all Claude Code invocations on the machine. This makes a health-check of one config source for a server poison a completely different config source for the same name:

  1. claude mcp list does not accept --mcp-config (error: unknown option '--mcp-config'), so it can only health-check servers discovered from the project .mcp.json / ~/.claude.json.
  2. If the project .mcp.json declares a server (say atlassian) without an Authorization header (the interactive-OAuth variant), that health-check gets an OAuth challenge and writes atlassian into mcp-needs-auth-cache.json.
  3. A subsequent claude -p --mcp-config /tmp/mcp_with_auth_token.json where /tmp/mcp_with_auth_token.json declares the same-named atlassian with a valid static Authorization: Basic … header is then skipped:

``
[DEBUG] MCP server "atlassian": Skipping connection (cached needs-auth)
``
The valid header is never sent — the correct config is short-circuited by a cache entry created from a different config that was never used for this run.

--strict-mcp-config does not help: the cache is checked by server name before any connection is attempted (cf. the code cited in #48670: if (await isInNeedsAuthCache(serverName)) return {type:"needs-auth"}), independent of which config source defined the server.

This is especially painful on long-lived shell-executor CI runners: the cache survives across jobs, so one poisoning blocks every later job — including jobs whose --mcp-config is entirely correct.

Environment

  • Claude Code: 2.1.206 (also reproducible on 2.1.218)
  • Platform: macOS (shell-executor CI runner), also general
  • Transport: HTTP (Streamable HTTP)

Repro

  1. Project .mcp.json (discovered from cwd) — server named demo, no auth header:

``json
{ "mcpServers": { "demo": { "type": "http", "url": "https://mcp.atlassian.com/v1/mcp" } } }
``

  1. Run the debug listing (as many CI setups do) — note it cannot be pointed at another config:

``
claude mcp list # health-checks the headerless
demo, gets OAuth challenge
# -> writes {"demo":{...}} into ~/.claude/mcp-needs-auth-cache.json
``

  1. Run headless with a correct config for the same name, via a temp file:

``json
// /tmp/mcp_with_auth_token.json
{ "mcpServers": { "demo": { "type": "http", "url": "https://mcp.atlassian.com/v1/mcp",
"headers": { "Authorization": "Basic <base64(email:api_token)>" } } } }
`
`
claude -p "use demo mcp ..." --mcp-config /tmp/mcp_with_auth_token.json --strict-mcp-config
`
**Expected:**
demo connects using the static header from /tmp/mcp_with_auth_token.json.
**Actual:**
[DEBUG] MCP server "demo": Skipping connection (cached needs-auth)` — the header is never sent; the tool is unavailable.

Confirmed separately that the header/config in step 3 is valid: the same --mcp-config connects and works when the cache does not contain demo (fresh machine / after rm -f ~/.claude/mcp-needs-auth-cache.json).

Suggested fixes (any of)

  1. Key the cache by the resolved config identity (endpoint URL + auth scheme/source), not by free-text server name — so a headerless variant and a header-bearing variant of the same name don't share a cache entry. (Overlaps with #78220.)
  2. Let --strict-mcp-config bypass the needs-auth cache for servers it defines (the user explicitly supplied a full config; a cache from another source shouldn't override it).
  3. Allow claude mcp list to accept --mcp-config / --strict-mcp-config, so health-checks and actual runs read the same config instead of silently diverging.
  4. Scope the cache to process lifetime (in-memory) or add a documented flag/env to disable/clear it — important for SDK/CI where /mcp re-auth is impossible (cf. #48670).

Related

  • #78220 — needs-auth flag keyed by name (not URL), not cleared by mcp remove.
  • #48670 — needs-auth cache blocks reconnection; TTL 15 min; rm -f workaround.
  • #44830 — discovery poisoning persists; connection skipped before any HTTP request.

Workaround

rm -f ~/.claude/mcp-needs-auth-cache.json before the run, and avoid claude mcp list against a config whose servers can't authenticate non-interactively.

View original on GitHub ↗

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