RFC 9728 discovery poisoning persists in v2.1.92 — OAuth MCP servers permanently stuck in 'needs authentication'
Summary
Since v2.1.85, Claude Code permanently skips OAuth-based MCP servers after a single transient connection failure. The server is never contacted again — hasMcpDiscoveryButNoToken() returns true, and the connection is silently dropped with "Skipping connection (cached needs-auth)" on every subsequent startup. This regression has not been fixed in any release through v2.1.92, which we confirmed this morning with debug logs from a live session.
---
Environment
- Claude Code version: v2.1.92 (also reproducible on v2.1.85 per issue #44774)
- Claude Desktop: Same behavior observed simultaneously
- OS: macOS (Sonoma 14.x)
- Node: v24.3.0
- MCP server transport: HTTP with OAuth 2.1 (Authorization Code + PKCE + Dynamic Client Registration)
---
Context
We operate mctx.ai, a hosting platform for MCP servers (Apps for AI). Developers connect a GitHub repo and mctx handles hosting, authentication, and subscriptions. Every hosted server uses OAuth 2.1 with Dynamic Client Registration and PKCE, authenticating via Auth0. Subscribers connect to servers like https://notes.mctx.ai or https://todos.mctx.ai from their Claude Code/Desktop config.
Each server serves both RFC 9728 (/.well-known/oauth-protected-resource) and RFC 8414 (/.well-known/oauth-authorization-server) metadata exactly as the MCP spec describes. This setup worked correctly before v2.1.85. It has been broken since.
This affects every subscriber using an mctx-hosted server — so this isn't a single-server misconfiguration issue. It's a client-side state management bug that affects all OAuth-based MCP servers serving correct RFC 9728 discovery metadata.
---
Observed Behavior
All OAuth-based MCP servers on mctx show "needs authentication" despite:
- Valid tokens stored from a previous successful authentication
- The server returning no 401s (verified server-side — see below)
- Auth0 being fully operational (no outages)
- No changes to server configuration or code after the last successful connection
Debug logs from this morning's session (v2.1.92, session 1a1e0d8c-a2da-48ad-a4a2-83a945025b87) show the connection is skipped before any HTTP request is made:
2026-04-07T16:51:51.815Z [DEBUG] MCP server "notes": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.815Z [DEBUG] MCP server "notes": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.818Z [DEBUG] MCP server "todos": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "todos": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "help-test": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "help-test": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "notes": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "todos": Skipping connection (cached needs-auth)
2026-04-07T16:51:51.819Z [DEBUG] MCP server "help-test": Skipping connection (cached needs-auth)
Each server logs the skip message three times (twice in rapid succession, then once more). No headersHelper invocation. No HTTP request to the server. No attempt to initiate any connection. The keychain cache short-circuits everything before any network activity.
The MCP server configs for the affected servers:
"notes": { "type": "http", "url": "https://notes.mctx.ai" }
"todos": { "type": "http", "url": "https://todos.mctx.ai" }
"help-test": { "type": "http", "url": "https://help-test.mctx.ai" }
For comparison, context7 — configured as an HTTP server with a static Authorization: Bearer header (not OAuth) — connected successfully in the same session at 111ms. The claude.ai proxy servers for Notion and Gmail also connected normally. The poisoning is specific to servers that go through OAuth discovery.
---
Server-Side Verification
We ruled out every server-side cause before concluding this is a client bug:
- No 401s returned. Our server logs show zero authentication failures for these servers since the re-authentication on April 6. The server never received a connection attempt.
- No Auth0 outage. Auth0 status was green on April 6-7 per status.auth0.com.
- No deploys after last successful connection. The last production deploy was April 6 at 20:05 UTC. The disconnections happened after that, with no code changes between.
- OAuth metadata is correct. Both discovery endpoints respond with valid metadata:
GET https://notes.mctx.ai/.well-known/oauth-protected-resource:
``json``
{
"resource": "https://notes.mctx.ai",
"authorization_servers": ["https://notes.mctx.ai"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email"]
}
GET https://notes.mctx.ai/.well-known/oauth-authorization-server:
``json``
{
"issuer": "https://notes.mctx.ai",
"authorization_endpoint": "https://notes.mctx.ai/authorize",
"token_endpoint": "https://auth.mctx.ai/oauth/token",
"registration_endpoint": "https://notes.mctx.ai/oauth/register",
"grant_types_supported": ["authorization_code"],
"scopes_supported": ["openid", "profile", "email"],
"code_challenge_methods_supported": ["S256"]
}
- No revocation markers written. We verified no logout, account closure, or admin token revocation event occurred during the affected window.
The client is not sending a single byte to these servers. There is nothing the server can do to recover from a poisoned discovery cache.
---
Root Cause Analysis
The mechanism described in #44774 matches what we observe exactly:
- v2.1.85 added RFC 9728 Protected Resource Metadata discovery.
- When a transient connection failure occurs (network blip, brief 401 during token exchange, anything), Claude Code enters the OAuth discovery flow.
- The discovery probes the server URL for
/.well-known/oauth-authorization-server. mctx servers respond correctly with valid metadata — so the discovery succeeds. - Claude Code saves this discovery state to the macOS keychain under the server URL.
- On the next startup,
hasMcpDiscoveryButNoToken()finds the cached discovery state but no matching valid token (possibly because the original token was consumed or invalidated during the failed attempt, or the token key doesn't match the discovery cache key). - The connection is permanently skipped. No retry. No re-auth prompt. Just silence.
The critical issue is that discovery state and token storage appear to use different keying strategies or have a matching bug, causing hasMcpDiscoveryButNoToken() to return true even when the user has successfully authenticated before.
We also see this reproduce across multiple machines simultaneously — consistent with both machines running the same v2.1.85+ code and both experiencing a transient failure during the same re-authentication window.
---
Impact
This affects every OAuth-based MCP server, not just mctx. Any server implementing the MCP OAuth spec correctly — serving RFC 9728 and RFC 8414 metadata — is vulnerable to permanent poisoning after a single transient failure.
Servers that use static API keys (context7, Linear, Datadog with API key mode) are completely unaffected because they never go through discovery. The bug only strikes servers doing OAuth the way the spec says to do it.
From the GitHub issue tracker, this pattern is also reported by Atlassian MCP users (#28262), and the auth token storage bugs trace back to at least v1.x (#28256, #19481, #10250). But the v2.1.85 change made it permanent — before, a bad auth state might cause a re-auth prompt; now it causes a silent permanent skip with no recovery path visible to the user.
At scale, this means every subscriber to an OAuth-based MCP hosting platform loses access to all their connected servers simultaneously, with no error message explaining why or how to fix it.
---
Workaround
Manual re-authentication via /mcp → clicking the server's auth link works when the user can find it. But:
- The
/mcpdialog doesn't always surface servers that need re-auth (#30272). - There is no error message telling users why their servers aren't connecting.
- On a platform like mctx where subscribers have multiple servers, they need to re-authenticate each one individually.
The keychain-clearing workaround from issue #44774 works for technical users:
security delete-generic-password -s "Claude Code-credentials" 2>/dev/null
rm ~/.claude/mcp-needs-auth-cache.json 2>/dev/null
But this clears all stored credentials, not just the poisoned entries. Not viable as subscriber guidance.
---
Suggested Fix Direction
The hasMcpDiscoveryButNoToken() check should not result in a permanent silent skip. At minimum:
- If discovery state exists but no valid token is found, attempt re-authentication rather than permanently skipping.
- Or: surface a visible error/re-auth prompt so the user knows what's happening.
- Or: tie the discovery cache lifetime to the token lifetime — if the token is gone, the discovery state should be cleared.
The discovery cache poisoning is a state consistency issue: discovery state lives longer than the tokens it was created alongside. When the tokens disappear (for any reason), the orphaned discovery state becomes a permanent block.
---
Related Issues
- #44774 —
headersHelper silently ignored since v2.1.85(direct duplicate, same root cause) - #44535 —
Local HTTP MCP server shows 'needs authentication'(same symptom) - #41690 —
headersHelper for HTTP MCP servers is silently ignored(closed as duplicate of #44774, not fixed) - #28262 —
MCP OAuth tokens not auto-refreshing despite valid refresh tokens(related token storage bug) - #19481 —
/mcp reconnect doesn't refresh expired OAuth access tokens - #28256 —
MCP OAuth Token Not Persisting After Authentication - #30272 —
/mcp menu doesn't surface servers needing re-auth
---
Changelog Verification
We checked the Claude Code changelog for all releases from v2.1.85 (March 26, 2026) through v2.1.92 (April 4, 2026). There are zero MCP authentication-related changes in any of these seven releases. Issue #44774 was opened today (April 7) and has no associated fix PR. Issue #41690 was closed on April 4 as a duplicate, not as fixed.
The v2.1.85 regression is still fully present in v2.1.92. We confirmed this with the debug logs included above from a live session this morning.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗