[Bug] MCP OAuth flow skips /authorize step after successful dynamic client registration
MCP Integration Bug Report — Claude.ai never calls /authorize after successful DCR
Summary
Claude.ai successfully completes OAuth discovery (PRM + OASM) and dynamic client registration (DCR → 201) against my MCP server, but never proceeds to the authorization step. The browser redirect to /authorize never happens. Instead, Claude.ai loops: register → POST / (unauthenticated) → 401 → re-discover → re-register → repeat 3× → give up. This has been reproduced across two different hostnames, multiple connection attempts, and with a completely clean OAuth database each time.
A second MCP server running identical code on a different Tailscale Funnel hostname (same tailnet) connects successfully on the first attempt.
Error References
- bca0be77418cd44e (fresh hostname attempt)
- cdc6c92b18883c27 (port 443, old hostname)
- c2d68aa824df3ac4 (port 443, old hostname)
- 06c4721a671f38eb (port 8443 attempt)
Working Server (for comparison)
- URL:
https://server-a.example.ts.net(redacted — Tailscale Funnel hostname) - Connects to Claude.ai successfully — full OAuth flow completes including /authorize, /login, /token
Failing Server
- URL:
https://server-b.example.ts.net(redacted — different Tailscale Funnel hostname, same tailnet) - Also tried a third hostname on the same machine and port 8443 — same behavior
- Claude.ai discovers and registers but NEVER calls /authorize
Evidence that the server is correct
- Protocol responses are identical. The 401 headers+body, PRM JSON, OASM JSON, and DCR 201 response from the failing server are structurally identical to the working server — only the hostname differs.
/authorizeworks when called manually. Hitting/authorizewith a valid client_id, redirect_uri, PKCE challenge, scope, and state returns HTTP 302 with a Location header pointing to the login page. The login page loads correctly with the correct form action URL.
- Fresh hostname made no difference. Changed the Tailscale hostname to a completely new name, updated MCP_SERVER_URL, deleted the OAuth DB, restarted the server. Claude.ai exhibited the exact same behavior on the new hostname.
- OAuth provider code is identical. Line-by-line diff of the OAuth provider against the working server shows only cosmetic differences (class name, login page title). The authorize(), register_client(), token exchange, and OASM construction methods are code-identical.
- SDK versions match. Both servers: mcp 1.26.0, fastmcp 3.1.0.
Claude.ai's exact request sequence (from server logs)
10:59:49 POST / → 401 (no auth)
10:59:52 GET /.well-known/oauth-protected-resource → 200
10:59:54 GET /.well-known/oauth-authorization-server → 200
10:59:55 POST / → 401 (unexpected re-probe)
10:59:56 POST /register → 201 (client registered)
10:59:57 GET /.well-known/oauth-protected-resource → 200 (re-discovers!)
10:59:59 GET /.well-known/oauth-authorization-server → 200
11:00:04 POST /register → 201 (re-registers!)
11:00:04 POST / → 401 (2nd IP)
11:00:06 GET /.well-known/oauth-protected-resource → 200
11:00:09 GET /.well-known/oauth-authorization-server → 200
11:00:12 POST / → 401
11:00:12 POST /register → 201 (3rd registration)
11:00:14 GET / → 401
11:00:15 GET / → 401 (gives up)
Note: /authorize never appears. The registration response (201 with client_id, client_secret) is not being used — Claude.ai re-discovers and re-registers instead of proceeding to authorization.
DCR Response (what the server sends back)
{
"client_id": "<uuid>",
"client_secret": "<uuid>",
"client_id_issued_at": 1741776596,
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"token_endpoint_auth_method": "client_secret_post",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "mcp",
"client_name": "Claude"
}
What I think is happening
Claude.ai's MCP client receives the 201 DCR response but fails to persist or use the registration result. Instead of constructing an authorization URL and redirecting the browser, it restarts the flow from scratch. This happens identically on two different hostnames, ruling out hostname-specific cached state.
The working server returns an identical 201 response and Claude.ai proceeds to /authorize on the first try.
Request
Can you check the client-side MCP OAuth flow for these error references? Specifically:
- Is the 201 DCR response being parsed and stored?
- What happens between receiving the 201 and the expected browser redirect to /authorize?
- Is there a validation check on the DCR response that fails silently?
- Is there hostname-specific state or rate limiting that could cause this?
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗