modelcontextprotocol / modelcontextprotocol/typescript-sdk

OAuth Token Exchange Fails with Separate Authorization Servers

Open
#1,450 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auth bug potentially close
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with _startOrAuthSse() in packages/client/src/client/streamableHttp.ts and _startOrAuth() in packages/client/src/client/sse.ts, then trace how the initial 401 and WWW-Authenticate header are handled before finishAuth(). Reproduce the separate-authorization-server flow with a resource_metadata value and verify that both transports retain it and exchange the code at the discovered authorization server token endpoint.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.