Skip to content

fix: route proxy OAuth flow through proxy fetch to bypass CORS#1638

Open
c-bik wants to merge 1 commit into
modelcontextprotocol:mainfrom
c-bik:fix/proxy-oauth-cors-token-exchange
Open

fix: route proxy OAuth flow through proxy fetch to bypass CORS#1638
c-bik wants to merge 1 commit into
modelcontextprotocol:mainfrom
c-bik:fix/proxy-oauth-cors-token-exchange

Conversation

@c-bik

@c-bik c-bik commented Jul 10, 2026

Copy link
Copy Markdown

Summary

When using Via Proxy connection mode, the browser-side token exchange POST to the OAuth token endpoint is CORS-blocked by corporate MCP gateways (and any auth server that doesn't return Access-Control-Allow-Origin on POST responses). This causes TypeError: Failed to fetch after the OAuth redirect completes, leaving the inspector unable to connect.

This PR fixes three related failure modes in the proxy OAuth flow. It is related to #1342 (which fixes the same OAuthCallback CORS issue) but additionally addresses two further bugs that appear after the token exchange succeeds.

Note: Inspector V2 is under development to address architectural and UX improvements. During this time, V1 contributions should focus on bug fixes and MCP spec compliance. See CONTRIBUTING.md for more details.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Refactoring (no functional changes)
  • Test updates
  • Build/CI improvements

Changes Made

  1. OAuthCallback.tsx: accept config and connectionType props; pass createProxyFetch(config) as fetchFn to auth() when connectionType === "proxy", routing the token exchange through the Node.js proxy instead of the browser. This is the same pattern already used in useConnection.handleAuthError and AuthDebugger.

  2. useConnection.ts - React state race fix: onOAuthConnect in App.tsx calls setSseUrl(serverUrl) immediately followed by connectMcpServer(). Because setSseUrl is async, connectMcpServer still sees the stale sseUrl and looks up the OAuth token under the wrong sessionStorage key, finding nothing. Fixed by adding an optional serverUrlOverride parameter to connect() and passing the URL directly from onOAuthConnect.

  3. useConnection.ts - spurious redirect fix: In proxy mode, authProvider: serverAuthProvider was passed to StreamableHTTPClientTransport and SSEClientTransport. When the transport receives a 401, the SDK calls auth() with serverUrl set to the proxy URL (localhost:6277/mcp), not the real MCP server. This causes OAuth discovery against the proxy, which returns 404, and the SDK then redirects the browser to localhost:6277/authorize. Removed authProvider from proxy transport options -- the Bearer token is already injected manually into headers before the connection is made.

  4. App.tsx: thread connectionType and config down to OAuthCallback; pass serverUrl explicitly to connectMcpServer after the OAuth callback.

Related Issues

Testing

  • Tested in UI mode
  • Tested in CLI mode
  • Tested with STDIO transport
  • Tested with SSE transport
  • Tested with Streamable HTTP transport
  • Added/updated automated tests
  • Manual testing performed

Test Results and/or Instructions

Validated end-to-end against a real corporate MCP gateway backed by Azure AD, which enforces strict CORS on the token endpoint.

Without this fix:

  1. Click Connect -> 401 -> OAuth browser redirect opens
  2. User logs in -> redirected back to /oauth/callback
  3. Token exchange fails with TypeError: Failed to fetch (CORS-blocked)
  4. Even if exchange somehow succeeds (e.g. token was cached), auto-connect after callback silently fails because connectMcpServer reads the wrong sessionStorage key
  5. Manual retry triggers bogus redirect to localhost:6277/authorize

With this fix:

  1. Click Connect -> OAuth redirect opens
  2. User logs in -> token exchange completes via Node.js proxy (no CORS)
  3. Inspector auto-connects immediately using the stored token

npm test -> 535/535 pass
npm run test:e2e -> 24/24 pass
npm run prettier-check -> passes on all changed files

Checklist

  • Code follows the style guidelines (ran npm run prettier-fix)
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (README, comments, etc.)

Breaking Changes

None. OAuthCallback gains two optional props; the single call site in App.tsx is updated. The connect() signature change is additive (optional third parameter).

Additional Context

The three bugs form a cascade: fix (1) alone and you hit (2); fix (1) and (2) and you hit (3). All three need to be addressed together for the proxy OAuth flow to work reliably against real-world auth servers with strict CORS policies.

When using Via Proxy connection mode, the browser-side token exchange
POST to the OAuth token endpoint is CORS-blocked by corporate MCP
gateways. Fix by:

1. OAuthCallback: pass createProxyFetch(config) as fetchFn to auth()
   when connectionType is "proxy", so the token exchange goes through
   the Node.js proxy instead of the browser.

2. useConnection: accept optional serverUrlOverride in connect() and
   use it for InspectorOAuthClientProvider, avoiding a React state
   timing race where sseUrl hasn't updated yet when connectMcpServer()
   is called from onOAuthConnect.

3. useConnection: remove authProvider from proxy transport options for
   SSE and Streamable HTTP. The token is injected manually via headers;
   having authProvider on a proxy-URL transport causes the SDK to do
   OAuth discovery against localhost:6277 on 401, triggering a bogus
   redirect to /authorize on the proxy.

4. App.tsx: pass connectionType and config to OAuthCallback, and
   forward serverUrl to connectMcpServer after OAuth callback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant