MCP OAuth fails with "Protected resource … does not match expected"

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

trying to connect to Userback / mcp, both Cursor itself and Claude code reports a bug in Cursor.

===========

Title: MCP OAuth fails with “Protected resource … does not match expected” — trailing-slash mismatch (Cursor 3.9.16)

Environment: Cursor 3.9.16, macOS. Remote MCP server: Userback (https://mcp.userback.io/v1/mcp/), streamable HTTP + OAuth.

Summary
OAuth fails at the token-exchange/callback step because Cursor compares the server’s Protected Resource Metadata resource (which ends in a trailing slash) against its own “expected” value (no trailing slash) using exact string equality, without normalizing either side. This is the same bug class VS Code fixed in microsoft/vscode #255415 (normalizeUrlForComparison).

Error

Protected resource https://mcp.userback.io/v1/mcp/ does not match expected https://mcp.userback.io/v1/mcp (or origin)

The server is RFC 9728-compliant (verified)
GET https://mcp.userback.io/.well-known/oauth-protected-resource returns:

{ "resource": "https://mcp.userback.io/v1/mcp/",
  "authorization_servers": ["https://app.userback.io"] }

The trailing slash is intentional and matches Userback’s own setup docs, which specify the endpoint as https://mcp.userback.io/v1/mcp/. So the client must handle it — the resource should be preserved/compared per RFC 8707/9728, not exact-matched after inconsistent normalization.

Key detail — config doesn’t help
Setting the mcp.json url both with and without the trailing slash produces the identical error; Cursor’s “expected” is always the no-slash form. So the comparison isn’t derived from (or normalized consistently with) the configured URL.

Repro

  1. Add a remote MCP server whose PRM resource ends in / (e.g. Userback’s https://mcp.userback.io/v1/mcp/).
  2. Trigger the OAuth flow.
  3. Auth fails at the callback/token exchange with the mismatch error. Tried both slash and no-slash in config — same result.

Logs (trimmed; no secrets in these lines)

[Shared MCP process] Transient error connecting to streamableHttp server: Protected resource https://mcp.userback.io/v1/mcp/ does not match expected https://mcp.userback.io/v1/mcp (or origin)
[Shared MCP process] MCP OAuth redirect
[Shared MCP process] OAuth callback exchange failed for user-userback: Protected resource https://mcp.userback.io/v1/mcp/ does not match expected https://mcp.userback.io/v1/mcp (or origin)
[MCPService] createClient completed for server: user-userback, connected=false, statusType=error, error=OAuth token exchange failed: Protected resource https://mcp.userback.io/v1/mcp/ does not match expected https://mcp.userback.io/v1/mcp (or origin)

Expected
OAuth should succeed when the server’s PRM resource differs from the resolved MCP URL only by a trailing slash. Normalize both sides consistently before comparison (per RFC 8707/9728), or preserve the resource string verbatim — as VS Code did in #255415.

Workaround for others hitting this
Bridge through npx mcp-remote <url> as a stdio server; it performs the OAuth itself and isn’t affected by Cursor’s comparison.

Steps to Reproduce

I’ve tried a bunch of workarounds but it will not connect. Can provide a output if needed.

Expected Behavior

I need to pull bug reports etc from Userback directly into Cursor using the mcp.

Operating System

MacOS

Version Information

Cursor 3.9.16

For AI issues: which model did you use?

Both Opus High and Sonnet High, when trying Claude code to help, it was on Opus Max (wrote the bug report)

Does this stop you from using Cursor

No - Cursor works, but with this issue