fix(api): return 405 for GET/DELETE on MCP endpoint#2691
fix(api): return 405 for GET/DELETE on MCP endpoint#2691jordan-simonovski wants to merge 3 commits into
Conversation
The stateless Streamable HTTP transport routed every method through the SDK, which answered GET with a server-initiated SSE stream that the per-request teardown immediately closes, and DELETE with a non-405 status. MCP SDK clients (e.g. Claude Code) abort the connection on anything but 405, so they could not connect at all. GET and DELETE now return 405 Method Not Allowed with Allow: POST, which the spec requires for a server that offers neither a standalone SSE stream nor client-terminable sessions. POST is unchanged. Rate limiting is kept on all methods.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: b5c5ad7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🔵 Tier 2 — Low RiskSmall, isolated change with no API route or data model modifications. Why this tier:
Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns. Stats
|
Greptile SummaryThis PR updates the MCP HTTP endpoint to make unsupported methods explicit. The main changes are:
Confidence Score: 4/5This is close, but the method discovery behavior should be fixed before merging.
packages/api/src/mcp/app.ts Important Files Changed
Reviews (3): Last reviewed commit: "fix(api): leave MCP OPTIONS to the globa..." | Re-trigger Greptile |
Express's automatic OPTIONS handler builds its Allow header from every registered route, which advertised GET and DELETE as usable even though both return 405. Route OPTIONS through the same handler so the endpoint honestly reports POST as its only supported method. The endpoint sets no CORS headers, so it was never a working preflight target.
E2E Test Results✅ All tests passed • 240 passed • 1 skipped • 814s
Tests ran across 4 shards in parallel. |
Deep Review✅ No critical issues found. The change is correct: GET/DELETE on the stateless MCP transport now return 🟡 P2 -- recommended
🔵 P3 nitpicks (2)
Reviewers (6): correctness, security, testing, maintainability, reliability, api-contract. Testing gaps:
Note: |
The previous commit's per-route OPTIONS handler was dead code: the global CORS middleware (api-app.ts) short-circuits every OPTIONS preflight with a 204 before the /mcp router runs, so the handler never executed in production and only 'worked' in the isolated unit test. It was also semantically wrong — returning 405 to a CORS preflight would break browser preflight. Revert it and document why OPTIONS is left to the CORS layer. GET/DELETE are unaffected: CORS only short-circuits OPTIONS, so they still reach the 405 handler.
Fixes #2686.
resolves HDX-4843
The MCP Streamable HTTP endpoint (
/api/mcp) rejects spec-compliant clients: GET and DELETE return statuses that make official MCP SDK clients (e.g. Claude Code) abort the connection, so the server is unusable with them even though POST-basedinitialize/tools/list/tools/callall work. This makes GET and DELETE return405 Method Not Allowed, which those clients treat as "not offered, continue".What changed
GET /api/mcpandDELETE /api/mcpnow return405with anAllow: POSTheader.POSThandling is unchanged; rate limiting still applies to every method.Background
The transport is stateless — a fresh MCP server/transport is created per POST and torn down immediately after. Such a server offers neither a server-initiated SSE stream (GET) nor client-terminable sessions (DELETE), and the Streamable HTTP spec requires
405in exactly that case. Previouslyapp.all('/')routed every method through the SDK, which opened a doomed SSE stream on GET (closed instantly by the per-request teardown) and answered DELETE with a non-405 status — both of which SDK clients treat as a failed connection.The issue also reports a third symptom:
notifications/initializedsent without aparamsmember returning400. That was fixed by the MCP SDK itself — the version this repo now depends on treatsparamsas optional in the notification schema, so no code change is needed here. It was only reproducible on the older SDK shipped in 2.31.0.Impact
Spec-compliant MCP clients can now complete the handshake and connect without a reverse-proxy shim. No change for existing POST-based usage.
Implementation detail
methodNotAllowedhandler is registered forGETandDELETE;POSTkeeps the full transport pipeline.packages/api/src/mcp/__tests__/app.test.ts— supertest checks asserting405+Allow: POSTfor both methods.