Editorial

Research guide · MCP · Streamable HTTP · 14 September 2026

MCP RemoteProtocolError: fix incomplete Streamable HTTP SSE responses

MCP SSE failures now show up in at least two high-intent shapes: a request-scoped Streamable HTTP response that ends before its final body arrives, and a long-lived subscriptions/listen stream that acknowledges successfully but dies when a later notification is written. Diagnose the HTTP/SSE lifecycle before changing OAuth, tool schemas or application logic.

Published 14 September 2026 · updated 14 September 2026 · sources checked 14 September 2026

Python client error

httpx.RemoteProtocolError: peer closed connection without sending complete message body

Python server error

ASGI callable returned without completing response

Go long-lived symptom

subscriptions/listen acknowledges, then a later write ends with unexpected EOF

Deployment clue

Failure timing lines up with http.Server.WriteTimeout or an intermediary idle timeout

Why the error is specific

The HTTP connection is closing before MCP finishes the response

The MCP Streamable HTTP contract allows an ordinary request POST to return either one application/json object or a request-scoped text/event-stream SSE response. For normal request-scoped SSE, the stream should reach the final JSON-RPC response before that request is complete.

Python SDK issue #3494, opened 11 September 2026, reports both sides of one failure family: uvicorn says the ASGI callable returned without completing the response, and httpx says the peer closed the connection before the complete message body arrived. The reproduction fails on the initial initialize POST and becomes more frequent with CPU contention or repeated server creation and shutdown on one event loop.

That pattern distinguishes a transport/body-completion problem from an MCP JSON-RPC error. If the server has already selected an SSE response and the connection then dies mid-body, changing OAuth scopes, tool names or output schemas is unlikely to address the first failure boundary.

Fresh Go deployment failure

A subscriptions/listen stream can look healthy until its first real notification fails

Go SDK issue #1262, filed 13 September 2026, describes a different but adjacent failure on current Streamable HTTP deployments. In its minimal reproduction, a subscriptions/listen response is acknowledged immediately while http.Server.WriteTimeout is set to two seconds. Four seconds later, the server produces a real notification and the stream ends with unexpected EOF. With WriteTimeout left at zero in that reproduction, the same notification is delivered and the stream stays open.

The important diagnostic lesson is timing: a long-lived stream can be quiet long enough for an application-server or intermediary deadline to expire even though both sides still appear connected. The failure may only become visible when the next write happens. Compare time-to-failure with your HTTP server deadline, reverse-proxy read timeout and any platform idle timeout before blaming the MCP message.

The 2026-07-28 MCP Streamable HTTP specification explicitly treats subscriptions/listen as a long-lived SSE response, recommends X-Accel-Buffering: no when initiating SSE, and encourages periodic SSE comment lines to keep quiet streams from being closed by intermediaries or client idle timeouts. A comment keepalive can address intermediary idleness, but it cannot by itself repair an application-server write deadline that has already expired; those are separate layers to inspect.

Nine-step diagnostic

Prove which layer kills the stream before applying a workaround

  1. 1. Capture both ends of the failure. Preserve the client exception and the server/runtime log from the same request or subscription. A client-side EOF alone does not prove which layer closed the connection.
  2. 2. Record the response content type. Determine whether the failing request selected text/event-stream or application/json. This guide is primarily about the SSE path.
  3. 3. Identify the exact MCP method. Separate an initialize or tools/call response that dies before its final result from a long-lived subscriptions/listen stream that dies after acknowledgement.
  4. 4. Compare failure time with server deadlines. On Go, inspect http.Server.WriteTimeout; on other stacks, identify the equivalent response/write deadline. If failure appears when the next write occurs after that boundary, the message payload may be innocent.
  5. 5. Audit intermediary idle behavior. Check reverse-proxy read timeouts, buffering and platform connection limits. For long-lived SSE, verify whether X-Accel-Buffering: no and transport-level comment keepalives are actually reaching the wire.
  6. 6. Compare fresh-process and reused-process behavior. The Python report found one reused server stable while repeated start/stop cycles accumulated failures. If a fresh process behaves differently, lifecycle state is a stronger lead than application payload content.
  7. 7. Test under reduced CPU contention. A load-sensitive change in failure rate points toward scheduling/lifecycle timing. Do not interpret a low-load pass as proof of correctness; use it to narrow the race.
  8. 8. Use JSON-only responses only where semantics allow. If an ordinary request needs no streaming and your SDK exposes a supported JSON response mode, this can remove request-scoped SSE while you validate the defect. It is not a replacement for subscriptions/listen.
  9. 9. Bound retries and keep evidence. Retry only with a clear cap and idempotent request semantics. Record SDK/runtime/proxy versions and the observed timeout boundary so a later fix can be verified rather than guessed.

Do not confuse adjacent failures

Four MCP symptoms that need different fixes

SymptomFirst place to investigate
Incomplete request-scoped SSE body / RemoteProtocolErrorASGI response lifecycle, event-stream completion, server process lifecycle and CPU pressure.
subscriptions/listen acknowledged, then first notification ends with unexpected EOFApplication-server write deadline, reverse-proxy idle timeout/buffering, and SSE keepalive behavior.
Long-lived subscriptions/listen pins a serverless runtimeAdvertised change capabilities, listen-stream behavior and stateless/serverless architecture.
HTTP 401/403 or protected-resource metadata 404OAuth resource metadata, audience/resource binding and mounted routing.

Primary evidence

Continue the implementation path