MCP server URL
{{internal company URL}}
Where does the issue occur?
When pressing the Connect button
Transport used
streamable-http
Client registration type
Dynamic Client Registration (DCR)
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-03-04 10:00am AEST
Browser and OS
ARC
Describe the issue
Summary
Claude.ai's MCP OAuth client fetches /.well-known/oauth-authorization-server metadata successfully but ignores the authorization_endpoint and token_endpoint values. Instead, it constructs /authorize and /token URLs from the MCP server's base URL. It also strips the path from registration_endpoint, hitting /register instead of the advertised /mcp/oauth/register.
This means any MCP server using an external authorization server (FusionAuth, Keycloak, Okta, Auth0, Entra ID, etc.) fails to authenticate with Claude.ai, even though the same server works correctly with ChatGPT and Claude Code CLI.
Environment
- MCP server: Custom Express.js server at {{internal company URL}}
- Authorization server: FusionAuth at
https://{{company auth server}}
- MCP spec version: Implements RFC 9728 (Protected Resource Metadata) + RFC 8414 (Authorization Server Metadata) + RFC 7591 (Dynamic Client Registration)
- Claude.ai user-agent:
python-httpx/0.28.1 (backend), Claude-User (MCP calls post-auth)
- Date tested: 2026-03-04
The Three Bugs
Bug 1: registration_endpoint path is stripped
Metadata advertises:
registration_endpoint: "https://<server>/mcp/oauth/register"
Claude.ai hits:
Expected:
POST /mcp/oauth/register → 201
Bug 2: authorization_endpoint is ignored
Metadata advertises:
authorization_endpoint: "https://{{company auth server}}/oauth2/authorize"
Claude.ai constructs:
GET https://<server>/authorize?response_type=code&client_id=...
Expected:
GET https://{{company auth server}}/oauth2/authorize?response_type=code&client_id=...
Bug 3: token_endpoint is ignored
Metadata advertises:
token_endpoint: "https://{{company auth server}}/oauth2/token"
Claude.ai constructs:
POST https://<server>/token
Expected:
POST https://{{company auth server}}/oauth2/token
Server Logs: Claude.ai vs ChatGPT
Claude.ai flow (fails without workarounds)
# Step 1: Initial MCP request → 401 (correct)
POST /mcp UA: python-httpx/0.28.1 → 401
# Step 2: Fetches protected resource metadata (correct)
GET /.well-known/oauth-protected-resource/mcp → 200
Response: {
"resource": "https://<server>/mcp",
"authorization_servers": ["https://<server>"],
"scopes_supported": ["mcp:read","mcp:write","mcp:admin"],
"bearer_methods_supported": ["header"]
}
# Step 3: Fetches authorization server metadata (correct)
GET /.well-known/oauth-authorization-server → 200
Response: {
"issuer": "....com",
"authorization_endpoint": "https://{{company auth server}}/oauth2/authorize", ← IGNORED
"token_endpoint": "https://{{company auth server}}/oauth2/token", ← IGNORED
"registration_endpoint": "https://<server>/mcp/oauth/register", ← PATH STRIPPED
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code","refresh_token"],
"code_challenge_methods_supported": ["S256"],
...
}
# Step 4: OpenID fallback (expected, fails gracefully)
GET /.well-known/openid-configuration → 404
# Step 5: BUG — Hits /register instead of /mcp/oauth/register
POST /register → 404 ← SHOULD BE POST /mcp/oauth/register
# Step 6: BUG — Hits /authorize on MCP server instead of {{company auth server}}
GET /authorize?response_type=code&client_id=... → 404 ← SHOULD GO TO {{company auth server}}
# Step 7: BUG — Hits /token on MCP server instead of {{company auth server}}
POST /token → 404 ← SHOULD GO TO {{company auth server}}
ChatGPT flow (works correctly)
# Step 1: Initial MCP request → 401 (same)
POST /mcp UA: Python/3.12 aiohttp/3.13.3 → 401
# Step 2: Fetches protected resource metadata (same)
GET /.well-known/oauth-protected-resource/mcp → 200
# Step 3: Fetches authorization server metadata (same)
GET /.well-known/oauth-authorization-server → 200
# Step 4: OpenID fallback (same)
GET /.well-known/openid-configuration → 404
# Step 5: CORRECT — Uses full registration_endpoint from metadata
POST /mcp/oauth/register → 201 ✅
Body: {"client_name":"ChatGPT","redirect_uris":["https://chatgpt.com/connector/oauth/..."],...}
# Step 6: CORRECT — Redirects user to authorization_endpoint from metadata
# (Browser goes directly to https://{{company auth server}}/oauth2/authorize) ✅
# Step 7: CORRECT — Exchanges code at token_endpoint from metadata
# (POST to https://{{company auth server}}/oauth2/token) ✅
# Step 8: Authenticated MCP calls succeed
POST /mcp UA: openai-mcp/1.0.0 (ChatGPT) → 200 ✅
Claude Code CLI flow (also works)
Claude Code CLI (Bun/1.3.11) correctly uses the full registration_endpoint path and follows the metadata endpoints. User-agent switches to claude-code/2.1.66 (cli) for MCP calls after authentication.
Root Cause Analysis
Claude.ai appears to still implement the old MCP authorization spec (2025-03-26) which states:
"The authorization base URL MUST be determined from the MCP server URL by discarding any existing path component."
The current MCP spec (2025-06-18+) introduced RFC 9728 support, which allows the authorization server to be a completely separate entity from the MCP server. Clients should read authorization_endpoint and token_endpoint from the metadata and use them directly.
ChatGPT and Claude Code CLI follow the current spec. Claude.ai (the web client) does not.
Three Anthropic Clients, Three Behaviors
| Behavior |
Claude.ai (web) |
Claude Code CLI |
ChatGPT |
Fetches /.well-known/oauth-authorization-server |
Yes |
Yes |
Yes |
Uses authorization_endpoint from metadata |
No — appends /authorize to server base URL |
Yes |
Yes |
Uses token_endpoint from metadata |
No — appends /token to server base URL |
Yes |
Yes |
Uses full registration_endpoint path |
No — strips to /register |
Yes |
Yes |
| OAuth user-agent |
python-httpx/0.28.1 |
Bun/1.3.11 |
Python/3.12 aiohttp/3.13.3 |
| MCP user-agent |
Claude-User |
claude-code/2.1.66 (cli) |
openai-mcp/1.0.0 (ChatGPT) |
Impact
Any MCP server that delegates authentication to an external identity provider (FusionAuth, Keycloak, Okta, Auth0, Azure Entra ID, AWS Cognito, etc.) cannot work with Claude.ai without implementing proxy routes on the MCP server itself.
This is the standard architecture for production applications — very few applications embed their own authorization server.
Workaround
The only workaround is to add proxy routes at the MCP server's domain root:
POST /register → mount the registration handler at root (Claude strips /mcp/oauth prefix)
GET /authorize → 302 redirect to authorization_endpoint with all query params forwarded
POST /token → proxy request body to token_endpoint, return the response
This is the same pattern used by Cloudflare's workers-oauth-provider and FastMCP's OAuth proxy.
Related Issues
Expected Fix
Claude.ai's MCP OAuth client should:
- Read
authorization_endpoint from /.well-known/oauth-authorization-server metadata and use it as-is for the authorization redirect
- Read
token_endpoint from the same metadata and use it as-is for token exchange
- Use the full
registration_endpoint URL from metadata without stripping path components
This would bring Claude.ai in line with the current MCP spec, RFC 8414, RFC 9728, and the behavior of ChatGPT and Claude Code CLI.
Issue details
Expected behavior
Not strip the parent path from the auth URL
Logs from your server
Additional context
No response
MCP server URL
{{internal company URL}}
Where does the issue occur?
When pressing the Connect button
Transport used
streamable-http
Client registration type
Dynamic Client Registration (DCR)
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-03-04 10:00am AEST
Browser and OS
ARC
Describe the issue
Summary
Claude.ai's MCP OAuth client fetches
/.well-known/oauth-authorization-servermetadata successfully but ignores theauthorization_endpointandtoken_endpointvalues. Instead, it constructs/authorizeand/tokenURLs from the MCP server's base URL. It also strips the path fromregistration_endpoint, hitting/registerinstead of the advertised/mcp/oauth/register.This means any MCP server using an external authorization server (FusionAuth, Keycloak, Okta, Auth0, Entra ID, etc.) fails to authenticate with Claude.ai, even though the same server works correctly with ChatGPT and Claude Code CLI.
Environment
https://{{company auth server}}python-httpx/0.28.1(backend),Claude-User(MCP calls post-auth)The Three Bugs
Bug 1:
registration_endpointpath is strippedMetadata advertises:
Claude.ai hits:
Expected:
Bug 2:
authorization_endpointis ignoredMetadata advertises:
Claude.ai constructs:
Expected:
Bug 3:
token_endpointis ignoredMetadata advertises:
Claude.ai constructs:
Expected:
Server Logs: Claude.ai vs ChatGPT
Claude.ai flow (fails without workarounds)
ChatGPT flow (works correctly)
Claude Code CLI flow (also works)
Claude Code CLI (
Bun/1.3.11) correctly uses the fullregistration_endpointpath and follows the metadata endpoints. User-agent switches toclaude-code/2.1.66 (cli)for MCP calls after authentication.Root Cause Analysis
Claude.ai appears to still implement the old MCP authorization spec (2025-03-26) which states:
The current MCP spec (2025-06-18+) introduced RFC 9728 support, which allows the authorization server to be a completely separate entity from the MCP server. Clients should read
authorization_endpointandtoken_endpointfrom the metadata and use them directly.ChatGPT and Claude Code CLI follow the current spec. Claude.ai (the web client) does not.
Three Anthropic Clients, Three Behaviors
/.well-known/oauth-authorization-serverauthorization_endpointfrom metadata/authorizeto server base URLtoken_endpointfrom metadata/tokento server base URLregistration_endpointpath/registerpython-httpx/0.28.1Bun/1.3.11Python/3.12 aiohttp/3.13.3Claude-Userclaude-code/2.1.66 (cli)openai-mcp/1.0.0 (ChatGPT)Impact
Any MCP server that delegates authentication to an external identity provider (FusionAuth, Keycloak, Okta, Auth0, Azure Entra ID, AWS Cognito, etc.) cannot work with Claude.ai without implementing proxy routes on the MCP server itself.
This is the standard architecture for production applications — very few applications embed their own authorization server.
Workaround
The only workaround is to add proxy routes at the MCP server's domain root:
POST /register→ mount the registration handler at root (Claude strips/mcp/oauthprefix)GET /authorize→ 302 redirect toauthorization_endpointwith all query params forwardedPOST /token→ proxy request body totoken_endpoint, return the responseThis is the same pattern used by Cloudflare's
workers-oauth-providerand FastMCP's OAuth proxy.Related Issues
authorization_endpoint)/authorizenot/oauth/authorizeurl.originonly)about:blankloop, never contacts auth servernew URL("/path", base)replaces entire path/v1/from registration endpoint/authorizeto base URL instead of reading metadataExpected Fix
Claude.ai's MCP OAuth client should:
authorization_endpointfrom/.well-known/oauth-authorization-servermetadata and use it as-is for the authorization redirecttoken_endpointfrom the same metadata and use it as-is for token exchangeregistration_endpointURL from metadata without stripping path componentsThis would bring Claude.ai in line with the current MCP spec, RFC 8414, RFC 9728, and the behavior of ChatGPT and Claude Code CLI.
Issue details
Expected behavior
Not strip the parent path from the auth URL
Logs from your server
Additional context
No response