You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make the subscription bus async and fix review findings
Review follow-ups to the subscriptions/listen runtime:
- Rename EventBus to SubscriptionBus: it carries exactly the four
subscription event kinds, and the generic name collided conceptually
with the unrelated EventStore in the same package.
- Make SubscriptionBus.publish async. Backend implementations (Redis,
NATS) do network I/O on publish; a sync protocol would force them to
block the loop or spawn their own tasks. This also makes the Context
notify_* methods genuinely async, so they cannot be called from sync
handlers (which run on worker threads where waking the listen streams
is unsafe) - the illegal context is now unrepresentable instead of
guarded.
- Subscribe each listen stream to the bus before sending the ack: an
event published while the ack write was suspended used to be lost
after the client had been told the subscription was live. The ack is
still the first frame - the handler task alone writes the stream and
only drains the buffer after the ack send returns.
- Register bus listeners under per-subscription tokens: equal callables
(e.g. the same bound method subscribed twice) used to collapse in the
set, so one unsubscribe silently detached both registrations.
- Drop the stdio claim from ListenHandler's docstring (no 2026-era
stdio serving exists yet) and document that resource-updated URIs are
matched as exact strings and do not reach legacy resources/subscribe
subscribers.
Copy file name to clipboardExpand all lines: docs/advanced/low-level-server.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -183,7 +183,7 @@ Each of these is one idea you now have the vocabulary for; each has its own chap
183
183
184
184
*`on_call_tool`, `on_get_prompt`, and `on_read_resource` may return an `InputRequiredResult` instead of their normal result to pause the call and ask the client for input; see **[Multi-round-trip requests](multi-round-trip.md)**. True to this tier, nothing is installed for you: where `MCPServer` seals `requestState` by default, here the `request_state` you set crosses the wire exactly as written until you opt in with `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))`: one line (both names import from `mcp.server.request_state`) for the identical sealing and verification `MCPServer` performs (**[Protecting `requestState`](multi-round-trip.md#protecting-requeststate)**).
185
185
*`on_list_resources`, `on_read_resource`, `on_list_prompts`, `on_get_prompt`, `on_completion` are the same `(ctx, params) -> result` shape for the other primitives.
186
-
*`on_subscriptions_listen` serves the 2026-07-28 `subscriptions/listen` stream. Pass an `mcp.server.subscriptions.ListenHandler` built over an `EventBus` (the in-memory default, or your own — e.g. Redis-backed), keep the bus where your other handlers can reach it (the lifespan is a natural home), and publish `ServerEvent`s to it. The handler owns the wire semantics: ack-first, per-stream filtering, and subscription-id tagging.
186
+
*`on_subscriptions_listen` serves the 2026-07-28 `subscriptions/listen` stream. Pass an `mcp.server.subscriptions.ListenHandler` built over a `SubscriptionBus` (the in-memory default, or your own — e.g. Redis-backed), keep the bus where your other handlers can reach it (the lifespan is a natural home), and publish `ServerEvent`s to it. The handler owns the wire semantics: ack-first, per-stream filtering, and subscription-id tagging.
187
187
*`server.streamable_http_app()` returns the same Starlette app `MCPServer`'s does; deploy it the way **[Running your server](../run/index.md)** deploys any other ASGI app. There is no `server.run(transport=...)` down here: `server.run(read_stream, write_stream, server.create_initialization_options())` drives one connection over a pair of streams, and that one line is the whole story.
Copy file name to clipboardExpand all lines: docs/tutorial/context.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -104,7 +104,7 @@ What a server offers is not fixed at import time. Register a tool at runtime, th
104
104
105
105
The siblings are `send_resource_list_changed()`, `send_prompt_list_changed()`, and `send_resource_updated(uri)` for a change to one specific resource.
106
106
107
-
On a 2026-07-28 connection, clients receive change notifications only on a `subscriptions/listen` stream they opened. The `Context` publish methods — `ctx.notify_tools_changed()`, `ctx.notify_prompts_changed()`, `ctx.notify_resources_changed()`, and `ctx.notify_resource_updated(uri)` — deliver to every subscribed stream at once, and are synchronous (no `await`). Behind a load balancer, pass your own `EventBus` implementation as `MCPServer(subscriptions=...)` to fan events out across replicas; the in-process default covers a single server.
107
+
On a 2026-07-28 connection, clients receive change notifications only on a `subscriptions/listen` stream they opened — the `send_*` methods above do not reach those streams. The `Context` publish methods — `await ctx.notify_tools_changed()`, `await ctx.notify_prompts_changed()`, `await ctx.notify_resources_changed()`, and `await ctx.notify_resource_updated(uri)` — deliver to every subscribed stream at once. Behind a load balancer, pass your own `SubscriptionBus` implementation as `MCPServer(subscriptions=...)` to fan events out across replicas; the in-process default covers a single server.
108
108
109
109
!!! check
110
110
Before anyone runs `enable_recommendations`, the tool you are promising does not exist. Call it
0 commit comments