From 4b358ef5ea0d450d808143879c6743bd9ef74ccb Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:53:46 -0600 Subject: [PATCH 1/4] feat(schema): add subscriptions/listen response subscriptions/listen was the only request without a response object. Add an empty SubscriptionsListenResult, sent by the server to signal a graceful end of the subscription (e.g. during shutdown), distinct from an abrupt transport drop which carries no response. Like other stream messages it carries the subscriptionId in _meta. Also add the subscriptionId to the stream notification examples that were missing it, matching the spec requirement that all stream messages carry it. --- .../draft/basic/patterns/subscriptions.mdx | 37 +++++++++++++++++-- docs/specification/draft/schema.mdx | 34 +++++++++++++++-- .../prompts-list-changed.json | 7 +++- .../resources-list-changed.json | 7 +++- .../file-resource-updated-notification.json | 3 ++ .../listen-closed.json | 6 +++ .../tools-list-changed.json | 7 +++- schema/draft/schema.json | 29 +++++++++++++++ schema/draft/schema.ts | 35 ++++++++++++++++++ 9 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 schema/draft/examples/SubscriptionsListenResult/listen-closed.json diff --git a/docs/specification/draft/basic/patterns/subscriptions.mdx b/docs/specification/draft/basic/patterns/subscriptions.mdx index b85db7072..92ebb0e02 100644 --- a/docs/specification/draft/basic/patterns/subscriptions.mdx +++ b/docs/specification/draft/basic/patterns/subscriptions.mdx @@ -114,12 +114,43 @@ A subscription ends when: - The **client** cancels it — close the SSE stream (HTTP) or send `notifications/cancelled` referencing the `subscriptions/listen` request ID (stdio). -- The **server** tears it down (e.g., during shutdown) — it **MUST** close the SSE - stream (HTTP) or send `notifications/cancelled` referencing the - `subscriptions/listen` request ID (stdio). +- The **server** tears it down (e.g., during shutdown) — it **SHOULD** send the + empty `subscriptions/listen` response to signal a graceful end (see + [Graceful Closure](#graceful-closure)), then close the stream. - The underlying transport closes (HTTP timeout, TCP disconnect, stdio process exit). +### Graceful Closure + +When the server ends a subscription on its own initiative (for example, during +shutdown), it **SHOULD** respond to the original `subscriptions/listen` request +with an empty result before closing the stream. This is the JSON-RPC response to +the long-lived request, correlated by its `id`, and signals that the subscription +ended gracefully — as opposed to an abrupt transport drop, which carries no +response. + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "resultType": "complete", + "_meta": { + "io.modelcontextprotocol/subscriptionId": 1 + } + } +} +``` + +Like every other message on the stream, the response carries +`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying which +subscription it closes. The value matches the JSON-RPC `id` of the originating +`subscriptions/listen` request. + +A client that receives this response knows the subscription closed cleanly; a +transport that closes without it indicates an unexpected disconnect, which the +client **MAY** treat as a trigger to reconnect. + On **stdio**, if the connection is terminated and then re-established, the client **MUST** re-send `subscriptions/listen` to re-establish its subscriptions — the server holds no subscription state across reconnections. diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx index cd11cd51b..bec7094d0 100644 --- a/docs/specification/draft/schema.mdx +++ b/docs/specification/draft/schema.mdx @@ -699,7 +699,7 @@ deprecated features registry.
An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the promptsListChanged filter field.
An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the resourcesListChanged filter field.
An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the resourcesListChanged filter field.
A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the resourceSubscriptions field of a subscriptions/listen request.
A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the resourceSubscriptions field of a subscriptions/listen request.
An optional notification from the server to the client, informing it that the list of tools it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the toolsListChanged filter field.
An optional notification from the server to the client, informing it that the list of tools it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the toolsListChanged filter field.
The response to a subscriptions/listen +request, signalling that the subscription has ended gracefully (for example, +during server shutdown). Because the listen stream is long-lived, this result +is sent only when the server tears the subscription down; an abrupt transport +close carries no response. The result body is otherwise empty.
Indicates the type of the result, which allows the client to determine +how to parse the result object.
Servers implementing this protocol version MUST include this field.
+For backward compatibility, when a client receives a result from a
+server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
resources/subscribe RPC. Extends MetaObject with the subscription-stream identifier carried by a SubscriptionsListenResult. All key naming rules from MetaObject apply.
MetaObject for key naming rules and reserved prefixes.
Identifies the subscription stream this response closes, so the client can
+correlate it with the originating subscription — mirroring the same key on
+the stream's notifications. The value is the JSON-RPC ID of the subscriptions/listen request that opened the stream (and equals this
+response's id).
The response to a subscriptions/listen +
The response to a subscriptions/listen request, signalling that the subscription has ended gracefully (for example, during server shutdown). Because the listen stream is long-lived, this result is sent only when the server tears the subscription down; an abrupt transport close carries no response. The result body is otherwise empty.
Indicates the type of the result, which allows the client to determine how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
-server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
resultType), the client MUST treat the absent field as "complete". resources/subscribe RPC.
### `SubscriptionsListenResultMeta`
-Extends MetaObject with the subscription-stream identifier carried by a SubscriptionsListenResult. All key naming rules from MetaObject apply.
MetaObject for key naming rules and reserved prefixes.
Identifies the subscription stream this response closes, so the client can +
Extends MetaObject with the subscription-stream identifier carried by a SubscriptionsListenResult. All key naming rules from MetaObject apply.
MetaObject for key naming rules and reserved prefixes.
Identifies the subscription stream this response closes, so the client can
correlate it with the originating subscription — mirroring the same key on
the stream's notifications. The value is the JSON-RPC ID of the subscriptions/listen request that opened the stream (and equals this
response's id).
An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the
promptsListChangedfilter field.