Initial Checks
Description
MCP client does not retry authenticated request after successful OAuth token exchange
Summary
The Claude.ai MCP client (identified as python-httpx/0.28.1 in nginx logs) completes the full OAuth 2.0 authorization code flow — including a successful POST /oauth/token response — but never sends an authenticated follow-up request to GET /sse (or POST /sse) with the issued Bearer token. The connection silently fails after token issuance.
Environment
- Client: Claude.ai web app / Claude mobile app (iOS)
- MCP client user-agent:
python-httpx/0.28.1
- Server: Custom MCP SSE server (FastMCP + Starlette + uvicorn, behind Cloudflare tunnel + nginx)
- Transport: SSE (
mcp.sse_app())
- MCP spec version: 2025-03-26
- Auth: Custom OAuth 2.0 implementation with dynamic client registration
Steps to Reproduce
- Stand up an MCP SSE server with OAuth 2.0 per the 2025-03-26 spec
- Expose it via a public HTTPS URL
- Add the server URL to Claude.ai
- Observe: the app discovers OAuth metadata, registers a client, completes the authorization code + PKCE flow, receives a
200 from POST /oauth/token with a valid Bearer token
- Observe: no further requests arrive at the server. The app does not retry
GET /sse or POST /sse with Authorization: Bearer <token>
Expected Behavior
After receiving a valid token from POST /oauth/token, the client should retry the original SSE connection request with Authorization: Bearer <token> in the header.
Actual Behavior
Nginx access logs confirm zero authenticated requests arrive after token issuance. The OAuth flow completes successfully on the server side and the connection is dropped client-side.
Evidence the Server Is Working Correctly
Manual curl with a valid Bearer token successfully opens the SSE stream and receives a properly-formed MCP response:
curl -s -X POST "https://theburrow.dev/sse" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}},"id":1}'
Response:
HTTP/2 200
content-type: text/event-stream
mcp-session-id: <redacted>
<img width="1079" height="1034" alt="Image" src="https://github.com/user-attachments/assets/2e46a305-a74f-4338-b561-bfbc286b1718" />
event: message
data: {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters"}}
The -32602 error is expected (test curl was missing clientInfo) — the point is the server accepted the token, opened the stream, and responded over SSE. The full stack is functional.
OAuth Sequence Confirmed Working (from nginx logs)
GET /.well-known/oauth-protected-resource → 200
GET /.well-known/oauth-authorization-server → 200
POST /oauth/register → 200
GET /oauth/authorize → 307 (redirect to claude.ai callback)
POST /oauth/token → 200
[no further requests]
Additional Notes
- The initial probe is
POST /sse → 405, which appears to trigger OAuth discovery. This is not a GET /sse → 401 + WWW-Authenticate flow as specified — it's possible the app's retry logic after 405-triggered OAuth is different from the 401-triggered path and the reconnect target URL is getting lost.
- The server does return
401 + WWW-Authenticate: Bearer on unauthenticated GET /sse requests (added during debugging). This did not change the behavior.
- Tested on both Claude.ai web and Claude mobile (iOS). Same result on both.
Example Code
Python & MCP Python SDK
Python 3.11
MCP Python SDK 1.27.1
Initial Checks
Description
MCP client does not retry authenticated request after successful OAuth token exchange
Summary
The Claude.ai MCP client (identified as
python-httpx/0.28.1in nginx logs) completes the full OAuth 2.0 authorization code flow — including a successfulPOST /oauth/tokenresponse — but never sends an authenticated follow-up request toGET /sse(orPOST /sse) with the issued Bearer token. The connection silently fails after token issuance.Environment
python-httpx/0.28.1mcp.sse_app())Steps to Reproduce
200fromPOST /oauth/tokenwith a valid Bearer tokenGET /sseorPOST /ssewithAuthorization: Bearer <token>Expected Behavior
After receiving a valid token from
POST /oauth/token, the client should retry the original SSE connection request withAuthorization: Bearer <token>in the header.Actual Behavior
Nginx access logs confirm zero authenticated requests arrive after token issuance. The OAuth flow completes successfully on the server side and the connection is dropped client-side.
Evidence the Server Is Working Correctly
Manual curl with a valid Bearer token successfully opens the SSE stream and receives a properly-formed MCP response:
Response:
The
-32602error is expected (test curl was missingclientInfo) — the point is the server accepted the token, opened the stream, and responded over SSE. The full stack is functional.OAuth Sequence Confirmed Working (from nginx logs)
Additional Notes
POST /sse → 405, which appears to trigger OAuth discovery. This is not aGET /sse → 401 + WWW-Authenticateflow as specified — it's possible the app's retry logic after405-triggered OAuth is different from the401-triggered path and the reconnect target URL is getting lost.401 + WWW-Authenticate: Beareron unauthenticatedGET /sserequests (added during debugging). This did not change the behavior.Example Code
Python & MCP Python SDK