Skip to content

OAuth metadata discovery throws on 5xx instead of falling through to next URL — breaks subpath deployments behind reverse proxies #1631

@Brakistad

Description

@Brakistad

Description

discoverAuthorizationServerMetadata() in packages/client/src/client/auth.ts tries multiple well-known URLs in sequence (RFC 8414 path-insert, OIDC path-insert, OIDC path-appended), but throws immediately on 5xx responses instead of continuing to the next URL. Only 4xx responses trigger the fallback.

This breaks MCP servers deployed at a subpath behind a reverse proxy or API gateway (e.g., Azure Application Gateway, nginx, Traefik) when the proxy returns 502 for paths it does not route.

Reproduction

  1. Deploy an MCP server at a subpath, e.g. https://example.com/my-app/mcp
  2. The server's Protected Resource Metadata correctly advertises the authorization server as https://example.com/my-app
  3. The server correctly serves metadata at https://example.com/my-app/.well-known/oauth-authorization-server (200 ✅)
  4. The reverse proxy has no rule for the root-level /.well-known/ path, so https://example.com/.well-known/oauth-authorization-server/my-app returns 502 (no backend)

The SDK tries URLs in this order:

  1. https://example.com/.well-known/oauth-authorization-server/my-app502 → ❌ throws immediately
  2. https://example.com/.well-known/openid-configuration/my-app → never reached
  3. https://example.com/my-app/.well-known/openid-configuration → never reached (would have worked)

Expected Behavior

A 5xx from a reverse proxy for a non-existent well-known path is semantically equivalent to a 404 — the endpoint does not exist. The discovery loop should continue to the next fallback URL, not throw.

Root Cause

Two locations in auth.ts:

1. discoverAuthorizationServerMetadata() (~line 1017):

if (response.status >= 400 && response.status < 500) {
    continue; // Try next URL — but ONLY for 4xx
}
throw new Error(\`HTTP \${response.status}...\`); // 5xx throws immediately

2. shouldAttemptFallback() (~line 820):

function shouldAttemptFallback(response: Response | undefined, pathname: string): boolean {
}

Both functions only treat 4xx as "try next". A 502/503/504 from a reverse proxy kills the entire discovery.

Suggested Fix

Change the condition to continue on any non-OK response:

// In discoverAuthorizationServerMetadata:
    await response.text?.().catch(() => {});
    continue; // Try next URL for any non-OK status
}
// In shouldAttemptFallback:
function shouldAttemptFallback(response: Response | undefined, pathname: string): boolean {
}

Environment

  • @modelcontextprotocol/sdk version: 1.25.3 (bundled in mcp-remote 0.1.38)
  • MCP server: ASP.NET Core with ModelContextProtocol.AspNetCore v1.0.0, deployed on AKS behind Azure Application Gateway Ingress Controller (AGIC)
  • The server correctly implements RFC 9728 Protected Resource Metadata and serves OAuth metadata at the subpath, but cannot serve at the RFC 8414 path-insert URL because the reverse proxy does not route /.well-known/* to the app

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions