MCP OAuth callback loses authorization server URL discovered from resource_metadata, causing token exchange failure

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

When connecting to an MCP server that uses OAuth 2.0 with a separate authorization server, the OAuth authorization code exchange fails after the user completes browser authorization. The root cause is that the resource_metadata URL extracted from the initial 401 WWW-Authenticate header is not persisted across the OAuth redirect flow.

Environment

  • Cursor version: 2.4.31
  • Transport: Streamable HTTP
  • MCP server setup: Resource server and authorization server are on different hosts; resource metadata is served at a non-standard path indicated by the WWW-Authenticate header

Background: How Resource Metadata Discovery Works

When discovering OAuth Protected Resource Metadata, the client uses the following fallback chain:

  1. If a resource_metadata URL is explicitly provided (e.g., from the WWW-Authenticate header): fetch that URL directly.
  2. If no explicit URL is provided, build well-known URLs based on the MCP server URL. For a server at https://mcp.example.com/v1/mcp:
    • Try https://mcp.example.com/.well-known/oauth-protected-resource/v1/mcp
    • If 404 and path is not /, fall back to https://mcp.example.com/.well-known/oauth-protected-resource
  3. If all attempts fail or return 404, the discovery throws, and the authorization server URL falls back to the MCP server’s root origin (e.g., https://mcp.example.com/).

Steps to Reproduce

Configure an MCP server at https://mcp.example.com/v1/mcp with the following setup:

  • The server returns 401 with:
    WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/v1/.well-known/oauth-protected-resource"
    
  • The resource metadata at that URL declares:
    {
      "resource": "https://mcp.example.com/v1/mcp",
      "authorization_servers": ["https://auth.example.com"]
    }
    
  • The standard well-known paths do NOT serve valid resource metadata:
    • https://mcp.example.com/.well-known/oauth-protected-resource/v1/mcp → 404
    • https://mcp.example.com/.well-known/oauth-protected-resource → 404 (or returns metadata for a different resource, or is not available)

Flow:

  1. Cursor connects to the MCP server, receives 401 with the resource_metadata URL.
  2. Cursor fetches https://mcp.example.com/v1/.well-known/oauth-protected-resource → discovers authorization_servers: ["https://auth.example.com"] → correctly redirects the user to https://auth.example.com for authorization.
  3. User completes authorization and is redirected back to Cursor via the cursor:// callback URI.
  4. Cursor handles the callback and attempts to exchange the authorization code. During this step, it needs to re-discover the authorization server, but:
    • The resource_metadata URL from the original 401 header is no longer available.
    • Standard well-known discovery falls back to:
      • https://mcp.example.com/.well-known/oauth-protected-resource/v1/mcp → 404
      • https://mcp.example.com/.well-known/oauth-protected-resource → 404
    • Discovery fails → authorization server falls back to https://mcp.example.com/ instead of https://auth.example.com.
  5. The token exchange request is sent to the wrong server → fails.

Expected Behavior

The authorization code exchange should use https://auth.example.com (the same authorization server used in step 2) to obtain tokens.

Actual Behavior

The authorization code exchange targets https://mcp.example.com/ (the fallback) because the resource_metadata URL from the initial WWW-Authenticate header was lost between the redirect and the callback.

Root Cause Analysis

During the initial connection, the transport layer extracts the resource_metadata URL from the 401 response and passes it to the auth flow. However, this URL is only held in memory. When the user is redirected to the browser for authorization, only mcp_server_url and mcp_code_verifier are persisted. The resource_metadata URL is not.

When the callback URI handler later invokes the auth flow to exchange the authorization code, it cannot provide the resource_metadata URL, so the resource metadata discovery must rely on the standard well-known fallback chain — which may not succeed for servers that only advertise their resource metadata via the WWW-Authenticate header.

Suggested Fix

Store the authorization_server_url from the resource_metadata URL—along with existing values like mcp_server_url, mcp_code_verifier, and mcp_client_information—when initiating the OAuth redirect, and return it to the authentication flow when handling the callback.

Operating System

MacOS

Version Information

Version: 2.4.31
VSCode Version: 1.105.1
Commit: 3578107fdf149b00059ddad37048220e41681000
Date: 2026-02-08T07:42:24.999Z
Build Type: Stable
Release Track: Default
Electron: 39.2.7
Chromium: 142.0.7444.235
Node.js: 22.21.1
V8: 14.2.231.21-electron.0
OS: Darwin arm64 25.2.0

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey, thanks for the report.

Got it. The issue is that the resource_metadata URL from the WWW-Authenticate header isn’t saved between the OAuth redirect and the callback. Then, when it falls back to re-discovery on well-known paths, it can’t find the right authorization server. As a result, the token exchange goes to the wrong place.

I’ve passed this to the team.