Repository navigation
v3.2.0
Streamable HTTP correctness — a client response hang, CRLF SSE parsing,
POST-stream primer events, a reconnect storm — plus transport hot-path
performance and an interoperability fix for clients that recycle request ids.
Most of this release was contributed by @ForrestThump (#14, #15, #16, #18,
#19, #20, #21, #24) — thank you!
This is a minor, not a patch, release. Two changes below alter the public
API in ways a strict semver check calls breaking; both are called out under
Changed with the one-line fix. Nothing else in the 3.x surface moved.
Fixed
-
A client that reuses JSON-RPC request ids is no longer locked out — every
transport kept a per-session set of every id it had ever seen and answered a
repeat with-32600 "Request ID already used in this session". The rule is
real but it binds the other party: the spec's "MUST NOT have been previously
used" constrains the requestor, whose purpose is correlating a response with
its request, while a receiver's only obligation is to echo the id back. So the
check bought the server nothing and made interop depend on a client's
id-allocation strategy; SEP-2567 re-scopes the constraint to ids that are
concurrently outstanding, and neitherrmcpnor TurboMCP v4 enforces it. The
set was also unbounded, growing for the life of the session. Concurrent reuse
is still logged as a warning — two responses carrying one id are unmatchable —
but never refused. (#25) -
The SSE reconnect loop no longer hammers a server that closes idle
streams — backoff was keyed on whether the GET was accepted, so a server
that accepted and then closed the stream was retried with zero delay
indefinitely (measured: 7,657 connections in 2s). Backoff is now keyed on how
long the stream actually stayed up. Separately, a routine reconnect was logged
aterror/warn; a server closing an idle stream is behaving correctly and
the client reconnecting is it doing its job, so that is nowdebug. Measured
on the reporter's deployment: 61 SSE error/warn lines in a 90-second window
before, 0 after. (#24) -
Streamable HTTP client no longer hangs waiting for POST-response SSE
streams to close —send()used to block until the server ended the
request's SSE stream, stalling clients against servers that keep the
stream open after answering. The client now returns as soon as the
correlated response (matching request ID with aresultorerror)
arrives; remaining stream messages are still consumed and queued. (#21) -
SSE event parsing accepts CRLF and lone-CR line endings — the client
HTTP transport now normalizes\r\nand\rto\nper the SSE
specification before splitting events, fixing interop with servers (and
proxies) that emit CRLF-framed streams. (#21) -
Spec-required primer event restored on POST-response SSE streams —
POST-initiated streams once again open with an empty-data event carrying a
globally unique, stream-encoding event ID ({session}-{stream}-0), giving
clients aLast-Event-IDfor resumption as the 2025-11-25 Streamable HTTP
transport spec recommends. (#16, #17) -
McpHandlerpublic-method types are re-exported so downstream code can
name them without depending on internal crates. (#15) -
just testworks on Windows. (#14)
Changed
StreamableHttpClientConfiggainedsse_healthy_stream_threshold
(default 10s), which decides whether a closed SSE stream counts as a failure
worth backing off from. Keep it comfortably under your server's idle timeout.
Breaking for exhaustive struct literals — if you construct this config by
naming every field, add..Default::default(). (#24)turbomcp_server::transport::http::SessionManageris no longer public
unless the non-publicinternal-benchfeature is on. It was internal
plumbing that had been exposed unintentionally; it is nowpub(crate)except
where the benchmarks need it. Breaking if you named this type — there is no
supported replacement, as it was never meant to be part of the API. (#20)- Transport per-message overhead reduced (#20, criterion-verified):
HTTP client transport metrics moved fromRwLockto the same lock-free
AtomicMetricsthe stdio transport uses; server SSE broadcast payloads are
shared asArc<str>instead of clonedStrings per subscriber; SSE frames
are encoded with a single exact-size allocation; the stdio transport slices
trimmed lines zero-copy from the read buffer. Benchmarks ship behind the
new non-publicinternal-benchfeature. - Duplicate dependency versions consolidated across the workspace. (#19)
- Documentation ASCII diagrams realigned. (#18)