Cursor MCP connector cannot load a remote MCP server (fails with and without OAuth)

Where does the bug appear (feature/product)?

Cloud Agent (GitHub, Slack, Web, Linear)

Describe the Bug

Reported by: Daniel Siva · Date: 2026-08-12

Server: self-hosted gbrain v0.43.0.0 (https://brain.siva.nz/mcp), Cloudflare Tunnel → Railway

Cursor surface: account-level MCP installer (cloud agents / “Inbox Triage” bot)

Summary

Cursor’s MCP connector cannot load a remote MCP server that four other MCP clients use
successfully
(claude.ai/Cowork, Claude Code, Perplexity, Grok Chat/Build) — and it fails both
with OAuth and with a plain bearer token.

The failure is entirely client-side: across ~10 attempts on three different hostnames, zero HTTP
requests from Cursor reached the origin
— no rows in oauth_clients, no rows in oauth_codes,
no origin log entries. Every leg of the flow succeeds when performed by hand with curl from
inside the Cursor agent’s own sandbox VM
, so neither network path nor server behaviour explains
it.

It initially presents as an OAuth/DCR problem, but see “Decisive test” below: with OAuth discovery
blocked and the challenge header stripped, and a valid bearer token supplied through connector
headers, the connector still reports Failed to load MCP server with 0 tools. The OAuth
symptoms appear to be downstream of a load failure, not the cause.

Observed errors

  1. Cursor's OAuth check is failing with: does not support dynamic client registration
    — even though registration_endpoint was advertised and POST /register returned 201 for
    an identical registration payload sent by hand from the same VM.

  2. With a bearer token supplied via AddMcpServer headers: the header is ignored“It sees
    the OAuth metadata on that URL and forces the connect-card path”
    — status stays needsAuth.

  3. On a second, never-before-seen hostname: Failed to load MCP server, and kicking auth returns
    Internal Server Error.

Server surface (verified live)

GET /.well-known/oauth-authorization-server → 200:

{"issuer":"https://brain.siva.nz/","authorization_endpoint":"https://brain.siva.nz/authorize",
 "response_types_supported":["code"],"code_challenge_methods_supported":["S256"],
 "token_endpoint":"https://brain.siva.nz/token",
 "token_endpoint_auth_methods_supported":["client_secret_post","none","client_secret_basic"],
 "grant_types_supported":["authorization_code","refresh_token","client_credentials"],
 "scopes_supported":["admin","agent","read","sources_admin","users_admin","write"],
 "revocation_endpoint":"https://brain.siva.nz/revoke",
 "registration_endpoint":"https://brain.siva.nz/register"}

GET /.well-known/oauth-protected-resource → 200:

{"resource":"https://brain.siva.nz/","authorization_servers":["https://brain.siva.nz/"],
 "scopes_supported":["admin","agent","read","sources_admin","users_admin","write"],
 "resource_name":"GBrain MCP Server"}

POST /mcp unauthenticated → 401 with the correct challenge:

www-authenticate: Bearer error="invalid_token", error_description="Missing Authorization header",
  resource_metadata="https://brain.siva.nz/.well-known/oauth-protected-resource"

Proof the server works: full handshake by hand, from Cursor’s own VM

Run in the Cursor agent’s sandbox (box@cursor:/workspace), against the same URL the connector
was given:

# 1. Dynamic client registration
curl -s -X POST https://brain.siva.nz/register -H 'Content-Type: application/json' \
  -d '{"client_name":"vm-handshake-test","redirect_uris":["http://localhost:8787/callback"],
       "grant_types":["authorization_code","refresh_token"],"response_types":["code"],
       "token_endpoint_auth_method":"none"}'
# -> 201, client_id returned

# 2. Authorization endpoint (PKCE S256)
curl -s -o /dev/null -D - "https://brain.siva.nz/authorize?response_type=code&client_id=$CID\
&redirect_uri=http%3A%2F%2Flocalhost%3A8787%2Fcallback&code_challenge=$C\
&code_challenge_method=S256&scope=read"
# -> HTTP/2 302
#    location: http://localhost:8787/callback?code=gbrain_code_1af12ad8...

# 3. Token exchange
curl -s -X POST https://brain.siva.nz/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d "grant_type=authorization_code&code=...&redirect_uri=...&client_id=$CID&code_verifier=$V"
# -> {"token_type":"bearer","access_token":"gbrain_at_...","refresh_token":"..."}

# 4. Authenticated MCP call
curl -s -X POST https://brain.siva.nz/mcp -H "Authorization: Bearer $TOK" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# -> 200, full tool list

Every leg succeeds. initialize and tools/list also succeed. The agent now uses the brain
productively through a local HTTP client with this token — only Cursor’s connector cannot.

Eliminated (so you don’t have to)

Hypothesis How it was ruled out
Edge/WAF blocking Cursor Cloudflare Browser Integrity Check was banning some agents (error 1010, e.g. Python-urllib/3.11). A skip rule fixed it, verified: previously-banned agents now get 200. Failure unchanged.
DCR disabled or flapping Verified live and stable before each attempt: registration_endpoint advertised and POST /register → 400 (not 404), checked 3× at 60s intervals, with no deploys during the attempt.
Stale local connector state Two complete removes + re-adds, cache cleared, fresh user-gbrain entry each time.
Cursor caching discovery per host Second hostname (brain2.siva.nz) never previously contacted by Cursor, self-consistent discovery (issuer/authorize/token/registration all on brain2), DCR live, no edge ban → Failed to load MCP server + Internal Server Error.
Trailing-slash issuer issuer is https://brain.siva.nz/; a minor RFC 8414 §2 deviation, but the MCP SDK performs no issuer comparison, and four other clients accept it.
Path-suffixed metadata (/.well-known/...-/mcp) Returns 404, but clients are directed by the resource_metadata parameter in the 401 challenge, which points at the served path.

Decisive test: it is not an OAuth bug

After the above, we removed OAuth from the equation entirely. On a third hostname
(grok-bot-brain.siva.nz, same origin, same server), we:

  • blocked /.well-known/oauth-* (returns 403 — no discoverable OAuth metadata), and
  • stripped the www-authenticate response header (no resource_metadata pointer),

so Cursor had no OAuth path to find and nothing to trigger the connect-card flow. We then added the
account-level connector with a valid, pre-issued bearer token in the Authorization header.

Result: Failed to load MCP server, tools = 0. The same token, against the same URL, from the
Cursor agent’s own VM, succeeds — initialize plus 100 tools listed.

This means the fault is not in the OAuth/DCR path. Cursor’s installer fails to load this MCP
server irrespective of the authentication mechanism, and reports an OAuth-flavoured error
(does not support dynamic client registration) only when OAuth metadata happens to be present.

A likely direction for whoever picks this up: the server responds to POST /mcp with SSE
framing (Accept: application/json, text/event-stream; body event: message\ndata: {...}), which
the agent’s own HTTP client handles correctly. If Cursor’s installer requires a different transport
shape — plain JSON responses, or a GET /mcp SSE stream — that would explain a load failure that
is entirely independent of authentication. We have not been able to confirm this from outside.

What we believe is happening (OAuth path only)

@modelcontextprotocol/sdk client/auth.jsregisterClient():

if (metadata && !metadata.registration_endpoint)
  throw new Error('Incompatible auth server: does not support dynamic client registration')

This throws before any HTTP request, which matches the zero-origin-traffic observation. But it
does not explain the failure when registration_endpoint is present and the route answers —
which is the state we verified repeatedly. Something in Cursor’s wrapper appears to retain or
mis-evaluate discovery state, and on a fresh host it degrades to Internal Server Error.

Requests

  1. The headline: with OAuth discovery blocked and the challenge header stripped, and a valid
    bearer token supplied via connector headers, the connector still reports Failed to load MCP server with 0 tools — while the identical token and URL work from the agent’s own VM. Please
    start there; the OAuth symptoms below appear to be downstream of a load failure.

  2. Surface the real error. does not support dynamic client registration was emitted while
    registration was demonstrably available; Internal Server Error on a virgin host tells the user
    nothing. Please log the discovery URL fetched, the HTTP status, and the parsed metadata.

  3. Honour bearer headers. When AddMcpServer is given an Authorization header, use it rather
    than forcing the connect-card path because OAuth metadata exists. Static-token auth is a
    legitimate configuration and is currently unreachable for any OAuth-advertising server.

  4. Provide a static client_id escape hatch for servers where DCR is deliberately closed (open
    DCR is a security exposure — see below).

  5. Document how to clear cached discovery state for a connector beyond remove/re-add.

Security note, for context on why DCR stays closed

gbrain’s /authorize has no human-login gate (advisory GHSA-42r8-3jfh-82vr, unfixed upstream), so
leaving Dynamic Client Registration open lets any network caller self-register. During one of these
test windows an unrelated client registered. This is why a static-client-id path matters: forcing
DCR forces server operators to keep an anonymous registration endpoint open.

Environment

  • Cursor: account-level MCP installer, cloud agent sandbox (box@cursor), egress 104.30.175.37
  • Server: gbrain v0.43.0.0, Node/Express behind Cloudflare Tunnel (Railway, Singapore)
  • Working clients against the identical endpoint: claude.ai/Cowork, Claude Code, Perplexity,
    Grok Chat, Grok Build

Steps to Reproduce

  1. Add a remote MCP server (https://brain.siva.nz/mcp) via the account-level MCP installer / AddMcpServer.
  2. Cursor’s connector attempts OAuth discovery + dynamic client registration; it errors with “does not support dynamic client registration” even though registration_endpoint is advertised and POST /register returns 201 from the same VM.
  3. Retry with a bearer token in headers - the header is ignored and status stays needsAuth.
  4. Retry on a never-before-seen hostname (brain2.siva.nz) - “Failed to load MCP server” and “Internal Server Error”.
  5. DECISIVE TEST: on a third hostname (grok-bot-brain.siva.nz, same server) block /.well-known/oauth-* (403) and strip the www-authenticate header, then add the connector with a valid pre-issued bearer token. Result: “Failed to load MCP server”, tools = 0 - no OAuth path involved.

Expected Behavior

Cursor’s MCP connector should load the remote MCP server and list its tools, both via OAuth and with a static bearer token, and should surface the real discovery URL / HTTP status on failure.

Operating System

Linux

Version Information

Cursor account-level MCP installer / cloud agent sandbox (box@cursor). Server: gbrain v0.43.0.0.

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey there,
One detail unlocks most of this: Cursor’s connector runs OAuth discovery, registration, and connect from our backend, not from the agent’s sandbox VM. So your curl tests from box@cursor and the Cloudflare skip rule (scoped to the VM’s agents) validated a different path than the connector uses.

From our side:

  • brain.siva.nz / brain2.siva.nz: discovery returned 200, then POST /register returned 500 from your server. That 500 is the “Failed to load” / “Internal Server Error” you saw. Our registration sends 3 redirect URIs (including a cursor:// scheme); your one-URI by-hand test returned 201, so that’s the variable to check.
  • grok-bot-brain.siva.nz: your Authorization header was sent on every request (including POST /mcp) and your server 401’d it; discovery returned 403 to our backend (the edge block hit us too). So it wasn’t ignored — the token was rejected and discovery was blocked.

Fastest unblock: use a static OAuth client instead of dynamic registration. That skips /register entirely (no 500) and only needs these two callbacks allowlisted (no cursor://), and lets you keep DCR closed:

https://www.cursor.com/agents/mcp/oauth/callback
http://localhost:8787/callback

Docs: Static OAuth for remote servers.

On your asks - surfacing the real error, and honoring a static client on the account-level path - you’re right, and both are issues we’re tracking. If the static-client route still won’t connect on the account-level installer, reply here and we’ll dig into that path.