Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions docs/specification/draft/basic/patterns/subscriptions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 30 additions & 4 deletions docs/specification/draft/schema.mdx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"jsonrpc": "2.0",
"method": "notifications/prompts/list_changed"
"method": "notifications/prompts/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed"
"method": "notifications/resources/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
},
"uri": "file:///project/src/main.rs"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"resultType": "complete",
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
"method": "notifications/tools/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
}
}
}
33 changes: 33 additions & 0 deletions schema/draft/schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions schema/draft/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,40 @@ export interface SubscriptionsListenRequest extends JSONRPCRequest {
params: SubscriptionsListenRequestParams;
}

/**
* Extends {@link MetaObject} with the subscription-stream identifier carried by a
* {@link SubscriptionsListenResult}. All key naming rules from `MetaObject` apply.
*
* @see {@link MetaObject} for key naming rules and reserved prefixes.
* @category `subscriptions/listen`
*/
export interface SubscriptionsListenResultMeta extends MetaObject {
/**
* 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`).
*/
"io.modelcontextprotocol/subscriptionId": RequestId;
}

/**
* The response to a {@link SubscriptionsListenRequest | 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.
*
* @example Subscription closed gracefully
* {@includeCode ./examples/SubscriptionsListenResult/listen-closed.json}
*
* @category `subscriptions/listen`
*/
export interface SubscriptionsListenResult extends Result {
_meta: SubscriptionsListenResultMeta;
}

/**
* Parameters for a {@link SubscriptionsAcknowledgedNotification | notifications/subscriptions/acknowledged} notification.
*
Expand Down Expand Up @@ -3134,6 +3168,7 @@ export type ServerResult =
| ListResourceTemplatesResult
| ListResourcesResult
| ReadResourceResult
| SubscriptionsListenResult
| CallToolResult
| ListToolsResult
| InputRequiredResult;
Loading