Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

fix(server): route all MCP-endpoint methods through the stateless handler (Cursor 404) - #48

Draft
mikemilla wants to merge 1 commit into
mainfrom
fix/expose-tools-sync-docs-cursor-oauth
Draft

fix(server): route all MCP-endpoint methods through the stateless handler (Cursor 404)#48
mikemilla wants to merge 1 commit into
mainfrom
fix/expose-tools-sync-docs-cursor-oauth

Conversation

@mikemilla

Copy link
Copy Markdown
Collaborator

Problem

Cursor (and other MCP clients) fail to connect to the hosted server with:

Error connecting to SSE server after fallback: SSE error: Non-200 status code (404)
Connection failed: SSE error: Non-200 status code (404)

The client opens an SSE GET on the MCP endpoint and receives a 404, which it treats as fatal.

Root cause

Per the MCP Streamable HTTP transport spec, the MCP endpoint must answer a GET (the client's optional server→client SSE stream) with either text/event-stream or 405 Method Not Allowed. A stateless server never pushes on that stream, so 405 is correct. Any other non-2xx — notably Express's default 404 when no GET route is registered — is fatal to the client.

This dev server mounted statelessHandler only on POST / and hand-rolled separate GET / and DELETE / routes to emit those 405s. That parallel-route pattern is fragile — the exact thing that can be dropped and leave GET / falling through to a 404.

Fix

statelessHandler already returns a spec-compliant 405 for any non-POST method. Route POST / GET / DELETE on / through the one handler instead of maintaining parallel 405 routes, so the behavior is owned by the handler and can't be dropped by accident.

Also bump the local dev port default 30003939 (+ README) to avoid colliding with other local dev servers.

Verification

Local server (:3939):

Method Result
POST / initialize 200
GET / (SSE open) 405 + JSON-RPC error body
DELETE / 405
OPTIONS / 200, Allow: POST,GET,HEAD,DELETE (CORS preflight preserved)

Scope / follow-ups

  • This is the local dev server (server/). It does not serve mcp.courier.com; the hosted server lives in trycourier/services (apps/mcp/service/http-api/src/app.ts).
  • The production fix for this same issue already exists in that services app but is uncommitted on staging / not yet deployed — that deploy is what actually fixes users. This PR keeps the repo's reference/dev server correct and in sync.
  • The services app also fixes a second Cursor failure mode not covered here: OAuth//register discovery probes returning an HTML-bodied 404 that Cursor's client crashes parsing as JSON. Porting that handler into server/ is a possible follow-up.
  • No @trycourier/courier-mcp package change is needed — this is entirely the HTTP/Express layer.

🤖 Generated with Claude Code

…dler

The local dev server mounted `statelessHandler` only on `POST /` and
hand-rolled separate `GET /` and `DELETE /` routes to emit the 405s the
MCP Streamable HTTP spec requires. That parallel-route pattern is exactly
what got dropped in a prior deploy: with no `GET /` route, Express answers
its default 404, which MCP clients (Cursor, the reference SDK) treat as a
fatal "Failed to open SSE stream".

`statelessHandler` already returns a spec-compliant 405 for any non-POST
method, so route POST/GET/DELETE through the one handler instead. The 405
behavior is now owned by the handler and can't be dropped by accident.

Also move the local dev port default 3000 -> 3939 (and update the README)
to avoid colliding with other local dev servers.

Spec: https://modelcontextprotocol.io/specification/2025-06-18/basic/transports

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant