Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions docs/handlers/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ Two things the stream is *not*:
handler on the low-level `Server` and acknowledge a smaller filter than the client asked
for; the acknowledgment is how the client learns what it actually got.

!!! warning "Streamable HTTP only, for now"
`subscriptions/listen` needs a transport that can stream a request's response, which today
means streamable HTTP. Over stdio a 2026-07-28 connection rejects the method with
METHOD_NOT_FOUND, even though `server/discover` advertises the subscription capabilities
there. Serving it over stdio is planned; the open-stream semantics for that transport are
not built yet.

## The client end

Here is a client on the other side of that stream, following the board:
Expand Down
2 changes: 1 addition & 1 deletion docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ That file is the pitch in one place: one server, one `Resolve`-backed tool, and

### Change notifications become one stream

At 2026-07-28 the standalone HTTP GET stream and `resources/subscribe` are replaced by `subscriptions/listen`: the client opens one long-lived stream and names the notification kinds it wants. `MCPServer` serves it out of the box; you publish with `await ctx.notify_resource_updated(uri)` (and `notify_tools_changed()`, and so on), and multi-replica deployments plug in a shared `SubscriptionBus`. On the client (since `2.0.0b2`), `async with client.listen(...)` opens the stream: the filter goes in as keyword arguments, typed change events come back, and `sub.honored` is the subset the server agreed to deliver. One honest caveat: over stdio the server does not serve the stream yet.
At 2026-07-28 the standalone HTTP GET stream and `resources/subscribe` are replaced by `subscriptions/listen`: the client opens one long-lived stream and names the notification kinds it wants. `MCPServer` serves it out of the box; you publish with `await ctx.notify_resource_updated(uri)` (and `notify_tools_changed()`, and so on), and multi-replica deployments plug in a shared `SubscriptionBus`. On the client (since `2.0.0b2`), `async with client.listen(...)` opens the stream: the filter goes in as keyword arguments, typed change events come back, and `sub.honored` is the subset the server agreed to deliver.
Comment thread
maxisbey marked this conversation as resolved.

**[Subscriptions](handlers/subscriptions.md)** covers publishing and serving, **[its Clients twin](client/subscriptions.md)** the watching end, and **[Deploy & scale](run/deploy.md)** the bus.

Expand Down
6 changes: 3 additions & 3 deletions src/mcp/server/lowlevel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,9 +703,9 @@ async def run(

Thin wrapper over `serve_dual_era_loop`: enters the server lifespan,
then drives the loop, serving the legacy handshake era and the modern
per-request-envelope era (the first era-distinctive message to succeed
locks the connection). Transports with their own lifespan owner (the
streamable-HTTP manager) call `serve_loop` directly instead.
per-request-envelope era (the client's first request decides which).
Transports with their own lifespan owner (the streamable-HTTP manager)
call `serve_loop` directly instead.
"""
async with self.lifespan(self) as lifespan_context:
await serve_dual_era_loop(
Expand Down
Loading
Loading