MCP OAuth client invents `<gateway-origin>/authorize` instead of resolving the RFC 9728 discovery chain (authorization_servers → AS metadata) — non-compliant with MCP auth spec

Status Closed — duplicate
Reported on v2.1.274
Maintainer reply None cached
Activity 2 comments · opened Sep 25, 2026 · closed Oct 11, 2026

Environment

  • Claude Code 2.1.274, macOS
  • Remote MCP server behind an API gateway (HTTP transport), authorization server = Okta (https://idp.example.com/oauth2/default)
Note: this is the same class of defect as #80731 (closed as stale without a fix), #85563 and #66456. Re-filing with a fully curl-verifiable reproduction, because the gateway-side metadata is provably RFC 9728-compliant — the bug is entirely in Claude Code's client-side discovery resolution.

Summary

When the MCP server returns a 401 challenge with WWW-Authenticate: Bearer resource_metadata="...", Claude Code never resolves the protected-resource metadata and never follows authorization_servers to fetch the authorization server's metadata. Instead it fabricates an authorize endpoint on the resource server's own origin (https://<gateway-host>/authorize?...), which 404s.

This violates:

  1. RFC 9728 §3.1 — protected resource metadata's authorization_servers field exists precisely so the resource can advertise its AS(s); the client MUST use it rather than assuming the resource origin is the AS.
  2. The MCP authorization specification (2025-06-18 and later drafts) — which normatively requires clients to implement RFC 9728 discovery from the WWW-Authenticate challenge on 401, then perform AS metadata discovery (RFC 8414) on the returned issuer.
  3. RFC 8414 §3.1 — the authorization_endpoint used in the redirect MUST come from the AS's well-known metadata, not be constructed by the client.

Reproduction (all steps verified with curl — gateway config is internally consistent)

  1. Client sends an unauthenticated request to the MCP route:

``
POST https://mcp.example.com/mcp/datadog
``

  1. Gateway correctly answers with a spec-compliant challenge:

``
HTTP/1.1 401
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
``

  1. That metadata document resolves correctly:

``json
{
"resource": "https://mcp.example.com/mcp/datadog",
"authorization_servers": ["https://idp.example.com/oauth2/default"]
}
``

  1. The advertised AS's real metadata resolves correctly too:

``
https://idp.example.com/oauth2/default/.well-known/openid-configuration
→ authorization_endpoint: https://idp.example.com/oauth2/default/v1/authorize
→ token_endpoint: https://idp.example.com/oauth2/default/v1/token
``

Expected

Claude Code:

  1. Parses the WWW-Authenticate challenge and fetches resource_metadata (RFC 9728 §5).
  2. Follows authorization_servers[0] and fetches the AS metadata (RFC 8414 / OIDC discovery).
  3. Redirects the browser to the discovered authorization_endpoint (Okta) and redeems the code at the discovered token_endpoint.

Actual

Claude Code constructs https://mcp.example.com/authorize?... — an endpoint that exists nowhere in any metadata document — and sends the browser there, which 404s. No request is ever made to the protected-resource metadata URL or to the Okta issuer's discovery document.

Impact

Any correctly-implemented MCP deployment with a gateway/proxy as resource server and a separate IdP as authorization server (Okta, Entra ID, Cognito, Auth0, Keycloak — the single most common enterprise topology) cannot be authenticated by Claude Code at all. The failure is silent and looks like a server-side 404, which sends users debugging the wrong component.

Suggested fix

In the MCP OAuth client flow:

  • On 401, extract resource_metadata from the WWW-Authenticate header and fetch it (falling back to RFC 9728 well-known path insertion only if the header is absent).
  • Resolve the AS from authorization_servers[0] of that document.
  • Run standard issuer metadata discovery (.well-known/openid-configuration, then RFC 8414 path-insertion fallback — see also #95270) and use the discovered authorization_endpoint / token_endpoint verbatim.

Upstream fix

The root cause lives in the vendored @modelcontextprotocol/sdk client auth code, and a fix has been proposed upstream in the TypeScript SDK:

  • modelcontextprotocol/typescript-sdk#2785 — fix(client): OAuth well-known fallback on 4xx and authorization-server origin — fixes the fallback URL construction so authorization-server metadata discovery stays on the AS host instead of falling back to the resource-server origin (this issue's /authorize fabrication), and widens the 4xx fallback trigger.

Once that PR is merged, Claude Code should pick it up via an SDK dependency bump. Related SDK issues: #2784, #2866.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗