Title: Serve subscriptions/listen with a pluggable event bus (SEP-2575) by maxisbey · Pull Request #3035 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Serve subscriptions/listen with a pluggable event bus (SEP-2575) by maxisbey · Pull Request #3035 · modelcontextprotocol/python-sdk
X Title: Serve subscriptions/listen with a pluggable event bus (SEP-2575) by maxisbey · Pull Request #3035 · modelcontextprotocol/python-sdk
Description: Implements server-side subscriptions/listen (SEP-2575) for the 2026-07-28 revision. NoteThis branch was previously a much larger change covering both sides of the protocol; it has been reset and rebuilt as a minimal server-side implementation. Client-side support will follow separately. On the 2026-07-28 wire there is no standing GET stream: a client opts in to server events by sending a subscriptions/listen request whose response is the stream. This PR adds the serving half: What's here mcp/server/subscriptions.py — the whole runtime: SubscriptionBus: a two-method protocol (async publish / sync subscribe) over four typed ServerEvent kinds. The bus carries events, not wire notifications — a custom implementation (e.g. Redis pub/sub for multi-replica deployments) never sees JSON-RPC. InMemorySubscriptionBus is the in-process default (raising listeners are isolated and logged). ListenHandler: serves the method. Ack-first by construction (the stream subscribes before the ack is sent, so nothing published during the ack write is lost, and the ack is still the first frame), per-stream filter honoring (URIs matched via a set), every frame tagged with the listen request's JSON-RPC id under _meta["io.modelcontextprotocol/subscriptionId"], and close() initiates graceful closure (each stream flushes and ends with the stamped SubscriptionsListenResult). Bounded: max_subscriptions (rejected pre-ack past the cap) and max_buffered_events (a stream whose client stopped reading is ended at the cap; the client re-listens). MCPServer: optional subscriptions= constructor parameter (defaults to the in-memory bus) and the handler registered automatically. Context gets the bus at construction and publishes through await ctx.notify_tools_changed() / notify_prompts_changed() / notify_resources_changed() / notify_resource_updated(uri). Era-aware capabilities: get_capabilities() takes an optional protocol_version; at 2026-07-28+ the listChanged/resources.subscribe bits derive from whether subscriptions/listen is served — that is what those bits mean on a wire whose only delivery channel is the listen stream. The handshake-era derivation is byte-identical and remains the default; server/discover passes the request's version. Lowlevel Server: composable, not pre-wired — bus in your lifespan state, handlers publish to it, ListenHandler (or your own handler) passed as on_subscriptions_listen=. Docs: docs/advanced/subscriptions.md (claims proved by tests/docs_src/test_subscriptions.py) and a runnable examples/stories/subscriptions story (both transports, both server tiers). No changes to ServerSession, dispatchers, client transports, or mcp-types. Design notes This mirrors the typescript-sdk's ServerEventBus + listen-router split: the SDK owns all wire semantics (ack ordering, filtering, id stamping, teardown, limits) so a buggy or external bus cannot violate the spec's MUSTs. Delivery is fire-and-forget with no replay — a dropped stream is not resumable; clients re-listen and refetch. json_response=True does not apply to listen: a listen response is a notification stream, so it always takes the SSE path (TypeScript/Go parity). Conformance server-stateless is 30/30: the three listen MUST checks (ack-first, subscription-id tagging, filter honoring) pass, and with the capability bits now advertised the two list-changed SHOULD checks run and pass against the everything-server's new test_trigger_*_change diagnostic tools. Draft and 2026-07-28 legs both pass their baselines; the server-stateless expected-failures entries are removed.
Open Graph Description: Implements server-side subscriptions/listen (SEP-2575) for the 2026-07-28 revision. NoteThis branch was previously a much larger change covering both sides of the protocol; it has been reset and re...
X Description: Implements server-side subscriptions/listen (SEP-2575) for the 2026-07-28 revision. NoteThis branch was previously a much larger change covering both sides of the protocol; it has been reset and re...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/3035
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:1bef6bde-6eed-d4a6-9964-21faaaf713ae |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 9038:1B9505:BFE501:F71A13:6A5C1ECD |
| html-safe-nonce | 2da861c7ad5e0064b1e29e529040fdd31f97b7121755ece4d9d66195f6d02a98 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MDM4OjFCOTUwNTpCRkU1MDE6RjcxQTEzOjZBNUMxRUNEIiwidmlzaXRvcl9pZCI6IjM3NTExMTUzMjg3NjE2MzQ1MDkiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | ec243c4742bdb683b5e9c78a15852253f533d77c175ee192942ccda9a8488551 |
| hovercard-subject-tag | pull_request:3965182113 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/3035/files |
| twitter:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| og:image:alt | Implements server-side subscriptions/listen (SEP-2575) for the 2026-07-28 revision. NoteThis branch was previously a much larger change covering both sides of the protocol; it has been reset and re... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width