Skip to content

OAuth Token Exchange Fails with Separate Authorization Servers #1450

Open

Description

@waltmayf

Describe the bug

When using OAuth with MCP servers that employ separate authorization servers (like AWS Cognito, Auth0, Okta) via RFC 9728 Protected Resource Metadata, the SDK fails during token exchange with an "Invalid api path" error. The root cause is that resourceMetadataUrl is not extracted from the WWW-Authenticate header during the initial connection attempt, causing finishAuth() to use undefined for the resource metadata URL. This makes the SDK fall back to incorrectly using the MCP server URL as the authorization server URL, resulting in token requests being sent to the wrong endpoint.

To Reproduce

Steps to reproduce the behavior:

  1. Set up an MCP server that uses a separate authorization server (e.g., AWS Cognito) and returns a 401 response with a WWW-Authenticate header containing resource_metadata parameter:

    WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://example.com/.well-known/oauth-protected-resource"
    
  2. Create an OAuth client provider and attempt to connect:

    import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client';
    
    const transport = new StreamableHTTPClientTransport(
      new URL('https://bedrock-agentcore.us-east-1.amazonaws.com/'),
      { authProvider: myOAuthProvider }
    );
    
    await transport.start(); // Triggers auth flow
  3. After user authorization, call finishAuth() with the authorization code:

    await transport.finishAuth(authorizationCode);
  4. Observe the error: Token exchange fails with "Invalid api path" because the SDK constructs the wrong token endpoint URL (e.g., https://bedrock-agentcore.us-east-1.amazonaws.com/token instead of the Cognito token endpoint).

Expected behavior

The SDK should:

  1. Extract the resource_metadata URL from the WWW-Authenticate header during the initial 401 response
  2. Store this URL in _resourceMetadataUrl before starting the auth flow
  3. Use the stored _resourceMetadataUrl when finishAuth() is called
  4. Correctly discover the authorization server's OpenID configuration from the resource metadata
  5. Use the correct token endpoint (e.g., Cognito's token endpoint) for token exchange
  6. Successfully complete the OAuth flow

Logs

Error during token exchange:

Error: Invalid api path
  at token exchange
  POST https://bedrock-agentcore.us-east-1.amazonaws.com/token
  (should be: https://cognito-idp.us-east-1.amazonaws.com/oauth2/token)

Additional context

  • This bug affects both StreamableHTTPClientTransport and SSEClientTransport
  • The issue occurs because _resourceMetadataUrl is only extracted in the send() method, which is called AFTER the initial connection is established
  • The initial 401 that triggers the auth flow happens during transport.start(), but the resource metadata URL extraction was missing at this point
  • When finishAuth() is later called, _resourceMetadataUrl is still undefined, causing the SDK to fall back to using the MCP server URL as the authorization server URL (line 12247 in the SDK)
  • This affects all MCP servers using:
    • Separate authorization servers (AWS Cognito, Auth0, Okta, etc.)
    • RFC 9728 Protected Resource Metadata
    • OAuth 2.0 with resource_metadata parameter in WWW-Authenticate headers

Workarounds

Until fixed, users can:

  1. Use a proxy that handles OAuth and presents the same domain for both MCP and OAuth endpoints
  2. Configure the MCP server to use the same domain for both the MCP endpoint and OAuth endpoints
  3. Pin to SDK version 1.20.0 (though this may have other limitations)

Files Affected

  • packages/client/src/client/streamableHttp.ts - _startOrAuthSse() method
  • packages/client/src/client/sse.ts - _startOrAuth() method

Activity

  1. sstklen commented on Feb 25, 2026

    @sstklen

    Hey! I ran into a similar pattern in our bug knowledge base and thought this might help.

    What's happening: In the MCP TypeScript SDK's OAuth flow, when the initial connection attempt receives a 401 response with a WWW-Authenticate header containing resource_metadata parameter, the SDK fails to parse and persist the resourceMetadataUrl from that header. When finishAuth() is later called (after user authorization), resourceMetadataUrl is undefined, causing the SDK to fall back to using the MCP server URL itself as the authorization server URL. This means token exchange requests (authorization code → access token) are sent to the MCP server instead of the actual authorization server (e.g., AWS Cognito, Auth0, Okta), resulting in 'Invalid api path' errors from the MCP server which doesn't handle token endpoints.

    What worked for us:

    The SDK's auth flow needs to extract the resource_metadata URL from the WWW-Authenticate header during the 401 challenge response and persist it so that finishAuth() can use it to discover the correct authorization server via RFC 9728 Protected Resource Metadata. Specifically: (1) parse the resource_metadata parameter from the WWW-Authenticate header in the 401 handler, (2) store it alongside other auth state (e.g., in the transport or auth provider), and (3) pass it to finishAuth() so the token exchange request goes to the correct authorization server endpoint discovered via the resource metadata document.

    Steps:

    1. In the 401 response handler (likely in StreamableHTTPClientTransport or the auth coordination layer), parse the WWW-Authenticate header to extract the resource_metadata parameter URL using a proper RFC 9110 challenge parser
    2. Store the extracted resourceMetadataUrl in the transport's auth state so it survives across the redirect-based OAuth flow (e.g., persist it alongside PKCE verifier, state parameter, etc.)
    3. In finishAuth(), pass the persisted resourceMetadataUrl to the authorization server discovery logic so it fetches /.well-known/oauth-protected-resource from the correct URL instead of falling back to the MCP server URL
    4. Add validation: if a resource_metadata parameter was present in the WWW-Authenticate header but cannot be fetched or parsed, fail with a clear error rather than silently falling back to the wrong server
    // In the 401 response handler (e.g., extractAuthInfo or similar):
    function extractResourceMetadataUrl(response: Response): string | undefined {
      const wwwAuth = response.headers.get('WWW-Authenticate');
      if (!wwwAuth) return undefined;
      // Parse resource_metadata parameter from Bearer challenge
      const match = wwwAuth.match(/resource_metadata="([^"]+)"/);
      return match?.[1];
    }
    
    // During 401 handling, persist the URL:
    const resourceMetadataUrl = extractResourceMetadataUrl(response);
    if (resourceMetadataUrl) {
      // Store in auth state that persists across the OAuth redirect flow
      this._resourceMetadataUrl = resourceMetadataUrl;
    }
    
    // In finishAuth(), use the persisted URL:
    async finishAuth(authorizationCode: string): Promise<void> {
      // Use stored resourceMetadataUrl instead of undefined
      const serverMetadata = await discoverOAuthProtectedResourceMetadata(
        this._resourceMetadataUrl ?? this._url // fallback only if no resource_metadata was advertised
      );
      // ... proceed with token exchange using correct authorization server
    }

    📊 We found 1 similar case in our knowledge base with the same pattern — this gives us medium confidence in this analysis.

    Hope this helps! Let me know if it doesn't match your case — happy to dig deeper. 🦞

    Disclosure: This analysis is from Confucius Debug, an AI-powered community KB for agent bugs. Please verify before applying.


    🦞 Confucius Debug — community knowledge base for AI agent bugs. Free to search via MCP.

  2. ghost added
    authIssues and PRs related to Authentication / OAuth
    on Apr 16, 2026
  3. ghost added
    potentially closeBot recommends closing — needs maintainer review
    on Apr 16, 2026
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

    authIssues and PRs related to Authentication / OAuthbugSomething isn't workingpotentially closeBot recommends closing — needs maintainer review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions