examples/stories/sse_polling/README.md
Legacy mechanism (2025 handshake era).
Last-Event-IDresumability and the sessionful transport are removed in the 2026-07-28 protocol (SEP-2575) with no modern-era equivalent; the closest 2026-era pattern is client-side reconnection over a persistedDiscoverResult—reconnect/. TODO(maxisbey): revisit before beta.
SEP-1699 server-initiated SSE disconnection with Last-Event-ID replay. The
server's EventStore stamps every SSE event with an ID and opens each response
stream with a priming event; mid-handler the tool calls
ctx.close_sse_stream() to release the open HTTP response (freeing a
connection slot), keeps emitting progress into the event store, and returns.
The client transport sees the stream end, reconnects with Last-Event-ID, and
the event store replays everything it missed — await client.call_tool(...)
resolves as if the disconnect never happened.
# HTTP — the client self-hosts the app on a free port, runs, then tears it down
uv run python -m stories.sse_polling.client --http --legacy
# same, against the lowlevel-API server variant
uv run python -m stories.sse_polling.client --http --legacy --server server_lowlevel
# against a server you run yourself (real uvicorn on :8000)
uv run python -m stories.sse_polling.server --port 8000 &
SERVER_PID=$!
uv run python -m stories.sse_polling.client --http http://127.0.0.1:8000/mcp --legacy
kill "$SERVER_PID"
client.py main — opens with async with Client(target, mode=mode).
There is no client-side resumability configuration: the Client and the
streamable_http_client transport handle the priming event, the SSE retry:
hint, and the Last-Event-ID reconnect automatically. The assertion that the
"after-close" progress message arrived is the proof — it was emitted while
no SSE stream was open.server.py — streamable_http_app(event_store=..., retry_interval=0).
Passing an EventStore is what enables resumability: every SSE event gets an
ID and the response opens with a priming event so the client always has a
Last-Event-ID to reconnect with. retry_interval=0 makes the client's
reconnect wait a no-op (the SSE retry: hint).server.py — await ctx.close_sse_stream(). Ends the current request's
SSE response without cancelling the handler. Everything emitted afterwards
goes to the event store and is replayed on reconnect. A no-op when no
event_store is configured.server_lowlevel.py — ctx.close_sse_stream. On the lowlevel API the
callback is an optional field on ServerRequestContext; it is None unless
an event store is wired and the negotiated version is in the 2025 era.streamable_http_app(...) is a hosting entry that reshapes in a later
release; this story calls it directly because the event-store and
retry-interval kwargs are the point.transport_security=NO_DNS_REBIND)
because the in-process httpx client sends no Origin header. Drop the kwarg
for a real deployment.event_store.py here is example-grade only (sequential IDs, no eviction). A
production server would back the EventStore interface with persistent
storage.Resumability and Redelivery · SEP-1699 (server-initiated SSE close)
standalone_get/ (the standalone-stream sibling of close_sse_stream()),
reconnect/ (the modern-era reconnection story — persisted DiscoverResult,
no event store), streaming/ (in-flight progress + cancellation without the
disconnect).