Skip to content

fix: handle null body status in createCustomFetch proxy#1180

Open
evan-choi wants to merge 1 commit intomodelcontextprotocol:mainfrom
evan-choi:fix/delete-204-null-body-status
Open

fix: handle null body status in createCustomFetch proxy#1180
evan-choi wants to merge 1 commit intomodelcontextprotocol:mainfrom
evan-choi:fix/delete-204-null-body-status

Conversation

@evan-choi
Copy link
Copy Markdown

@evan-choi evan-choi commented Apr 6, 2026

Summary

  • Fix TypeError: Response constructor: Invalid response status code 204 when disconnecting from MCP servers that return HTTP 204 No Content on DELETE session termination
  • Affects all MCP servers using the Streamable HTTP transport (reproducible with the official Go MCP SDK)

Problem

createCustomFetch wraps node-fetch responses for the SDK, but fails on null body statuses (204, 205, 304):

  1. SSE branch crash: headerHolder retains Accept: text/event-stream from prior GET/POST requests. When DELETE fires, the stale header makes isSSE = true, and new Response(webStream, {status: 204}) throws because the Web Response constructor rejects a body for null body statuses.
  2. Pass-through branch crash: Even if the SSE branch is skipped, the raw node-fetch response is returned via as unknown as Response. The SDK then calls response.body?.cancel() which doesn't exist on Node.js Readable streams (node-fetch), causing TypeError: response.body?.cancel is not a function.

Fix

Detect null body statuses before the SSE stream conversion check and return a proper Web Response with null body:

const isNullBodyStatus =
  response.status === 204 ||
  response.status === 205 ||
  response.status === 304;

if (isNullBodyStatus) {
  return new Response(null, { status, statusText, headers });
}

Note on 101 Switching Protocols: The Fetch spec also lists 101 as a null body status. It is intentionally excluded here because the body stream carries the upgraded protocol data (e.g., WebSocket), which must not be discarded.

Known limitations / Follow-up

  • The stale Accept header leaking from headerHolder into unrelated requests (e.g., DELETE) is a separate root cause that could be addressed in a follow-up PR by scoping header merging per request type.
  • Path Completion requests #3 (return response as unknown as Response) is a pre-existing issue where non-SSE, non-null-body responses are not properly converted from node-fetch to Web Response. This could cause silent failures for response.body?.cancel() on other status codes.

Test plan

  • npm run prettier-check passes
  • npm test passes (487 tests)
  • Manual test: connect to MCP server (Go SDK) via Streamable HTTP → disconnect → no errors in proxy log
  • Manual test: connect → call tool → disconnect → clean shutdown confirmed

When an MCP server returns HTTP 204 No Content (e.g., on DELETE session
termination), the proxy's createCustomFetch crashes with:

  TypeError: Response constructor: Invalid response status code 204

This happens because node-fetch returns a non-null body stream even for
204 responses, and the Web Response constructor rejects a body for null
body statuses per the Fetch spec.

Additionally, returning the raw node-fetch response (path modelcontextprotocol#3) causes
`response.body?.cancel is not a function` because node-fetch uses
Node.js Readable streams, not Web ReadableStreams.

Fix: detect null body statuses (204, 205, 304) and return a proper
Web Response with null body before the SSE stream conversion check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant