anthropics / anthropics/claude-ai-mcp
Custom Connector: POST /token never called after successful /authorize (mcp_token_exchange_failed)
- Dominant language
- No language data
- Stars
- 471
- Forks
- 76
- PR merge metrics
- No merged PRs in 30d
Description
### MCP server URL
https://garmin.kopylov.pw:8443/mcp
### Where does the issue occur?
When pressing the Connect button
### Transport used
streamable-http
### Client registration type
My server does not have auth
### SSE only: Does your server return the expected status code for POST requests?
Not applicable (my server does not use SSE)
### When did you last reproduce this?
2026-08-11 18:28 IST (12:58 UTC)
### Browser and OS
Chrome MacOS 15.6.1 (24G90)
### Describe the issue
/authorize succeeds and redirects correctly (302 to https://claude.ai/api/mcp/auth_callback?code=...&state=...), but Claude never follows up with POST /token. Confirmed with server logs and an 8-minute tcpdump capture during a real connection attempt: zero inbound TCP connections to my server from any IP after the callback, from any address (not just Anthropic's documented 160.79.104.0/21 range — literally nothing).
I manually drove the full OAuth+MCP flow end-to-end with my own PKCE keypair to rule out any server-side bug:
1. GET /authorize (real browser session) -> 302 with a real code
2. POST /token with that code + my own code_verifier -> 200, valid access_token
3. POST /mcp initialize with that access_token -> 200, correct MCP handshake
4. POST /mcp tools/call (health) -> 200, real data returned
Every step works correctly when actually invoked. The only step missing in real Claude connector attempts is step 2 — Claude's backend never performs it.
I also implemented full RFC 7591 Dynamic Client Registration (POST /register, registration_endpoint in Authorization Server Metadata) after seeing "Couldn't register with Garmin's sign-in service" errors with empty Client ID/Secret fields — but Claude never calls /register either, even after fully deleting and re-adding the connector (confirmed via server logs, zero hits).
Separately, I noticed Claude's actual /authorize request is missing the `resource` parameter (RFC 8707), which the MCP Authorization spec (2025-11-25) says clients MUST send in both authorization and token requests, regardless of server support.
Error shown in Claude: "Authorization with Garmin failed... If this persists, share this reference with support" with error_code=mcp_token_exchange_failed.
Reference IDs (flow_id) from failed attempts: ofid_5b1feeb46f95921c, ofid_d62936bb69864452, ofid_88fc04aedf55460d
Connector/server UUID: deb33d42-eb6d-4020-bb07-06a3f80fa381
Discovery endpoints (all return correct, spec-compliant responses):
- https://garmin.kopylov.pw:8443/.well-known/oauth-protected-resource
- https://garmin.kopylov.pw:8443/.well-known/oauth-authorization-server
- https://garmin.kopylov.pw:8443/.well-known/openid-configuration (added experimentally)
Happy to share full logs, the tcpdump capture, or a more detailed written report on request.
### Issue details
```shell
Error shown in Claude after the OAuth redirect completes: "Authorization with Garmin failed. You can check your credentials and permissions. If this persists, share this reference with support: ofid_88fc04aedf55460d" (also reproduced with ofid_5b1feeb46f95921c and ofid_d62936bb69864452 on separate attempts).
MCP server URL: https://garmin.kopylov.pw:8443/mcp
Auth: OAuth 2.1, Authorization Code + PKCE S256, redirect_uri allowlisted to https://claude.ai/api/mcp/auth_callback, token_endpoint_auth_method: "none" (public client). RFC 7591 Dynamic Client Registration also implemented and available (POST /register, registration_endpoint advertised) but never invoked by Claude.
Server-side, /authorize completes successfully and issues a valid authorization code seconds before the failure is shown — no error, no delay, no retry from Claude's side. POST /token is never called afterward.
```
### Expected behavior
After the 302 redirect to https://claude.ai/api/mcp/auth_callback?code=...&state=... completes, Claude should extract the code and state, then POST to the token_endpoint advertised in /.well-known/oauth-authorization-server (grant_type=authorization_code, code, redirect_uri, code_verifier) to exchange the code for an access token, and finally use that Bearer token to initialize the MCP session.
Instead, Claude never makes the POST /token request at all, and the connector is reported as failed.
### Logs from your server
```shell
2026-08-11T12:58:03Z OAuth /authorize: issued code_hash=e50934ca06e4 user_id=1 client_id='1' redirect_uri='https://claude.ai/api/mcp/auth_callback' state_present=True location='https://claude.ai/api/mcp/auth_callback?code=yMy-5J28kb3R2hp-OOO6LWl6x-feb8A-iwcWACyDlmo&state=zHNajM245q7GE7ADc1nMBVTQ9lvJA40M-RNarBkNFGc'
2026-08-11T12:58:03Z GET /authorize?response_type=code&client_id=1&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback&code_challenge=WhV7HplehMVjtiEgxRh0j9JxxoNJfopCb5MWg8sJoaY&code_challenge_method=S256&state=zHNajM245q7GE7ADc1nMBVTQ9lvJA40M-RNarBkNFGc HTTP/1.1" 302 Found
-- no POST /token from any external caller follows this, ever --
Across the full lifetime of the server (7+ separate real connection attempts, all producing the same 302 above with a fresh code each time), POST /token has never once been called by Claude. The only POST /token entries in our logs are our own manual/diagnostic tests.
We also ran an 8-minute tcpdump capture on the server's external interface (filtered on tcp port 8443, port 80, and Anthropic's documented egress range 160.79.104.0/21) during a real connection attempt from inside a Claude.ai chat. Result: zero inbound TCP connections to the server from any IP other than the user's own browser (which made the /authorize request above) — no SYN packet, nothing, from any address, after the callback.
To confirm this is not a bug in our /token or /mcp implementation, we manually completed the same flow end-to-end with our own PKCE keypair (using a real authorization code obtained via the resource owner's own authenticated browser session, not fabricated):
POST /token (real code + our own code_verifier) -> 200 {"access_token":"1:","token_type":"bearer"}
POST /mcp initialize (with that access_token as Bearer) -> 200, correct MCP handshake
POST /mcp tools/call "health" -> 200 {"status":"ok","checks":{"database":"ok"}}
Every step of the OAuth + MCP chain works correctly when actually invoked by a real client. The only missing piece in real Claude connector attempts is the POST /token call itself.
```
### Additional context
_No response_
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.