fix: resolve MCP HTTP hanging on Claude Code Cloud#51
Merged
Conversation
Three issues caused the MCP server to hang with Claude Code Cloud: 1. GET /mcp SSE stream never closed: The ReadableStream sent `: connected` but never called controller.close(), creating a zombie connection that blocked the client's connection pool indefinitely. Now returns 405 (correct for stateless servers per MCP spec and official SDK pattern). 2. POST /mcp always returned SSE: Because Accept includes text/event-stream, even initialize and tools/list were wrapped in SSE format. Now prefers JSON when client accepts it, only falls back to SSE when client exclusively requests text/event-stream. 3. Removed invalid Connection: keep-alive header (prohibited in HTTP/2). https://claude.ai/code/session_013qxyMP1Eci9SqmErxff6E9
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
oddkit | b7e4dfe | Commit Preview URL Branch Preview URL |
Feb 19 2026, 01:38 PM |
The actual root cause of the hanging: the GET /mcp ReadableStream never called controller.close(), creating a zombie connection. Fixed by adding controller.close() after the : connected comment. Restores SSE response format when Accept includes text/event-stream (required by test suite and MCP spec). Removes invalid Connection: keep-alive header (prohibited in HTTP/2). https://claude.ai/code/session_013qxyMP1Eci9SqmErxff6E9
This comment has been minimized.
This comment has been minimized.
…on/json MCP spec-compliant clients send Accept: application/json, text/event-stream. The previous condition (wantsSSE = acceptHeader includes text/event-stream) was always true for these clients, making the JSON response path unreachable. Now the POST handler only uses SSE when the client exclusively requests text/event-stream. When the client also accepts application/json, the server prefers JSON, matching the PR description intent and preventing hangs with Claude Code Cloud.
This comment has been minimized.
This comment has been minimized.
…er requirement Previously, GET requests with Accept: text/event-stream received a 200 SSE stream that immediately closed. Per the MCP 2025-03-26 spec, servers that do not support GET MUST return 405. The immediately-closing stream could trigger auto-reconnection loops in SSE clients. Also fixes the error message which previously suggested using GET with SSE, contradicting the stateless server design.
This comment has been minimized.
This comment has been minimized.
Reverts the regressions from 58a2d5f and eedb071 which broke 4 tests: - Test 4c: GET /mcp with Accept: text/event-stream must return SSE, not 405. The 405 should only apply when SSE is NOT requested. - Tests 4f/4g/4h: POST /mcp with Accept: application/json, text/event-stream must return SSE. The MCP spec says SSE takes priority when both are accepted. The `!includes("application/json")` guard was wrong. The root cause of the original hanging bug remains fixed: controller.close() is called in the GET SSE ReadableStream. https://claude.ai/code/session_013qxyMP1Eci9SqmErxff6E9
Comments explain the SSE contract, reference specific test numbers
(4c, 4d, 4f, 4g, 4h), and warn against two specific anti-patterns
that have already caused regressions twice:
1. DO NOT return 405 for ALL GETs — only when Accept lacks
text/event-stream (test 4c vs 4d)
2. DO NOT add `&& !includes("application/json")` to the SSE
condition — MCP clients send both in Accept (tests 4f, 4g, 4h)
3. DO NOT remove controller.close() — that's the root cause of
the original hanging bug
https://claude.ai/code/session_013qxyMP1Eci9SqmErxff6E9
The JSON-RPC 2.0 spec requires id: null when the request id cannot be determined. Added the missing field to the new 405 GET handler and the pre-existing parse error catch handler.
|
Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three issues caused the MCP server to hang with Claude Code Cloud:
GET /mcp SSE stream never closed: The ReadableStream sent
: connectedbut never called controller.close(), creating a zombie connection that
blocked the client's connection pool indefinitely. Now returns 405
(correct for stateless servers per MCP spec and official SDK pattern).
POST /mcp always returned SSE: Because Accept includes text/event-stream,
even initialize and tools/list were wrapped in SSE format. Now prefers
JSON when client accepts it, only falls back to SSE when client
exclusively requests text/event-stream.
Removed invalid Connection: keep-alive header (prohibited in HTTP/2).
https://claude.ai/code/session_013qxyMP1Eci9SqmErxff6E9
Note
Medium Risk
Changes MCP HTTP response negotiation and streaming behavior; incorrect
Accepthandling or SSE framing could break compatibility with MCP clients, though the change is localized to the/mcpendpoint.Overview
Fixes the Cloudflare Worker MCP endpoint to follow the SSE/JSON contract and avoid client hangs by ensuring the
GET /mcpSSEReadableStreamimmediatelycontroller.close()s after sending the initial comment.Updates content negotiation so
text/event-streaminAccepttakes priority (even whenapplication/jsonis also present), adjustsGET /mcpwithout SSEAcceptto return a JSON-RPC405error with proper headers, removes theConnection: keep-aliveheader, and standardizes parse-error responses to includeid: null.Bumps versions to
0.14.2inpackage.json,workers/package.json, andworkers/package-lock.json.Written by Cursor Bugbot for commit b7e4dfe. This will update automatically on new commits. Configure here.