Key takeaways
- SDK 1.30.0 reported an errored request-scoped SSE body through transport onerror at 18 ms and 2 ms, yet the associated ping promises rejected only at 303 ms and 902 ms.
- A clean SSE EOF was silent and the matching requests rejected at 302 ms and 903 ms, again with protocol timeout rather than a response-leg error.
- The application/json control resolved in 0 ms in the same fixture, isolating the wait to the asynchronous SSE response lifecycle rather than request dispatch or JSON-RPC matching generally.
- Each timed-out request emitted one notifications/cancelled message at the deadline. Cancellation cleaned up after the wait; it did not turn body loss into prompt request settlement.
- Size MCP agent deadlines from consequence and recovery budgets, cap concurrent in-flight work, and treat transport error, request timeout and unknown remote outcome as distinct states.
The sharp question: when does an agent learn that its response path is gone?
An MCP tool request can use Streamable HTTP so a server returns one or more messages over Server-Sent Events. For an AI agent, the practical contract is not merely that the transport logs a broken stream. The request promise must settle soon enough for the orchestrator to release capacity, preserve the operation’s unknown outcome and choose a bounded recovery path.
In this fixture, SDK 1.30.0 observed a synthetic SSE body error within 18 ms at a 300 ms deadline and within 2 ms at a 900 ms deadline. The ping promise did not reject at either observation. It waited until 303 ms and 902 ms respectively, then rejected as timed out. A clean EOF produced no transport error and waited 302 ms or 903 ms.
Chronology: Streamable HTTP made one POST response a small protocol channel
MCP’s 2024-11-05 HTTP+SSE design separated message submission from a listening stream. The newer Streamable HTTP transport replaced it with one endpoint supporting POST and GET. For a JSON-RPC request, the server may return one application/json response or open a text/event-stream response that eventually carries the matching JSON-RPC result or error.
SDK 1.30.0 shipped July 27, 2026. Issue 2739 was opened August 30 after reproductions on SDK 1.26.0 and 1.29.0. On September 1, another contributor reported the same behavior on 1.30.0 with a JSON control. This AccessAllGPT run independently tested 1.30.0 at two timeout values, captured the emitted cancellation notification and bound the distributed transport source by hash.
The specification ties the SSE body to the initiating request
The 2025-11-25 transport specification says each client message uses a new POST. When that POST contains a JSON-RPC request, a successful server may answer with application/json or text/event-stream. If it chooses SSE, the stream should eventually include the JSON-RPC response for the request. The response body is therefore not an unrelated telemetry feed; it is a delivery leg for request completion.
The specification also permits disconnection and resumability patterns. This fixture deliberately supplied no SSE event ID, priming event or resumption token. Once its body errored or ended before a response, it had no tested replay handoff. The bounded question was whether the original request settled at that loss or only at its separate protocol deadline.
The implementation hands SSE reading to an unawaited task
At tag 1.30.0, send awaits fetch and inspects the response content type. The application/json branch awaits response.json, parses JSON-RPC messages and forwards them before send completes. The text/event-stream branch calls _handleSseStream and does not await a body-lifetime promise.
Inside _handleSseStream, processStream reads the decoded event stream. EOF leaves its loop and may schedule reconnection only when the stream is reconnectable or has received a priming event. A read error calls this.onerror and may apply the same resumption test. The function invokes processStream without awaiting or returning it, so non-resumable EOF or error does not reject the already-completed send call.
Protocol timeout remains the request’s settlement clock
The Client’s request layer tracks the JSON-RPC ID and waits for a matching response. Because transport send has resolved after handing off the SSE body, an SSE reader failure does not flow through the send rejection path. Without a matching JSON-RPC message, the configured request timeout remains the mechanism that settles the pending promise.
That separation explains the probe’s timing pattern: transport observation and application settlement were different events. Logging the first did not release the second. Clean EOF was harder to see because it generated neither a JSON-RPC response nor the global error callback in this non-resumable fixture.
The fixture varied only response mode, body ending and deadline
The checked-in harness installs the official package into a temporary directory. Its synthetic fetch implementation answers initialize with valid JSON, accepts notifications/initialized, rejects the optional GET listening stream with 405 and then handles ping. No socket, DNS lookup or third-party MCP server is involved.
For SSE error, a ReadableStream errors on the next timer turn. For EOF, it closes on that turn without an event. For the control, the same ping receives a matching application/json JSON-RPC result. The four SSE runs use 300 ms and 900 ms request deadlines so a fixed processing delay can be distinguished from deadline-bound settlement.
The error arrived early; the 300 ms request did not
With the 300 ms deadline, the errored body reached client.onerror at 18 ms with “SSE stream disconnected: Error: synthetic response leg lost.” The ping rejected at 303 ms with MCP error -32001, “Request timed out.” The transport knew the response leg had failed for roughly 285 ms before the request settled.
This is not a claim that every environment adds exactly 285 ms. Timer granularity, load and runtime vary. The important observed relationship is semantic: the error callback timestamp was far earlier than the request rejection, and the rejection’s class was protocol timeout rather than the stream error.
Tripling the deadline tripled the pending interval
The 900 ms error run reported the broken body at 2 ms and rejected ping at 902 ms. If the first 303 ms result had been a fixed transport cleanup interval, changing the outer deadline would not be expected to move settlement by about 599 ms. It did.
The two-point check does not characterize every timeout or scheduler. It does show that, in this fixture, the request remained coupled to the configured protocol timeout after the body error. A production setting of 30 seconds or five minutes should therefore be treated as potential retention time for a lost non-resumable response, not only as permission for slow legitimate work.
Clean EOF was silent and still consumed the whole deadline
The EOF runs closed their POST SSE bodies before sending an event. Neither generated a transport onerror callback. Their ping promises rejected at 302 ms and 903 ms with the same timeout error. For monitoring based only on transport errors, this path looks like an ordinary slow tool until the deadline fires.
A server can close cleanly at the byte-stream layer while still violating the application expectation that this request receive a JSON-RPC response. “No stream exception” is not equivalent to “request complete.” Hosts need request-ID lifecycle telemetry in addition to connection and parser errors.
The JSON response control resolved immediately
The application/json control used the same client, transport class, synthetic endpoint and ping method with a 900 ms deadline. It returned the matching JSON-RPC result and resolved in 0 ms at the harness’s millisecond resolution. It emitted no cancellation notification and no transport error.
This control rules out a generally broken ping implementation, request-ID mismatch or fixture-wide wait. It does not compare streaming performance with JSON performance. It proves only that direct response parsing settled the controlled request promptly while an SSE body without its matching response did not.
Cancellation happened at timeout, not at response-leg loss
All four SSE runs sent exactly one notifications/cancelled message when the protocol timeout settled ping. Before client.close, the harness observed no abort event on the transport’s fetch signal. Closing the client later aborted the shared transport signal; those close-time events were deliberately excluded from the request-settlement assertion.
This narrows the original issue wording rather than copying it. SDK 1.30.0 did emit protocol cancellation in this run, but only at the deadline. That cleanup is useful for a server still doing work. It does not recover the interval during which a non-resumable response leg was already known to be unusable.
For agents, pending requests consume more than latency budget
An autonomous host often gates model turns, tool concurrency, leases and approval state on pending calls. A lost response that remains pending can occupy a worker slot, hold a task lock, delay a plan, retain request context and push later work into queue timeouts. Multiplying a long deadline by many concurrent calls turns a response-lifecycle seam into capacity risk.
The remote execution outcome may also be unknown. The server accepted the POST before its response leg disappeared. Rejecting promptly would improve local control, but it would not prove the tool did not run. Hosts must separate response delivery failure from operation failure and must not automatically replay consequential tools merely because the client lacks a result.
Bound timeouts by consequence, then bound concurrency separately
Choose deadlines from measured operation envelopes and recovery objectives, not one generous global default. Read-only lookup, model inference, build execution and an approval-gated deployment have different safe waiting and replay rules. Record queue time, server processing budget and response-delivery budget separately where the stack permits it.
A timeout alone is not admission control. Cap in-flight MCP requests per server, tenant and authority class; reserve capacity for cancellation and status checks; and circuit-break on correlated EOF, stream errors or timeout growth. Long-running work should use durable task semantics or an application job handle rather than depending on one POST response body for the entire operation.
What failed, and what remains unknown
The first harness run failed one assertion because it expected no notifications/cancelled message. Inspection showed SDK 1.30.0 emitted exactly one at each timeout. I changed the assertion and article to preserve that contradiction with the issue’s pre-timeout description. The completed run then passed 42 assertions, and a repeated run reproduced the deadline-bound pattern.
The probe did not test a language model, real tool, remote network, HTTP proxy, authentication, session ID, reconnectable SSE, event ID, resumption, multiple responses, batch request, server implementation, v2 split SDK, another language SDK, load, process crash or external side effect. It establishes one local request-lifecycle behavior, not prevalence, exploitability or the maintainers’ preferred fix.
Decision: deploy Streamable HTTP only with an explicit unknown-outcome path
Deploy MCP SSE responses for agent work only when request IDs have bounded deadlines, in-flight caps, transport and request-lifecycle telemetry, cancellation evidence, idempotency classification and an unknown-outcome state that blocks unsafe replay. Regression-test body error, clean EOF, malformed event, reconnect exhaustion, successful SSE, successful JSON and client shutdown on the exact SDK and proxy path in production.
Constrain to read-only or draft-producing tools when the host cannot distinguish response loss from execution failure. Roll back or circuit-break when lost bodies retain enough requests to exhaust capacity, clean EOF is invisible until an unacceptable deadline, cancellation cannot be verified, or a timeout causes automatic replay of a consequential action. A transport error callback is evidence. It is not request settlement.
Copy-ready MCP response-lifecycle gate
Complete this record for each Streamable HTTP authority class before an AI agent can call it autonomously.
Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].
Server, tenant, method or tool, consequence, expected duration, idempotency and compensation path.
SDK/version/digest, protocol version, proxy path, JSON/SSE modes, session behavior and resumption support.
Queue, execution, response-delivery and total deadlines; evidence behind each; maximum retained context.
Successful JSON/SSE, body error, clean EOF, malformed event, reconnect exhaustion, timeout, cancellation and close.
State recorded when POST acceptance may have preceded response loss; status lookup and evidence required before replay.
Per-server, tenant and authority in-flight caps; reserved recovery capacity; circuit-break and backpressure thresholds.
Notification timing, server acknowledgement, abort behavior, work termination evidence and late-result handling.
Request ID, transport error/EOF, response mode, deadline, settlement reason, cancellation, remote operation ID and retained traces.
Read retry, idempotency key, exactly-once evidence, compensation and actions never repeated from a timeout alone.
Capacity saturation, silent EOF beyond budget, lost request identity, unverified cancellation or unsafe automatic replay.
Primary sources
Browse the publication-wide evidence index →
- Transports (protocol version 2025-11-25)Model Context Protocol Specification · Reviewed: Streamable HTTP architecture, POST request rules, JSON and SSE response modes, listening streams, disconnection and session management · Retrieved · Supports: The specification permits a server to answer an MCP JSON-RPC request with either application/json or text/event-stream and requires an SSE response eventually to include the JSON-RPC response for that request.
- TypeScript SDK release 1.30.0Model Context Protocol TypeScript SDK · Reviewed: Release identity, tag, publication date, v1.x branch and changed-package record · Retrieved · Supports: The official release record binds the tested @modelcontextprotocol/sdk package to version 1.30.0, released July 27, 2026 on the v1.x line.
- StreamableHTTPClientTransport at SDK 1.30.0Model Context Protocol TypeScript SDK · Reviewed: SSE reader lifecycle, EOF and error branches, processStream invocation, POST send path, response media-type branch and JSON control path · Retrieved · Supports: The pinned implementation starts its SSE reader without returning or awaiting the reader task; errors reach the transport onerror callback, while send returns after handing off an SSE body. The JSON branch awaits and parses its response directly.
- @modelcontextprotocol/sdk 1.30.0 registry recordnpm Registry · Reviewed: Package identity, version, distribution integrity, publication metadata, repository and runtime requirements · Retrieved · Supports: The npm registry identifies the installable official SDK 1.30.0 artifact used in the clean sandbox and binds it to distribution metadata.
- POST SSE responses remain pending after EOF or error (issue 2739)Model Context Protocol TypeScript SDK issue tracker · Reviewed: Original report, reproduction, expected behavior, implementation analysis, tested versions, related lifecycle issues and open state · Retrieved · Supports: An independent contributor reported on August 30 that SDK 1.26.0 and 1.29.0 left request-scoped SSE responses pending until the protocol timeout after body EOF or error, and distinguished the case from explicit cancellation.
- Independent SDK 1.30.0 reproduction on issue 2739Model Context Protocol TypeScript SDK issue tracker · Reviewed: Independent environment, SDK versions, 300 millisecond measurements, SSE error path, clean EOF path, JSON control and linked reproduction · Retrieved · Supports: A second contributor independently reported the same timeout-bound settlement on Node 22.18.0 with SDK 1.29.0 and 1.30.0, while an application/json control resolved promptly.
Limitations
This is a bounded local reproduction on Node v24.10.0, Darwin arm64, @modelcontextprotocol/sdk 1.30.0 and Zod 3.25.76 using a synthetic fetch implementation, one ping method, non-resumable request-scoped POST SSE bodies and 300 ms and 900 ms deadlines. It did not test a model, production agent, remote server or network, proxy, authentication, session, event ID, resumption, successful SSE event, batch, concurrency, load, v2 package, another SDK, real tool, external effect or user data. Millisecond timings are observations from two runs per failure mode, not a performance benchmark or universal overhead estimate. The experiment demonstrates that body error and clean EOF did not settle the matching request before its protocol timeout in this fixture; it does not measure prevalence, prove exploitation or prescribe the SDK fix.
Disclosures
AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. Public issue 2739 and its independent follow-up prompted the question; their authors supplied no private information and did not validate this run. No model, production client, third-party MCP server, credential, external endpoint or user data was used. The MCP project, issue participants, SDK maintainers, npm and Zod maintainers did not review this article or provide access, private data, payment or endorsement. AccessAllGPT Research is operated by NeuralArc, is independent, and is not affiliated with OpenAI, Anthropic, the MCP project, npm or Zod. Publication-wide relationships are listed on the disclosures page.
Further AccessAllGPT guidance
- An Optional MCP Task Schema Can Strand an Agent’s Completed Result
- MCP v2 Can Return a Partial Tool List as Complete
- MCP Pagination Can Evict Earlier Tool Validators
- MCP Output Validation Depends on Tool Discovery
- MCP’s readOnlyHint Is Not a Permission Boundary
- MCP Token Passthrough Needs an Audience Boundary
- Where Human Approval Belongs in AI Automation
- AccessAllGPT Research methodology
- Publication disclosures
Continue the research
Get evidence-led updates for teams making production AI decisions.