docs(servers): document resources/subscribe serving; exercise subscriptions in the resources example (#2413)
This commit is contained in:
parent
35f6856a17
commit
ddf6cc1d73
2 changed files with 221 additions and 11 deletions
|
|
@ -87,7 +87,7 @@ On stdio, [`serveStdio`](../serving/stdio.md) routes the instance's own `send*Li
|
|||
:::
|
||||
|
||||
::: info Coming from 2025-era subscriptions
|
||||
A 2025-era connection delivers per-resource updates without a listen stream — [Subscriptions](../clients/subscriptions.md#fall-back-to-legacy-per-resource-subscribe) covers that path, and [Protocol versions](../protocol-versions.md) the era split.
|
||||
A 2025-era connection delivers per-resource updates without a listen stream — [Resources](./resources.md#serve-per-resource-subscriptions) covers the server's subscription bookkeeping, [Subscriptions](../clients/subscriptions.md#fall-back-to-legacy-per-resource-subscribe) the client call, and [Protocol versions](../protocol-versions.md) the era split.
|
||||
:::
|
||||
|
||||
## Pick an event bus for multi-process deployments
|
||||
|
|
|
|||
|
|
@ -212,6 +212,51 @@ server.sendResourceListChanged();
|
|||
|
||||
The notification tells connected clients to call `resources/list` again. A change to one resource's content is a different signal, `notifications/resources/updated` — [Notifications](./notifications.md) covers both.
|
||||
|
||||
## Serve per-resource subscriptions
|
||||
|
||||
A 2025-era client opts into `notifications/resources/updated` for one URI with `resources/subscribe`. The SDK routes the verb; the bookkeeping is yours: advertise the capability, track the URIs per connection, and send the notification to subscribers only.
|
||||
|
||||
```ts source="../../examples/guides/servers/resources.examples.ts#sendResourceUpdated_subscribers"
|
||||
let deployStatus = 'idle';
|
||||
|
||||
const deploys = new McpServer({ name: 'deploys', version: '1.0.0' }, { capabilities: { resources: { subscribe: true } } });
|
||||
|
||||
deploys.registerResource(
|
||||
'deploy-status',
|
||||
'deploys://status',
|
||||
{ description: 'The current deploy state', mimeType: 'text/plain' },
|
||||
async uri => ({ contents: [{ uri: uri.href, text: deployStatus }] })
|
||||
);
|
||||
|
||||
// The SDK routes the two verbs; which URIs this connection watches is yours to track.
|
||||
const subscribedUris = new Set<string>();
|
||||
deploys.server.setRequestHandler('resources/subscribe', request => {
|
||||
subscribedUris.add(request.params.uri);
|
||||
return {};
|
||||
});
|
||||
deploys.server.setRequestHandler('resources/unsubscribe', request => {
|
||||
subscribedUris.delete(request.params.uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
async function setDeployStatus(status: string): Promise<void> {
|
||||
deployStatus = status;
|
||||
if (subscribedUris.has('deploys://status')) {
|
||||
await deploys.server.sendResourceUpdated({ uri: 'deploys://status' });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Set` belongs to one server instance, and each connection gets its own instance from your factory — a subscription never leaks across connections. Send `resources/updated` only to connections that subscribed; unsolicited per-resource updates are wrong on 2025-era connections.
|
||||
|
||||
The pattern needs a connection that outlives the subscribe call: over stdio (and any sessionful wiring) the instance and its `Set` live as long as the connection. Behind `createMcpHandler`'s stateless legacy fallback each POST gets a fresh instance, so `resources/subscribe` succeeds and the `Set` is discarded with it — no update can ever be delivered on that posture. [Support legacy clients](../serving/legacy-clients.md) covers the serving postures.
|
||||
|
||||
::: info
|
||||
On [2026-07-28](../protocol-versions.md) connections the verb does not exist: clients name resource URIs in their `subscriptions/listen` filter, and the entry filters delivery itself — `serveStdio` routes the instance's own `sendResourceUpdated` call onto matching streams, and `createMcpHandler` delivers what you publish on its notifier ([Notifications](./notifications.md#publish-a-resource-update-through-the-handler)).
|
||||
:::
|
||||
|
||||
A dual-era server therefore still calls `sendResourceUpdated` on 2026-07-28 connections, where the subscribe set is always empty — gate on the connection's era as well as the set. The [`resources` example](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/resources) guards with `reqCtx.era === 'modern' || subscribedUris.has(uri)` in its factory and runs as a self-verifying pair: delivery is asserted over stdio on both eras and over HTTP on the 2026-07-28 listen path; the stateless legacy HTTP leg asserts only that the subscribe calls succeed.
|
||||
|
||||
## Recap
|
||||
|
||||
- `registerResource(name, uri, config, readCallback)` registers a resource at a fixed URI.
|
||||
|
|
@ -220,3 +265,4 @@ The notification tells connected clients to call `resources/list` again. A chang
|
|||
- A template's `list` callback is what makes its instances appear in `resources/list`.
|
||||
- Resolve file-backed paths to their real location and reject anything outside the root before reading.
|
||||
- Registration changes emit `notifications/resources/list_changed` automatically.
|
||||
- `resources/subscribe` bookkeeping is the server's: advertise `resources: { subscribe: true }`, track URIs per connection, send `resources/updated` to subscribers only.
|
||||
|
|
|
|||
Loading…
Reference in a new issue