Skip to content

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

Closed

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-app → 502 → ❌ 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

Activity

  1. matantsach commented on Mar 5, 2026

    @matantsach
    Contributor

    I'd like to work on this. The fix is straightforward — two locations:

    1. discoverAuthorizationServerMetadata(): The loop currently only continues for 4xx responses and throws for 5xx. Changing it to continue for any non-OK response lets the discovery fall through to the next URL.

    2. shouldAttemptFallback(): Same issue — the response.status >= 400 && response.status < 500 condition prevents 5xx from triggering the root URL fallback in the deprecated discoverMetadataWithFallback path. Changing to !response.ok fixes this.

    Will have a PR up shortly with tests covering the 5xx fallthrough scenarios.

  2. added a commit that references this issue on Mar 5, 2026
    3d98845
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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