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
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:
claude mcp listdoes not accept--mcp-config(error: unknown option '--mcp-config'), so it can only health-check servers discovered from the project.mcp.json/~/.claude.json.- If the project
.mcp.jsondeclares a server (sayatlassian) without anAuthorizationheader (the interactive-OAuth variant), that health-check gets an OAuth challenge and writesatlassianintomcp-needs-auth-cache.json. - A subsequent
claude -p --mcp-config /tmp/mcp_with_auth_token.jsonwhere/tmp/mcp_with_auth_token.jsondeclares the same-namedatlassianwith a valid staticAuthorization: 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 on2.1.218) - Platform: macOS (shell-executor CI runner), also general
- Transport: HTTP (Streamable HTTP)
Repro
- Project
.mcp.json(discovered from cwd) — server nameddemo, no auth header:
``json``
{ "mcpServers": { "demo": { "type": "http", "url": "https://mcp.atlassian.com/v1/mcp" } } }
- Run the debug listing (as many CI setups do) — note it cannot be pointed at another config:
``demo
claude mcp list # health-checks the headerless , gets OAuth challenge``
# -> writes {"demo":{...}} into ~/.claude/mcp-needs-auth-cache.json
- 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
demo
**Expected:** connects using the static header from /tmp/mcp_with_auth_token.json.[DEBUG] MCP server "demo": Skipping connection (cached needs-auth)` — the header is never sent; the tool is unavailable.
**Actual:**
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)
- 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.)
- Let
--strict-mcp-configbypass the needs-auth cache for servers it defines (the user explicitly supplied a full config; a cache from another source shouldn't override it). - Allow
claude mcp listto accept--mcp-config/--strict-mcp-config, so health-checks and actual runs read the same config instead of silently diverging. - Scope the cache to process lifetime (in-memory) or add a documented flag/env to disable/clear it — important for SDK/CI where
/mcpre-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 -fworkaround. - #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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗