MCP OAuth: stale dynamic client registration replayed forever after server prunes it ("Unrecognized client_id"); no clear-auth option in /mcp
Summary
When the authorization server behind a remote MCP server prunes a dynamic client registration (RFC 7591), Claude Code keeps replaying the cached client_id from its credential store on every subsequent authenticate attempt. The authorize endpoint rejects it before any login/consent screen, and authentication for that MCP server is permanently broken until the user hand-edits the credential store. There is no way to recover from inside Claude Code: the /mcp menu offers no "clear authentication" option for plugin MCP servers.
This is the root cause behind #80748 and #84609 (both filed against the Supabase MCP, but the defect is generic — it will hit any MCP server whose authorization server expires or prunes DCR clients).
Environment
- Claude Code 2.1.223, macOS (darwin 25.5.0); also reproduced by other users on Windows 11 and Ubuntu 24.04 (see #80748, #84609)
- Server:
plugin:supabase:supabase→https://mcp.supabase.com/mcp, authorization serverhttps://api.supabase.com/v1/oauth
Steps to reproduce
- Authenticate a remote MCP server that uses OAuth dynamic client registration (e.g. the Supabase plugin). Works fine.
- Wait for the authorization server to prune the dynamic client registration (Supabase appears to do this routinely — over months my store accumulated seven dead registrations for this one server).
- Run
/mcp→ select the server → Authenticate. - Claude Code opens an authorize URL reusing the cached, now-deleted
client_id:
https://api.supabase.com/v1/oauth/authorize?response_type=code&client_id=12d58779-6f9d-4e77-b4d3-77a31a81b67e&code_challenge=…&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost%3A49785%2Fcallback&state=…&scope=…&resource=https%3A%2F%2Fmcp.supabase.com%2Fmcp
- The browser shows only:
{"message":"Unrecognized client_id"}
- Every retry replays the same dead
client_id. No amount of/mcpre-authentication recovers.
Evidence from the credential store
On macOS the live store is the Keychain item Claude Code-credentials (the legacy ~/.claude/.credentials.json also exists but was stale). Its mcpOAuth object contained seven plugin:supabase:supabase|<hash> entries accumulated over months, each with a different pruned client_id. The entry plugin:supabase:supabase|9910156a2b9fd53f held clientId: 12d58779-6f9d-4e77-b4d3-77a31a81b67e — exactly the value in the failing authorize URL — with a clientSecret but no access/refresh token (a registration cached from a flow that never completed, then replayed forever).
Workaround (manual)
Delete the matching mcpOAuth entries from the credential store, restart Claude Code, then /mcp → Authenticate. A fresh dynamic client registration is created and the flow completes immediately. On macOS:
BLOB=$(security find-generic-password -s "Claude Code-credentials" -w)
CLEANED=$(printf '%s' "$BLOB" | jq -c '.mcpOAuth |= with_entries(select(.key | test("supabase") | not))')
security add-generic-password -U -a "$USER" -s "Claude Code-credentials" -w "$CLEANED"
That this works on the first try confirms the registration cache — not the plugin, scopes, or the Supabase account — is the problem.
Expected behavior / proposed fixes
- Invalidate on rejection: when the authorize (or token) endpoint answers with
invalid_client/ an "unrecognized client" error, discard the cached client registration and transparently re-run dynamic client registration instead of replaying the deadclient_id. - Expose "Clear authentication" in
/mcpfor plugin MCP servers so users can recover without editing the Keychain by hand. - Garbage-collect superseded
mcpOAuthentries: seven dead entries for one server suggests old registrations are never cleaned up when a new one is created for the same server.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗