MCP OAuth: no per-server "clear authentication", and no re-registration when a cached DCR client is rejected

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

Environment

  • Claude Code 2.1.215 (native install), macOS
  • Remote HTTP MCP server using OAuth 2.0 Dynamic Client Registration (RFC 7591). Concretely hit with a self-hosted Metabase MCP (/api/metabase-mcp), but the issue is general to any DCR-based OAuth MCP server.

Problem

Claude Code caches the dynamically-registered OAuth client_id per server (on macOS in the keychain item Claude Code-credentials, under an mcpOAuth object). When the server drops or rotates its registered clients — e.g. a self-hosted instance redeploys/restarts and doesn't persist DCR client registrations — the cached client_id becomes invalid. From that point:

  1. /mcp → _server_ → Authenticate reuses the stale client_id rather than performing a fresh registration, so the browser flow fails identically every time (the auth server rejects the unknown client; Metabase surfaces a generic {"error":"invalid_request","error_description":"The authorization request is invalid."}).
  2. There is no per-server "clear authentication" action in the /mcp UI — only "Authenticate", which reuses the dead client. The only recovery is either manual keychain surgery to delete just that server's mcpOAuth entry, or deleting the entire Claude Code-credentials item (which also logs you out of Claude and drops every server's registration, forcing a full re-auth).

Net effect: a self-hosted OAuth MCP server that recycles DCR clients on deploy permanently wedges every developer who authenticated before the deploy, with no in-product recovery.

Expected behavior

Either (ideally both):

  • (a) Auto re-register. When the authorization or token endpoint rejects a cached client (invalid_client, or a generic invalid_request that resolves to an unknown/again-invalid client), transparently perform a fresh DCR (registration_endpoint) and retry, instead of reusing the cached client_id.
  • (b) Per-server "clear authentication" / "re-register" action in the /mcp UI, so a single server can be reset without nuking the whole credential store.

Reproduction

  1. Configure a remote HTTP MCP server that advertises OAuth with a registration_endpoint (DCR) and authenticate it via /mcp (Claude caches a client_id).
  2. Invalidate that client server-side — e.g. restart/redeploy an instance that stores DCR clients ephemerally (or otherwise rotate/expire registered clients).
  3. /mcp → _server_ → Authenticate → fails, and keeps failing on every retry. The client_id in the /oauth/authorize?... URL is unchanged across attempts (only code_challenge/state rotate), confirming the stale cached client is being reused.

Diagnostic tell (for others hitting this)

In the browser's /oauth/authorize?... URL: a stable client_id across two Authenticate attempts ⇒ stale cached client (this issue). A different client_id each attempt ⇒ Claude is re-registering and something else is wrong.

Related nicety

A machine-local event (a Claude Code native auto-update, or a Claude account switch/re-login) can reset the local MCP OAuth cache, forcing re-auth of all servers at once. A short note in the MCP docs about which events invalidate cached MCP OAuth would cut down on "why did every server suddenly need re-auth?" confusion.

Related issues

  • #33704 (docs: re-authentication behavior after refresh-token expiry) — adjacent, but about refresh expiry, not stale DCR clients / missing clear-auth.
  • #53803 (closed) — refresh-token reuse; different failure mode.

View original on GitHub ↗

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