49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
streaming
The three in-flight server→client channels during a tool call: progress
(ctx.report_progress → the caller's progress_callback=), logging
(notifications/message → the client's logging_callback=), and
cancellation (abandoning the client's awaiting scope interrupts the server
handler). One countdown(steps) tool emits a progress notification and a log
line per step; the client asserts both streams arrive in order, then cancels a
long call mid-flight by cancelling the enclosing anyio.CancelScope from
inside the progress callback (event-driven, no sleep).
Run it
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.streaming.client
uv run python -m stories.streaming.client --server server_lowlevel
# HTTP — the client self-hosts the server on a free port, runs, then tears it
# down
uv run python -m stories.streaming.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.streaming.client --http --server server_lowlevel
What to look at
client.pymain— opens withasync with Client(target, mode=mode, logging_callback=on_log). The story owns that construction; the harness only picks the target and era.logging_callbackis constructor-only onClient(no setter after connect), so the callback and thelogslist it fills are closed over right above theClient(...)call.server.py—ctx.report_progress(i, steps, msg)is a silent no-op when the caller passed noprogress_callback; the SDK reads the token from the request's_metafor you. The log notification is sent via the rawsession.send_notification(...)because thectx.log()/ctx.info()shorthands are deprecated (SEP-2577) with no non-deprecated replacement yet.related_request_id=keeps the log on this request's response stream — over streamable HTTP an unrelated notification would ride the standalone GET stream instead.server.py—ctx.request_context.session/ctx.request_context.request_idis the interim 2-hop path; a later release will shorten these.server.py— theexcept anyio.get_cancelled_exc_class(): raiseblock is where a real handler would release resources before re-raising. Never swallow the cancellation exception.client.py— cancellation is just cancelling theanyioscope aroundawait client.call_tool(...); the SDK sendsnotifications/cancelledfor you on stateful transports. There is noclient.cancel(request_id)API.server_lowlevel.py— the same wire contract built by hand againstServerRequestContext.sessiondirectly.
Caveats
- Logging is deprecated in the 2026-07-28 protocol (SEP-2577); functional
through the deprecation window. Migration: write to stderr or emit
OpenTelemetry instead of
notifications/message. It is shown here because servers still need to support 2025-era clients during that window. Progress and cancellation are not deprecated. TODO(maxisbey): revisit before beta. - When a request is cancelled the server currently replies with
ErrorData(code=0, message="Request cancelled"); the spec says it should not reply at all. The client never observes it (its awaiting task is already cancelled), so this story does not assert on the reply.
Spec
Progress, cancellation, logging
See also
parallel_calls/ (concurrent in-flight calls), error_handling/ (the
cancellation error path), tools/ (the basics this builds on).