# What structural signals indicate the SSE resumption cursor has silently desynchronized from the MCP session's stream epoch **Date of Record:** 2026 November 13 **Scope:** Applies to MCP servers and clients negotiating the HTTP-native streamable transport (POST for request/response, GET for the server-to-client SSE channel) where the client advertises resumability via `Last-Event-ID`. Does not apply to pure WebSocket transports, nor to single-shot non-resumable SSE where the server never emits `id:` fields. ## Core Distinction The distinction that matters is between *transport-level resumption* and *session-level stream continuity*. The MCP streamable transport lets a client reconnect its SSE GET channel and present `Last-Event-ID` so the server can replay buffered events. The trap: an `id:` value is a valid integer that the server will happily accept, and the replay will "succeed" — HTTP 200, a well-formed event stream — while the cursor points into a *prior stream epoch* (a previous session incarnation, or a buffer position that was truncated and reused). The reconnect is transport-correct and session-incorrect. The structural signal is not an error. It is the shape of the first replayed frame. When the cursor is coherent, the first event after reconnect is the immediate successor of the last delivered event, and its `id` is `last_seen + 1` within the same epoch. When the cursor has desynchronized, one of three structural signatures appears: (a) the first replayed `id` is *lower* than `last_seen` (buffer wrapped or epoch reset), (b) the first replayed `id` jumps forward by more than one (events were dropped between epochs), or (c) the server emits a fresh `id` with no replay at all despite the client presenting a cursor — meaning the server silently discarded the buffer and opened a new epoch under the same session identifier. ## What Was Observed During a long-running tool-call session, the client's SSE GET channel dropped on an idle timeout. The client reconnected with `Last-Event-ID: 8412`. The server returned HTTP 200 and immediately emitted an event with `id: 17`. There was no error frame, no `retry:` hint, no session-termination notice. The client library treated `17` as the next cursor and continued. Subsequent events incremented `17, 18, 19...`. The session had, in fact, been torn down server-side and recreated under a recycled session key; the server's per-session event buffer restarted its monotonic counter. The client's in-flight tool call — a multi-part streaming result whose continuation was keyed to event `8413` onward — never received its remaining parts. The tool call hung until a separate application-level timeout fired, and the failure surfaced as a generic "tool did not complete" rather than a transport error. The decisive structural signal was the *regression* of `id` across the reconnect boundary: `8412 → 17`. A monotonic cursor contract was violated, and nothing in the transport layer flagged it. ## Condition of Validity This diagnosis is valid only when the server's event `id` is specified as monotonically increasing within a session epoch, and the client persists the last seen `id` across reconnects. If the server uses opaque non-numeric IDs, or explicitly documents per-epoch counters with an epoch tag embedded in the ID (e.g., `epoch:seq`), then a bare integer comparison is meaningless and the epoch tag must be compared instead. Likewise, if the client does not persist `Last-Event-ID` and instead reconnects without a cursor, the server is entitled to start a fresh stream and the regression is expected, not a fault. ## Decision Boundary The boundary is: **does the client validate cursor monotonicity and epoch identity on the first replayed frame, or does it trust the transport's HTTP 200 as proof of continuity?** - Below the boundary (accept): short-lived sessions where any dropped event is recoverable by re-issuing the originating request, and where the server guarantees buffer lifetime exceeds session lifetime. - Above the boundary (reject and re-establish): any session carrying partial multi-part tool results, streaming model output, or stateful server-side resources where a gap in the event sequence corrupts the caller's state. When above the boundary, the correct response to a detected regression is *not* to resume. It is to treat the session as lost, re-negotiate a new session, and replay the originating request from the last application-level checkpoint — never from the SSE cursor. ## Operational Effect Clients that enforce cursor monotonicity convert a silent data-loss failure into an explicit, recoverable session-restart. Clients that do not enforce it exhibit the worst failure class in the transport: a hung tool call with no transport-level error, indistinguishable at the application layer from a slow server. The enforcement cost is one integer comparison per reconnect; the cost of omission is unbounded latency on a fraction of reconnects that grows with session duration and server buffer churn. ```python class StreamEpochDesyncError(RuntimeError): """Raised when a resumed SSE cursor violates the session's epoch/monotonicity contract.""" pass def validate_resumed_cursor(last_seen_id: int, first_replayed_id: int, same_epoch: bool) -> None: """ Called on the first SSE frame after a reconnect that presented Last-Event-ID. Enforces: same epoch, and first replayed id == last_seen_id + 1. Anything else is a desync and must NOT be resumed. """ if not same_epoch: raise StreamEpochDesyncError( f"resumed into a different stream epoch: last_seen={last_seen_id}, " f"first_replayed={first_replayed_id}; buffer was reset or session recycled" ) if first_replayed_id <= last_seen_id: raise StreamEpochDesyncError( f"cursor regression: last_seen={last_seen_id}, first_replayed={first_replayed_id}; " f"server buffer wrapped or counter restarted" ) if first_replayed_id != last_seen_id + 1: raise StreamEpochDesyncError( f"event gap across reconnect: expected {last_seen_id + 1}, " f"got {first_replayed_id}; events {last_seen_id + 1}..{first_replayed_id - 1} lost" ) # Contract satisfied: safe to continue consuming this stream. ```