- Treat a read stream whose receive end the transport closed as
end-of-input in the opening peek and the relay, mirroring the
tolerance the dispatcher's read loop already had; the read loop moved
here, so its EOF handling moves with it. Two tests close the stream
before and after the first request and expect a clean return.
- ListenHandler's docstring no longer claims it needs SSE mode.
- The legacy refusal states the era rule instead of narrating what the
first request was, so it holds for every opener.
- Frames arriving ahead of the client's first request are kept only up
to a small bound and never decide the era: past the bound they are
dropped while the loop keeps waiting for the opening request, so a
bare notifications/initialized still opens the gate and a flood cannot
grow the buffer or pick an era.
- Check the captured sender context with `is not None`, since an empty
contextvars.Context is falsy.
- Word the legacy refusal to the actual rule (first request carried no
2026 envelope) and update the Server.run docstring and one test to the
decide-from-the-opening-request model.
- The opening-request replay now carries each frame's captured sender
context, so the streams the SSE transport hands the loop keep their
contextvars propagation; pinned by a test on the dual-era loop.
- Bound how many frames may precede the client's first request; past
the limit the loop stops looking for an opening request and serves a
handshake connection, so a peer streaming pre-request frames cannot
grow the buffer without bound.
- Drop the stale docs admonition that stdio still rejects listen, and
trim the era-decision docstring to the rule it now implements.
The stdio driver derived a connection's protocol era from whichever
request finished first, so it could never host a request that does not
return: subscriptions/listen was hard-refused with -32601 even though the
same connection advertised list-changed capabilities, and a legacy
handshake arriving during a slow 2026 request was accepted and locked
the connection out from under it.
Decide the era from the client's first request instead, once, before
any handler runs: a request carrying the 2026-07-28 per-request
envelope opens a 2026 connection, anything else (the initialize
handshake) opens a 2025 one, and the deciding frame is replayed into
the chosen serving loop. A conflicting later claim is refused in one
place (initialize on a 2026 connection gets -32022 naming the served
versions; an enveloped request on a handshake connection gets -32600).
The listen refusal is deleted; the handler was always transport-agnostic.