Skip to content

Commit 4d9d0f9

Browse files
docs: auto-open listen filter is configured ∩ server-advertised everywhere
The behavior landed in ce9251c but the changeset, migration.md, the autoOpenedSubscription getter JSDoc, the example header comment, and the e2e requirement text still said the filter is 'derived from which sub-options were set'. Sync all five to the implemented behavior (and that auto-open is skipped — autoOpenedSubscription stays undefined — when the intersection is empty). Rebuild examples-client dist.
1 parent 64fa136 commit 4d9d0f9

6 files changed

Lines changed: 14 additions & 10 deletions

File tree

.changeset/subscriptions-listen-client.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
'@modelcontextprotocol/client': minor
44
---
55

6-
`Client.listen(filter)` opens a `subscriptions/listen` stream on a 2026-07-28-era connection, resolving once the server's acknowledged notification arrives with an `McpSubscription { honoredFilter, close() }`. Change notifications delivered on the stream dispatch to the existing `setNotificationHandler` registrations — the same handlers the 2025-era unsolicited notifications fire on a legacy connection — so `listen()` is era-transparent for consumers that already register those. `close()` closes the listen request's SSE stream (Streamable HTTP) or sends `notifications/cancelled` referencing the listen id (stdio); no automatic re-listen. On a 2025-era connection `listen()` throws a typed `MethodNotSupportedByProtocolVersion` steering to `resources/subscribe` and `ClientOptions.listChanged`. `ClientOptions.listChanged` now auto-opens a listen stream on a modern connection (filter derived from which sub-options were set; the auto-opened subscription is exposed at `client.autoOpenedSubscription`). `TransportSendOptions` gains `requestSignal` for per-request abort on the Streamable HTTP transport.
6+
`Client.listen(filter)` opens a `subscriptions/listen` stream on a 2026-07-28-era connection, resolving once the server's acknowledged notification arrives with an `McpSubscription { honoredFilter, close() }`. Change notifications delivered on the stream dispatch to the existing `setNotificationHandler` registrations — the same handlers the 2025-era unsolicited notifications fire on a legacy connection — so `listen()` is era-transparent for consumers that already register those. `close()` closes the listen request's SSE stream (Streamable HTTP) or sends `notifications/cancelled` referencing the listen id (stdio); no automatic re-listen. On a 2025-era connection `listen()` throws a typed `MethodNotSupportedByProtocolVersion` steering to `resources/subscribe` and `ClientOptions.listChanged`. `ClientOptions.listChanged` now auto-opens a listen stream on a modern connection — the filter is the intersection of the configured sub-options and the server-advertised `listChanged` capabilities; auto-open is skipped (`client.autoOpenedSubscription` stays `undefined`) when that intersection is empty; otherwise the auto-opened subscription is exposed at `client.autoOpenedSubscription`. `TransportSendOptions` gains `requestSignal` for per-request abort on the Streamable HTTP transport.

docs/migration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,8 +1172,8 @@ const handler = createMcpHandler(() => buildServer());
11721172
handler.notify.toolsChanged();
11731173
```
11741174

1175-
**Client side.** `ClientOptions.listChanged` keeps working: on a 2026-07-28 connection the SDK auto-opens a `subscriptions/listen` stream with the filter derived from which sub-options were set, so the same handlers fire on every published change (the auto-opened subscription is
1176-
exposed at `client.autoOpenedSubscription` for `close()`). `client.listen(filter)` opens a stream explicitly and resolves once the server's acknowledged notification arrives with `{ honoredFilter, close() }`; change notifications dispatch to the existing `setNotificationHandler`
1175+
**Client side.** `ClientOptions.listChanged` keeps working: on a 2026-07-28 connection the SDK auto-opens a `subscriptions/listen` stream whose filter is the intersection of the configured sub-options and the server-advertised `listChanged` capabilities, so the same handlers
1176+
fire on every published change (the auto-opened subscription is exposed at `client.autoOpenedSubscription` for `close()`; when the intersection is empty auto-open is skipped and `autoOpenedSubscription` stays `undefined`). `client.listen(filter)` opens a stream explicitly and resolves once the server's acknowledged notification arrives with `{ honoredFilter, close() }`; change notifications dispatch to the existing `setNotificationHandler`
11771177
registrations. `resources/subscribe` is 2025-only — on a 2026-07-28 connection, request `notifications/resources/updated` via the `resourceSubscriptions` field of the listen filter instead.
11781178

11791179
### Multi round-trip requests (2026-07-28): write-once handlers and the client auto-fulfilment driver

examples/client/src/subscriptionsListenClient.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*
66
* 1. **auto-open via `ClientOptions.listChanged`** — the same option a
77
* 2025-era client sets; on a modern connection the SDK auto-opens a
8-
* listen stream with the filter derived from which sub-options were set,
9-
* so the configured `onChanged` handlers fire on every published change;
8+
* listen stream whose filter is the intersection of the configured
9+
* sub-options and the server-advertised `listChanged` capabilities
10+
* (auto-open is skipped when the intersection is empty), so the
11+
* configured `onChanged` handlers fire on every published change;
1012
* 2. **manual `client.listen()`** — opens a stream explicitly, registers a
1113
* `notifications/tools/list_changed` handler the stream feeds, and closes
1214
* after a few notifications.

packages/client/src/client/client.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,9 +1423,11 @@ export class Client extends Protocol<ClientContext> {
14231423

14241424
/**
14251425
* The subscription auto-opened by `ClientOptions.listChanged` on a modern
1426-
* connection (the listen filter derived from which sub-options were set),
1427-
* or `undefined` on a legacy connection or before connect. Exposed so the
1428-
* consumer can `close()` it.
1426+
* connection — the listen filter is the intersection of the configured
1427+
* sub-options and the server-advertised `listChanged` capabilities.
1428+
* `undefined` on a legacy connection, before connect, or when that
1429+
* intersection is empty (auto-open skipped). Exposed so the consumer can
1430+
* `close()` it.
14291431
*/
14301432
get autoOpenedSubscription(): McpSubscription | undefined {
14311433
return this._autoOpenedSubscription;

packages/client/test/client/listen.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ describe('Client.listen()', () => {
358358
expect((error as SdkError).code).toBe(SdkErrorCode.NotConnected);
359359
});
360360

361-
it('ClientOptions.listChanged auto-opens a listen stream on a modern connection (filter derived from sub-options)', async () => {
361+
it('ClientOptions.listChanged auto-opens a listen stream on a modern connection (filter = configured ∩ server-advertised)', async () => {
362362
const filters: unknown[] = [];
363363
const { clientTx } = await scriptedModern((_id, filter) => filters.push(filter));
364364
const onChanged = () => {};

test/e2e/requirements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2695,7 +2695,7 @@ export const REQUIREMENTS: Record<string, Requirement> = {
26952695
'typescript:subscriptions:listChanged-auto-open-modern': {
26962696
source: 'sdk',
26972697
behavior:
2698-
'ClientOptions.listChanged auto-opens a subscriptions/listen stream on a modern connection (filter derived from which sub-options were set), so the configured handlers fire on every published change. The auto-opened subscription is exposed for close.',
2698+
'ClientOptions.listChanged auto-opens a subscriptions/listen stream on a modern connection — the filter is the intersection of the configured sub-options and the server-advertised listChanged capabilities (auto-open is skipped and autoOpenedSubscription stays undefined when the intersection is empty) — so the configured handlers fire on every published change. The auto-opened subscription is exposed for close.',
26992699
addedInSpecVersion: '2026-07-28',
27002700
transports: ['entryModern'],
27012701
note: 'Hosted by the test body via createMcpHandler so it can publish via handler.notify.'

0 commit comments

Comments
 (0)