MCP OAuth: stale dynamic client registration replayed forever after server prunes it ("Unrecognized client_id"); no clear-auth option in /mcp

Status Open
Reported on v2.1.223
Maintainer reply None cached
Activity 1 comment · opened Aug 6, 2026

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:supabasehttps://mcp.supabase.com/mcp, authorization server https://api.supabase.com/v1/oauth

Steps to reproduce

  1. Authenticate a remote MCP server that uses OAuth dynamic client registration (e.g. the Supabase plugin). Works fine.
  2. 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).
  3. Run /mcp → select the server → Authenticate.
  4. 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
  1. The browser shows only:
{"message":"Unrecognized client_id"}
  1. Every retry replays the same dead client_id. No amount of /mcp re-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

  1. 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 dead client_id.
  2. Expose "Clear authentication" in /mcp for plugin MCP servers so users can recover without editing the Keychain by hand.
  3. Garbage-collect superseded mcpOAuth entries: seven dead entries for one server suggests old registrations are never cleaned up when a new one is created for the same server.

View original on GitHub ↗

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