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
-
Cursor's OAuth check is failing with: does not support dynamic client registration
— even thoughregistration_endpointwas advertised andPOST /registerreturned 201 for
an identical registration payload sent by hand from the same VM. -
With a bearer token supplied via
AddMcpServerheaders: the header is ignored — “It sees
the OAuth metadata on that URL and forces the connect-card path” — status staysneedsAuth. -
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-authenticateresponse header (noresource_metadatapointer),
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.js → registerClient():
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
-
The headline: with OAuth discovery blocked and the challenge header stripped, and a valid
bearer token supplied via connector headers, the connector still reportsFailed to load MCP serverwith 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. -
Surface the real error.
does not support dynamic client registrationwas emitted while
registration was demonstrably available;Internal Server Erroron a virgin host tells the user
nothing. Please log the discovery URL fetched, the HTTP status, and the parsed metadata. -
Honour bearer headers. When
AddMcpServeris given anAuthorizationheader, 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. -
Provide a static
client_idescape hatch for servers where DCR is deliberately closed (open
DCR is a security exposure — see below). -
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), egress104.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
- Add a remote MCP server (
https://brain.siva.nz/mcp) via the account-level MCP installer / AddMcpServer. - Cursor’s connector attempts OAuth discovery + dynamic client registration; it errors with “does not support dynamic client registration” even though
registration_endpointis advertised andPOST /registerreturns 201 from the same VM. - Retry with a bearer token in headers - the header is ignored and status stays
needsAuth. - Retry on a never-before-seen hostname (
brain2.siva.nz) - “Failed to load MCP server” and “Internal Server Error”. - DECISIVE TEST: on a third hostname (
grok-bot-brain.siva.nz, same server) block/.well-known/oauth-*(403) and strip thewww-authenticateheader, 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