docs(servers): document resources/subscribe serving; exercise subscriptions in the resources example#2413
Conversation
…ptions in the resources example The server side of per-resource subscriptions was undocumented. resources.md gains a "Serve per-resource subscriptions" section — advertise the capability, track subscribed URIs per connection, deliver resources/updated to subscribers only — synced from an executable companion region whose harness verifies the claims (one notification after subscribe, none after unsubscribe), with a cross-reference from notifications.md. The resources example becomes the running reference: a mutable counter://value resource and an increment tool, announcing changes in-band to 2025-era subscribers and stdio connections, and on the handler's notifier for the listen streams other HTTP requests hold open. The client subscribes via subscriptions/listen (2026-07-28) or resources/subscribe (2025), asserts the notifications/resources/updated it produces, and skips the delivery assertion on per-request legacy HTTP, which has no channel to carry it.
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
…nable assertion in the client The subscriptions section now states that the pattern needs a connection outliving the subscribe call and that the stateless legacy HTTP fallback discards the tracking set with the per-request instance; the closing sentence says exactly what each example leg asserts. The client clears the race timer so the process exits promptly, and asserts the re-read against the value the increment tool returned so the story re-runs against a long-lived server.
… the notifier The era info-box described only the createMcpHandler path; on stdio the entry routes the instance's own sendResourceUpdated call onto matching listen streams, so a dual-era server must make that call on 2026-07-28 connections where the subscribe set is always empty. The prose now states both entries' behavior and names the era-or-subscribed guard the resources example uses.
There was a problem hiding this comment.
All prior review feedback has been addressed in 9739542 and 6da9627 — this is docs and examples only, so approving.
Extended reasoning...
Overview
This PR is documentation and examples only: a new "Serve per-resource subscriptions" section in docs/servers/resources.md (synced from an executable companion region), a cross-reference in docs/servers/notifications.md, and an extension of the resources example story (mutable counter://value resource, increment tool, era-split subscription client). No SDK package source code is touched.
Security risks
None. The changes do not touch auth, transports, protocol handling, or any published package code. The example server tracks subscription URIs per connection in a Set, which is the pattern the docs recommend and does not introduce cross-connection leakage.
Level of scrutiny
Low-to-moderate: prose accuracy and example correctness are the main concerns, and both were exercised in earlier review rounds. The three earlier review comments (missing serving-mode caveat, uncleared 8s timer keeping the event loop alive, hard-coded counter assertion breaking re-runs) and the follow-up on the era info-box overstating the handler.notify path on stdio were all addressed by the author in commits 9739542 and 6da9627, and the preloaded file contents confirm the fixes are present (timer cleared in a finally, re-read asserted against the tool's returned text, serving-mode caveat and dual-era guard sentence added, info-box reworded for the serveStdio delivery path).
Other factors
The example story is self-verifying and runs in CI across all four transport×era legs via run-examples.ts, and the docs companion harness asserts the section's claims (exactly one resources/updated after subscribe, none after unsubscribe). No changeset is needed since only @mcp-examples/* private packages and docs change. The bug hunting system found no issues on the latest revision.
The server side of per-resource subscriptions was undocumented:
docs/servers/resources.mdsaid nothing aboutresources/subscribe, and the SDK (by design, as in v1.x) leaves the bookkeeping to the application. This adds the missing section and turns theresourcesexample story into the running reference for it.Motivation and Context
A client-side page covers the subscribe fallback, and
notifications.mdcovers the 2026-07-28 listen/notifier path — but nothing documented what a server must do for 2025-era subscribers: advertiseresources: { subscribe: true }, route the two verbs, track subscribed URIs per connection, and sendnotifications/resources/updatedto subscribers only. Real-world interop testing against a deployed v2 server surfaced this as a gap: the pattern is easy to get subtly wrong (unsolicited updates, cross-connection leakage) and there was no reference to copy.Changes:
docs/servers/resources.md: new "Serve per-resource subscriptions" section, synced from an executable companion region; era info-box pointing the 2026-07-28 path atnotifications.md; recap bullet.examples/guides/servers/resources.examples.ts: the new region plus a harness that asserts the section's claims — exactly oneresources/updatedafter subscribe, none after unsubscribe — via a 2025-era in-memory client.examples/resources: adds a mutablecounter://valueresource and anincrementtool with era-split delivery (in-band to 2025 subscribers and stdio connections; published on the handler's notifier for the listen streams other HTTP requests hold open). The client subscribes era-split (subscriptions/listenon 2026-07-28,resources/subscribeon 2025), asserts the notification and the re-read, and skips the delivery assertion on per-request legacy HTTP, which has no channel to carry it.docs/servers/notifications.md; story and matrix README rows.How Has This Been Tested?
resourcesstory legs (stdio/http × modern/legacy) pass underscripts/examples/run-examples.ts.npx tsx guides/servers/resources.examples.ts) and self-verifies the page's claims.pnpm sync:snippets --check, examples typecheck + lint, and the docs build all pass.Breaking Changes
None.
Types of changes
Checklist
Additional context
The example intentionally demonstrates the delivery matrix per serving mode rather than hiding it: connection-oriented entries announce in-band, per-request HTTP publishes on the entry's notifier, and stateless legacy HTTP cannot deliver — the client encodes that last fact by skipping the assertion on exactly that leg.