Skip to content

MCP client does not retry authenticated request after successful OAuth token exchange #2577

@jkgibson-source

Description

@jkgibson-source

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

  1. Stand up an MCP SSE server with OAuth 2.0 per the 2025-03-26 spec
  2. Expose it via a public HTTPS URL
  3. Add the server URL to Claude.ai
  4. 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
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    authIssues and PRs related to Authentication / OAuthbugSomething isn't workingneeds reproneeds additional information to be able to reproduce bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions