From d4854bce6071dd7f8b2fd1e31ead5c7038d82df2 Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Sun, 7 Jun 2026 13:57:24 +0100
Subject: [PATCH 1/6] Align draft schema with spec docs
- Declare the reserved io.modelcontextprotocol/subscriptionId _meta key
via a new NotificationMetaObject type, including the rule for deriving
the value from the subscriptions/listen request's JSON-RPC ID
- Rewrite the three list_changed notification doc comments to reflect
the opt-in subscriptions model instead of unsolicited delivery
- Add the HEADER_MISMATCH (-32001) error code and HeaderMismatchError
type required by the Streamable HTTP transport's header validation
- Update cacheScope JSDoc to the authorization-context caching model
used by the caching utility doc
- Make CancelledNotificationParams.requestId required
- Describe cancellation as client-initiated, with one server-side use:
on stdio a server sends notifications/cancelled solely to terminate a
subscriptions/listen stream
- Give ListRootsRequest a minimal params shape instead of RequestParams,
matching other server-initiated input requests
- Remove ProgressNotification from ClientNotification: only clients
issue requests, so only servers report progress
Regenerated schema.json and schema.mdx.
---
docs/specification/draft/schema.mdx | 121 +++++++++++++++++++---------
schema/draft/schema.json | 118 +++++++++++++++++++++------
schema/draft/schema.ts | 81 +++++++++++++++----
3 files changed, 242 insertions(+), 78 deletions(-)
diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx
index d9ed12511..4d0891243 100644
--- a/docs/specification/draft/schema.mdx
+++ b/docs/specification/draft/schema.mdx
@@ -148,11 +148,23 @@ deprecated features registry.
+
+
+### `NotificationMetaObject`
+
+
"io.modelcontextprotocol/subscriptionId"?: string
+
+
+
### `NotificationParams`
-
+
@@ -246,6 +258,27 @@ input_required - the request requires additional input and the result conta
+
+
+### `HEADER_MISMATCH`
+
+
HEADER_MISMATCH : -32001
+
+
+
+
+
+### `HeaderMismatchError`
+
+
+
+
+
### `InternalError`
@@ -604,7 +637,7 @@ without nested objects or arrays.
### `CancelledNotification`
-method: "notifications/cancelled" params: CancelledNotificationParams
+method: "notifications/cancelled" params: CancelledNotificationParams
@@ -612,7 +645,7 @@ without nested objects or arrays.
### `CancelledNotificationParams`
-
+
@@ -633,9 +666,9 @@ deprecated features registry. interface LoggingMessageNotificationParams { _meta ?: MetaObject ; level : LoggingLevel ; logger ?: string ; data : unknown ; }
@@ -654,7 +687,7 @@ deprecated features registry. interface ProgressNotificationParams { _meta ?: MetaObject ; progressToken : ProgressToken ; progress : number ; total ?: number ; message ?: string ; } progressToken: ProgressToken
+progressToken: ProgressToken
@@ -665,7 +698,7 @@ deprecated features registry. interface PromptListChangedNotification { jsonrpc : "2.0" ; method : "notifications/prompts/list_changed" ; params ?: NotificationParams ; } method: "notifications/prompts/list_changed" params?: NotificationParams
+method: "notifications/prompts/list_changed" params?: NotificationParams
@@ -676,7 +709,7 @@ deprecated features registry. interface ResourceListChangedNotification { jsonrpc : "2.0" ; method : "notifications/resources/list_changed" ; params ?: NotificationParams ; } method: "notifications/resources/list_changed" params?: NotificationParams
+method: "notifications/resources/list_changed" params?: NotificationParams
@@ -695,7 +728,7 @@ deprecated features registry. interface ResourceUpdatedNotificationParams { _meta ?: MetaObject ; uri : string ; }
+
@@ -716,7 +749,7 @@ types it agreed to honor. notifications: SubscriptionFilter
### `ToolListChangedNotification`
-
+
@@ -749,7 +782,7 @@ the server has no prompts), it is omitted from this set.
### `ElicitationCompleteNotificationParams`
-
+
@@ -865,10 +898,12 @@ If present, there may be more results available. cacheScope: "public" | "private"
+milliseconds after receiving the response. cacheScope: "public" | "private"
@@ -925,10 +960,12 @@ If present, there may be more results available. cacheScope: "public" | "private"
+milliseconds after receiving the response. cacheScope: "public" | "private"
@@ -981,10 +1018,12 @@ server implementing an earlier protocol version (which does not include re
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age. If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed. If positive, the client SHOULD consider the result fresh for this many
-milliseconds after receiving the response. cacheScope: "public" | "private" contents: (TextResourceContents | BlobResourceContents)[]
+milliseconds after receiving the response. cacheScope: "public" | "private" contents: (TextResourceContents | BlobResourceContents)[]
@@ -1019,10 +1058,12 @@ If present, there may be more results available. cacheScope: "public" | "private" resourceTemplates: ResourceTemplate[]
+milliseconds after receiving the response. cacheScope: "public" | "private" resourceTemplates: ResourceTemplate[]
@@ -1044,13 +1085,13 @@ if present). Inherited from BaseMetadata
### `ListRootsRequest`
-
params?: { _meta?: MetaObject }
@@ -1248,10 +1289,12 @@ and should not duplicate information already in tool descriptions. If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed. If positive, the client SHOULD consider the result fresh for this many
-milliseconds after receiving the response. cacheScope: "public" | "private"
+milliseconds after receiving the response. cacheScope: "public" | "private"
@@ -1404,10 +1447,12 @@ If present, there may be more results available.
+milliseconds after receiving the response.
diff --git a/schema/draft/schema.json b/schema/draft/schema.json
index 5ce9df12e..9faf1e814 100644
--- a/schema/draft/schema.json
+++ b/schema/draft/schema.json
@@ -126,7 +126,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -264,7 +264,7 @@
"type": "object"
},
"CancelledNotification": {
- "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.",
+ "description": "This notification is sent by the client to indicate that it is cancelling a request it previously issued.\n\nOn stdio, the server also sends this notification, solely to terminate a {@link SubscriptionsListenRequestsubscriptions/listen} stream: it references the ID of the `subscriptions/listen` request that opened the stream. Servers MUST NOT use this notification to cancel any other request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.",
"properties": {
"jsonrpc": {
"const": "2.0",
@@ -289,7 +289,7 @@
"description": "Parameters for a `notifications/cancelled` notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"reason": {
"description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.",
@@ -297,9 +297,12 @@
},
"requestId": {
"$ref": "#/$defs/RequestId",
- "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction."
+ "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request the client previously issued."
}
},
+ "required": [
+ "requestId"
+ ],
"type": "object"
},
"ClientCapabilities": {
@@ -354,14 +357,26 @@
"type": "object"
},
"ClientNotification": {
- "anyOf": [
- {
- "$ref": "#/$defs/CancelledNotification"
+ "description": "This notification is sent by the client to indicate that it is cancelling a request it previously issued.\n\nOn stdio, the server also sends this notification, solely to terminate a {@link SubscriptionsListenRequestsubscriptions/listen} stream: it references the ID of the `subscriptions/listen` request that opened the stream. Servers MUST NOT use this notification to cancel any other request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.",
+ "properties": {
+ "jsonrpc": {
+ "const": "2.0",
+ "type": "string"
},
- {
- "$ref": "#/$defs/ProgressNotification"
+ "method": {
+ "const": "notifications/cancelled",
+ "type": "string"
+ },
+ "params": {
+ "$ref": "#/$defs/CancelledNotificationParams"
}
- ]
+ },
+ "required": [
+ "jsonrpc",
+ "method",
+ "params"
+ ],
+ "type": "object"
},
"ClientRequest": {
"anyOf": [
@@ -728,7 +743,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -966,7 +981,7 @@
"description": "Parameters for a {@link ElicitationCompleteNotificationnotifications/elicitation/complete} notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"elicitationId": {
"description": "The ID of the elicitation that completed.",
@@ -1163,6 +1178,42 @@
],
"type": "object"
},
+ "HeaderMismatchError": {
+ "description": "Returned when a server rejects a request because the values in the HTTP\nheaders do not match the corresponding values in the request body, or\nbecause required headers are missing or malformed. For HTTP, the response\nstatus code MUST be `400 Bad Request`.",
+ "properties": {
+ "error": {
+ "allOf": [
+ {
+ "$ref": "#/$defs/Error"
+ },
+ {
+ "properties": {
+ "code": {
+ "const": -32001,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "code"
+ ],
+ "type": "object"
+ }
+ ]
+ },
+ "id": {
+ "$ref": "#/$defs/RequestId"
+ },
+ "jsonrpc": {
+ "const": "2.0",
+ "type": "string"
+ }
+ },
+ "required": [
+ "error",
+ "jsonrpc"
+ ],
+ "type": "object"
+ },
"Icon": {
"description": "An optionally-sized icon that can be displayed in a user interface.",
"properties": {
@@ -1639,7 +1690,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -1728,7 +1779,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -1817,7 +1868,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -1881,7 +1932,12 @@
"type": "string"
},
"params": {
- "$ref": "#/$defs/RequestParams"
+ "properties": {
+ "_meta": {
+ "$ref": "#/$defs/MetaObject"
+ }
+ },
+ "type": "object"
}
},
"required": [
@@ -1937,7 +1993,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -2033,7 +2089,7 @@
"description": "Parameters for a `notifications/message` notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"data": {
"description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here."
@@ -2194,11 +2250,21 @@
],
"type": "object"
},
+ "NotificationMetaObject": {
+ "description": "Extends {@link MetaObject} with additional notification-specific fields. All key naming rules from `MetaObject` apply.",
+ "properties": {
+ "io.modelcontextprotocol/subscriptionId": {
+ "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\n\nThe value is derived from the JSON-RPC ID of the `subscriptions/listen`\nrequest that opened the stream: the decimal string representation of the\nID if it is a number, or the ID verbatim if it is a string.",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
"NotificationParams": {
"description": "Common params for any notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
}
},
"type": "object"
@@ -2369,7 +2435,7 @@
"description": "Parameters for a {@link ProgressNotificationnotifications/progress} notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"message": {
"description": "An optional message describing the current progress.",
@@ -2465,7 +2531,7 @@
"type": "object"
},
"PromptListChangedNotification": {
- "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.",
+ "description": "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 {@link SubscriptionsListenRequestsubscriptions/listen} stream when the client requested it via the `promptsListChanged` filter field.",
"properties": {
"jsonrpc": {
"const": "2.0",
@@ -2580,7 +2646,7 @@
"$ref": "#/$defs/MetaObject"
},
"cacheScope": {
- "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.",
+ "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: The response does not contain user-specific data. Any\n client or intermediary (e.g., shared gateway, caching proxy) MAY cache\n the response and serve it across authorization contexts.\n- `\"private\"`: The response MAY be cached and reused only within the\n same authorization context. Caches MUST NOT be shared across\n authorization contexts (e.g., a different access token requires a\n different cache).",
"enum": [
"private",
"public"
@@ -2836,7 +2902,7 @@
"type": "object"
},
"ResourceListChangedNotification": {
- "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.",
+ "description": "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 {@link SubscriptionsListenRequestsubscriptions/listen} stream when the client requested it via the `resourcesListChanged` filter field.",
"properties": {
"jsonrpc": {
"const": "2.0",
@@ -2964,7 +3030,7 @@
"description": "Parameters for a `notifications/resources/updated` notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"uri": {
"description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.",
@@ -3314,7 +3380,7 @@
"description": "Parameters for a {@link SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged} notification.",
"properties": {
"_meta": {
- "$ref": "#/$defs/MetaObject"
+ "$ref": "#/$defs/NotificationMetaObject"
},
"notifications": {
"$ref": "#/$defs/SubscriptionFilter",
@@ -3638,7 +3704,7 @@
"type": "object"
},
"ToolListChangedNotification": {
- "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.",
+ "description": "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 {@link SubscriptionsListenRequestsubscriptions/listen} stream when the client requested it via the `toolsListChanged` filter field.",
"properties": {
"jsonrpc": {
"const": "2.0",
diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts
index f99c52953..4255265bf 100644
--- a/schema/draft/schema.ts
+++ b/schema/draft/schema.ts
@@ -110,6 +110,27 @@ export interface RequestMetaObject extends MetaObject {
"io.modelcontextprotocol/logLevel"?: LoggingLevel;
}
+/**
+ * Extends {@link MetaObject} with additional notification-specific fields. All key naming rules from `MetaObject` apply.
+ *
+ * @see {@link MetaObject} for key naming rules and reserved prefixes.
+ * @see [General fields: `_meta`](/specification/draft/basic/index#meta) for more details.
+ * @category Common Types
+ */
+export interface NotificationMetaObject extends MetaObject {
+ /**
+ * Identifies the subscription stream a notification was delivered on. The
+ * server MUST include this key on every notification delivered via a
+ * {@link SubscriptionsListenRequest | subscriptions/listen} stream, so the
+ * client can correlate the notification with the originating subscription.
+ *
+ * The value is derived from the JSON-RPC ID of the `subscriptions/listen`
+ * request that opened the stream: the decimal string representation of the
+ * ID if it is a number, or the ID verbatim if it is a string.
+ */
+ "io.modelcontextprotocol/subscriptionId"?: string;
+}
+
/**
* A progress token, used to associate progress notifications with the original request.
*
@@ -147,7 +168,7 @@ export interface Request {
* @category Common Types
*/
export interface NotificationParams {
- _meta?: MetaObject;
+ _meta?: NotificationMetaObject;
}
/** @internal */
@@ -357,6 +378,15 @@ export interface InternalError extends Error {
code: typeof INTERNAL_ERROR;
}
+/**
+ * Error code returned when the HTTP headers of a request do not match the
+ * corresponding values in the request body, or required headers are
+ * missing or malformed.
+ *
+ * @category Errors
+ */
+export const HEADER_MISMATCH = -32001;
+
/**
* Error code returned when a server requires a client capability that was
* not declared in the request's `clientCapabilities`.
@@ -373,6 +403,23 @@ export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003;
*/
export const UNSUPPORTED_PROTOCOL_VERSION = -32004;
+/**
+ * Returned when a server rejects a request because the values in the HTTP
+ * headers do not match the corresponding values in the request body, or
+ * because required headers are missing or malformed. For HTTP, the response
+ * status code MUST be `400 Bad Request`.
+ *
+ * @category Errors
+ */
+export interface HeaderMismatchError extends Omit<
+ JSONRPCErrorResponse,
+ "error"
+> {
+ error: Error & {
+ code: typeof HEADER_MISMATCH;
+ };
+}
+
/**
* Returned when the request's protocol version is unknown to the server or
* unsupported (e.g., a known experimental or draft version the server has
@@ -529,9 +576,9 @@ export interface CancelledNotificationParams extends NotificationParams {
/**
* The ID of the request to cancel.
*
- * This MUST correspond to the ID of a request previously issued in the same direction.
+ * This MUST correspond to the ID of a request the client previously issued.
*/
- requestId?: RequestId;
+ requestId: RequestId;
/**
* An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.
@@ -540,7 +587,9 @@ export interface CancelledNotificationParams extends NotificationParams {
}
/**
- * This notification can be sent by either side to indicate that it is cancelling a previously-issued request.
+ * This notification is sent by the client to indicate that it is cancelling a request it previously issued.
+ *
+ * On stdio, the server also sends this notification, solely to terminate a {@link SubscriptionsListenRequest | subscriptions/listen} stream: it references the ID of the `subscriptions/listen` request that opened the stream. Servers MUST NOT use this notification to cancel any other request.
*
* The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.
*
@@ -1007,11 +1056,13 @@ export interface CacheableResult extends Result {
* Indicates the intended scope of the cached response, analogous to HTTP
* `Cache-Control: public` vs `Cache-Control: private`.
*
- * - `"public"`: Any client or intermediary (e.g., shared gateway, proxy)
- * MAY cache the response and serve it to any user.
- * - `"private"`: Only the requesting user's client MAY cache the response.
- * Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached
- * copy to a different user.
+ * - `"public"`: The response does not contain user-specific data. Any
+ * client or intermediary (e.g., shared gateway, caching proxy) MAY cache
+ * the response and serve it across authorization contexts.
+ * - `"private"`: The response MAY be cached and reused only within the
+ * same authorization context. Caches MUST NOT be shared across
+ * authorization contexts (e.g., a different access token requires a
+ * different cache).
*
*/
cacheScope: "public" | "private";
@@ -1154,7 +1205,7 @@ export interface ReadResourceResultResponse extends JSONRPCResultResponse {
}
/**
- * An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.
+ * 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 {@link SubscriptionsListenRequest | subscriptions/listen} stream when the client requested it via the `resourcesListChanged` filter field.
*
* @example Resources list changed
* {@includeCode ./examples/ResourceListChangedNotification/resources-list-changed.json}
@@ -1598,7 +1649,7 @@ export interface EmbeddedResource {
_meta?: MetaObject;
}
/**
- * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.
+ * 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 {@link SubscriptionsListenRequest | subscriptions/listen} stream when the client requested it via the `promptsListChanged` filter field.
*
* @example Prompts list changed
* {@includeCode ./examples/PromptListChangedNotification/prompts-list-changed.json}
@@ -1740,7 +1791,7 @@ export interface CallToolRequest extends JSONRPCRequest {
}
/**
- * An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.
+ * 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 {@link SubscriptionsListenRequest | subscriptions/listen} stream when the client requested it via the `toolsListChanged` filter field.
*
* @example Tools list changed
* {@includeCode ./examples/ToolListChangedNotification/tools-list-changed.json}
@@ -2571,7 +2622,9 @@ export interface PromptReference extends BaseMetadata {
*/
export interface ListRootsRequest {
method: "roots/list";
- params?: RequestParams;
+ params?: {
+ _meta?: MetaObject;
+ };
}
/**
@@ -3054,7 +3107,7 @@ export type ClientRequest =
| ListToolsRequest;
/** @internal */
-export type ClientNotification = CancelledNotification | ProgressNotification;
+export type ClientNotification = CancelledNotification;
/** @internal */
export type ClientResult = EmptyResult;
From be09b82ee4ae60e4f4498f2ad1510518d1ba7d3f Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Sun, 7 Jun 2026 13:57:24 +0100
Subject: [PATCH 2/6] Fix direction language and subscription ID rule in
pattern docs
- Cancellation: describe cancellation as client-to-server, with the
server-side exception for subscriptions/listen stream teardown on
stdio
- Progress: describe progress notifications as server-to-client only,
and use Client/Server in the sequence diagram
- Subscriptions: state how io.modelcontextprotocol/subscriptionId is
derived from the subscriptions/listen request's JSON-RPC ID (decimal
string for numeric IDs, verbatim for string IDs)
---
.../draft/basic/patterns/cancellation.mdx | 26 +++++++++-----
.../draft/basic/patterns/progress.mdx | 35 ++++++++++---------
.../draft/basic/patterns/subscriptions.mdx | 12 ++++---
3 files changed, 44 insertions(+), 29 deletions(-)
diff --git a/docs/specification/draft/basic/patterns/cancellation.mdx b/docs/specification/draft/basic/patterns/cancellation.mdx
index c1ee57cf2..a4ae7a6fe 100644
--- a/docs/specification/draft/basic/patterns/cancellation.mdx
+++ b/docs/specification/draft/basic/patterns/cancellation.mdx
@@ -5,12 +5,18 @@ title: Cancellation
The Model Context Protocol (MCP) supports optional cancellation of in-progress requests
-through notification messages. Either side can send a cancellation notification to
-indicate that a previously-issued request should be terminated.
+through notification messages. Because all requests flow from client to server,
+cancellation flows in one direction as well: the client sends a cancellation notification
+to indicate that a request it previously issued should be terminated.
+
+There is one exception: on stdio, a server **MUST** send `notifications/cancelled`
+referencing a `subscriptions/listen` request ID when it tears down that subscription
+stream (see [Subscriptions][subscriptions]). Servers **MUST NOT** send
+`notifications/cancelled` for any other purpose.
## Cancellation Flow
-When a party wants to cancel an in-progress request, it sends a `notifications/cancelled`
+When a client wants to cancel an in-progress request, it sends a `notifications/cancelled`
notification containing:
- The ID of the request to cancel
@@ -40,18 +46,20 @@ How a client signals cancellation depends on the transport:
## Behavior Requirements
1. Cancellation notifications **MUST** only reference requests that:
- - Were previously issued in the same direction
+ - Were previously issued by the client
- Are believed to still be in-progress
-1. Receivers of cancellation notifications **SHOULD**:
+1. Server-sent cancellation notifications **MUST** only reference a
+ `subscriptions/listen` request, to terminate that subscription stream
+1. Servers receiving cancellation notifications **SHOULD**:
- Stop processing the cancelled request
- Free associated resources
- Not send a response for the cancelled request
-1. Receivers **MAY** ignore cancellation notifications if:
+1. Servers **MAY** ignore cancellation notifications if:
- The referenced request is unknown
- Processing has already completed
- The request cannot be cancelled
-1. The sender of the cancellation notification **SHOULD** ignore any response to the
- request that arrives afterward
+1. The client **SHOULD** ignore any response to the cancelled request that arrives
+ afterward
## Timing Considerations
@@ -90,3 +98,5 @@ Invalid cancellation notifications **SHOULD** be ignored:
This maintains the "fire and forget" nature of notifications while allowing for race
conditions in asynchronous communication.
+
+[subscriptions]: /specification/draft/basic/patterns/subscriptions
diff --git a/docs/specification/draft/basic/patterns/progress.mdx b/docs/specification/draft/basic/patterns/progress.mdx
index e35f0b177..a52df885b 100644
--- a/docs/specification/draft/basic/patterns/progress.mdx
+++ b/docs/specification/draft/basic/patterns/progress.mdx
@@ -5,16 +5,17 @@ title: Progress
The Model Context Protocol (MCP) supports optional progress tracking for long-running
-operations through notification messages. Either side can send progress notifications to
-provide updates about operation status.
+operations through notification messages. Because all requests flow from client to
+server, progress flows in one direction as well: the server sends progress notifications
+to report the status of requests the client has issued.
## Progress Flow
-When a party wants to _receive_ progress updates for a request, it includes a
+When a client wants to _receive_ progress updates for a request, it includes a
`progressToken` in the request metadata.
- Progress tokens **MUST** be a string or integer value
-- Progress tokens can be chosen by the sender using any means, but **MUST** be unique
+- Progress tokens can be chosen by the client using any means, but **MUST** be unique
across all active requests.
```json
@@ -30,7 +31,7 @@ When a party wants to _receive_ progress updates for a request, it includes a
}
```
-The receiver **MAY** then send progress notifications containing:
+The server **MAY** then send progress notifications containing:
- The original progress token
- The current progress value so far
@@ -61,30 +62,30 @@ The receiver **MAY** then send progress notifications containing:
- Were provided in an active request
- Are associated with an in-progress operation
-2. Receivers of progress requests **MAY**:
+2. Servers receiving a request with a progress token **MAY**:
- Choose not to send any progress notifications
- Send notifications at whatever frequency they deem appropriate
- Omit the total value if unknown
```mermaid
sequenceDiagram
- participant Sender
- participant Receiver
+ participant Client
+ participant Server
- Note over Sender,Receiver: Request with progress token
- Sender->>Receiver: Method request with progressToken
+ Note over Client,Server: Request with progress token
+ Client->>Server: Method request with progressToken
- Note over Sender,Receiver: Progress updates
- Receiver-->>Sender: Progress notification (0.2/1.0)
- Receiver-->>Sender: Progress notification (0.6/1.0)
- Receiver-->>Sender: Progress notification (1.0/1.0)
+ Note over Client,Server: Progress updates
+ Server-->>Client: Progress notification (0.2/1.0)
+ Server-->>Client: Progress notification (0.6/1.0)
+ Server-->>Client: Progress notification (1.0/1.0)
- Note over Sender,Receiver: Operation complete
- Receiver->>Sender: Method response
+ Note over Client,Server: Operation complete
+ Server->>Client: Method response
```
## Implementation Notes
-- Senders and receivers **SHOULD** track active progress tokens
+- Clients and servers **SHOULD** track active progress tokens
- Both parties **SHOULD** implement rate limiting to prevent flooding
- Progress notifications **MUST** stop after completion
diff --git a/docs/specification/draft/basic/patterns/subscriptions.mdx b/docs/specification/draft/basic/patterns/subscriptions.mdx
index e2a05bdce..1b97088dc 100644
--- a/docs/specification/draft/basic/patterns/subscriptions.mdx
+++ b/docs/specification/draft/basic/patterns/subscriptions.mdx
@@ -78,8 +78,12 @@ any unsupported types gracefully.
## Receiving Notifications
All notifications delivered on the stream carry
-`io.modelcontextprotocol/subscriptionId` in `_meta`, matching the ID of the
-`subscriptions/listen` request that opened the stream. On stdio, where all messages
+`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying the
+`subscriptions/listen` request that opened the stream. The value is always a string,
+derived from the JSON-RPC ID of the `subscriptions/listen` request: the decimal string
+representation of the ID if it is a number, or the ID verbatim if it is a string. In
+the examples above, the request used `"id": 1`, so the acknowledgment and all
+subsequent notifications carry the subscription ID `"1"`. On stdio, where all messages
share a single channel, clients **MUST** use this field to correlate notifications
with their originating subscription.
@@ -102,8 +106,8 @@ A client **MAY** have multiple active subscriptions concurrently — for example
one listening for tools-list changes and another for resource updates. Each
subscription is identified by the JSON-RPC request ID of its
`subscriptions/listen` request, and every notification on the stream carries
-that ID in `io.modelcontextprotocol/subscriptionId` so clients can demultiplex
-them.
+that ID (converted to a string as described above) in
+`io.modelcontextprotocol/subscriptionId` so clients can demultiplex them.
## Cancellation
From dfb90ad3e3e3e8963ea6ec43e871bfe37fb3a971 Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Tue, 9 Jun 2026 14:12:48 +0100
Subject: [PATCH 3/6] Address review feedback on cancellation wording,
subscriptionId docs, and error example
- Make the server-to-client direction of the stdio cancellation exception
explicit in cancellation.mdx
- Document why io.modelcontextprotocol/subscriptionId is optional on
NotificationMetaObject (the type covers all notifications, not just
subscription-stream deliveries)
- Add a HeaderMismatchError example so -32001 renders in the schema
Error section
---
docs/specification/draft/basic/patterns/cancellation.mdx | 3 ++-
docs/specification/draft/schema.mdx | 7 +++++--
.../examples/HeaderMismatchError/header-mismatch.json | 8 ++++++++
schema/draft/schema.json | 2 +-
schema/draft/schema.ts | 6 ++++++
5 files changed, 22 insertions(+), 4 deletions(-)
create mode 100644 schema/draft/examples/HeaderMismatchError/header-mismatch.json
diff --git a/docs/specification/draft/basic/patterns/cancellation.mdx b/docs/specification/draft/basic/patterns/cancellation.mdx
index a4ae7a6fe..2ac3f35e7 100644
--- a/docs/specification/draft/basic/patterns/cancellation.mdx
+++ b/docs/specification/draft/basic/patterns/cancellation.mdx
@@ -9,7 +9,8 @@ through notification messages. Because all requests flow from client to server,
cancellation flows in one direction as well: the client sends a cancellation notification
to indicate that a request it previously issued should be terminated.
-There is one exception: on stdio, a server **MUST** send `notifications/cancelled`
+There is one exception, where cancellations are sent from server to client: on stdio, a
+server **MUST** send `notifications/cancelled`
referencing a `subscriptions/listen` request ID when it tears down that subscription
stream (see [Subscriptions][subscriptions]). Servers **MUST NOT** send
`notifications/cancelled` for any other purpose.
diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx
index 4d0891243..320f21ad8 100644
--- a/docs/specification/draft/schema.mdx
+++ b/docs/specification/draft/schema.mdx
@@ -154,7 +154,10 @@ deprecated features registry.
"io.modelcontextprotocol/subscriptionId"?: string
@@ -275,7 +278,7 @@ missing or malformed.
+status code MUST be 400 Bad Request.
diff --git a/schema/draft/examples/HeaderMismatchError/header-mismatch.json b/schema/draft/examples/HeaderMismatchError/header-mismatch.json
new file mode 100644
index 000000000..a0f2e569c
--- /dev/null
+++ b/schema/draft/examples/HeaderMismatchError/header-mismatch.json
@@ -0,0 +1,8 @@
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "error": {
+ "code": -32001,
+ "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
+ }
+}
diff --git a/schema/draft/schema.json b/schema/draft/schema.json
index 9faf1e814..efdc13ff0 100644
--- a/schema/draft/schema.json
+++ b/schema/draft/schema.json
@@ -2254,7 +2254,7 @@
"description": "Extends {@link MetaObject} with additional notification-specific fields. All key naming rules from `MetaObject` apply.",
"properties": {
"io.modelcontextprotocol/subscriptionId": {
- "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\n\nThe value is derived from the JSON-RPC ID of the `subscriptions/listen`\nrequest that opened the stream: the decimal string representation of the\nID if it is a number, or the ID verbatim if it is a string.",
+ "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\nThe key is absent on notifications not delivered via a subscription\nstream (e.g. progress notifications for an in-flight request), which is\nwhy it is optional here.\n\nThe value is derived from the JSON-RPC ID of the `subscriptions/listen`\nrequest that opened the stream: the decimal string representation of the\nID if it is a number, or the ID verbatim if it is a string.",
"type": "string"
}
},
diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts
index 4255265bf..14b7b7ace 100644
--- a/schema/draft/schema.ts
+++ b/schema/draft/schema.ts
@@ -123,6 +123,9 @@ export interface NotificationMetaObject extends MetaObject {
* server MUST include this key on every notification delivered via a
* {@link SubscriptionsListenRequest | subscriptions/listen} stream, so the
* client can correlate the notification with the originating subscription.
+ * The key is absent on notifications not delivered via a subscription
+ * stream (e.g. progress notifications for an in-flight request), which is
+ * why it is optional here.
*
* The value is derived from the JSON-RPC ID of the `subscriptions/listen`
* request that opened the stream: the decimal string representation of the
@@ -409,6 +412,9 @@ export const UNSUPPORTED_PROTOCOL_VERSION = -32004;
* because required headers are missing or malformed. For HTTP, the response
* status code MUST be `400 Bad Request`.
*
+ * @example Header mismatch
+ * {@includeCode ./examples/HeaderMismatchError/header-mismatch.json}
+ *
* @category Errors
*/
export interface HeaderMismatchError extends Omit<
From 8792e0b12f4da334eb86c034eb36fad0c57842d0 Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Tue, 9 Jun 2026 18:12:50 +0100
Subject: [PATCH 4/6] Apply suggestions from code review
Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com>
---
docs/specification/draft/basic/patterns/cancellation.mdx | 8 +++-----
docs/specification/draft/basic/patterns/progress.mdx | 3 +--
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/docs/specification/draft/basic/patterns/cancellation.mdx b/docs/specification/draft/basic/patterns/cancellation.mdx
index 2ac3f35e7..1f5a56b86 100644
--- a/docs/specification/draft/basic/patterns/cancellation.mdx
+++ b/docs/specification/draft/basic/patterns/cancellation.mdx
@@ -5,12 +5,10 @@ title: Cancellation
The Model Context Protocol (MCP) supports optional cancellation of in-progress requests
-through notification messages. Because all requests flow from client to server,
-cancellation flows in one direction as well: the client sends a cancellation notification
+through notification messages. A client **SHOULD** sends a cancellation notification
to indicate that a request it previously issued should be terminated.
-There is one exception, where cancellations are sent from server to client: on stdio, a
-server **MUST** send `notifications/cancelled`
+A server **MUST** send `notifications/cancelled`
referencing a `subscriptions/listen` request ID when it tears down that subscription
stream (see [Subscriptions][subscriptions]). Servers **MUST NOT** send
`notifications/cancelled` for any other purpose.
@@ -49,7 +47,7 @@ How a client signals cancellation depends on the transport:
1. Cancellation notifications **MUST** only reference requests that:
- Were previously issued by the client
- Are believed to still be in-progress
-1. Server-sent cancellation notifications **MUST** only reference a
+1. Server-sent cancellation notifications **MUST** reference a
`subscriptions/listen` request, to terminate that subscription stream
1. Servers receiving cancellation notifications **SHOULD**:
- Stop processing the cancelled request
diff --git a/docs/specification/draft/basic/patterns/progress.mdx b/docs/specification/draft/basic/patterns/progress.mdx
index a52df885b..f6dc5c041 100644
--- a/docs/specification/draft/basic/patterns/progress.mdx
+++ b/docs/specification/draft/basic/patterns/progress.mdx
@@ -5,8 +5,7 @@ title: Progress
The Model Context Protocol (MCP) supports optional progress tracking for long-running
-operations through notification messages. Because all requests flow from client to
-server, progress flows in one direction as well: the server sends progress notifications
+operations through notification messages. The server **MAY** send progress notifications
to report the status of requests the client has issued.
## Progress Flow
From 15d23b3813d4b2450ddd5348d998c4b4c7eee25d Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Tue, 9 Jun 2026 18:16:11 +0100
Subject: [PATCH 5/6] Make subscriptionId carry the JSON-RPC ID verbatim as
string or number
The _meta value is now typed as RequestId (string | number) instead of
string, so no numeric-to-string conversion rule is needed. Update the
subscriptions and resources doc examples to show a numeric ID passed
through unchanged, and fix a verb agreement typo in cancellation.mdx.
---
.../draft/basic/patterns/cancellation.mdx | 2 +-
.../draft/basic/patterns/subscriptions.mdx | 16 ++++++++--------
docs/specification/draft/schema.mdx | 8 ++++----
docs/specification/draft/server/resources.mdx | 2 +-
schema/draft/schema.json | 4 ++--
schema/draft/schema.ts | 8 ++++----
6 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/docs/specification/draft/basic/patterns/cancellation.mdx b/docs/specification/draft/basic/patterns/cancellation.mdx
index 1f5a56b86..c20b5067a 100644
--- a/docs/specification/draft/basic/patterns/cancellation.mdx
+++ b/docs/specification/draft/basic/patterns/cancellation.mdx
@@ -5,7 +5,7 @@ title: Cancellation
The Model Context Protocol (MCP) supports optional cancellation of in-progress requests
-through notification messages. A client **SHOULD** sends a cancellation notification
+through notification messages. A client **SHOULD** send a cancellation notification
to indicate that a request it previously issued should be terminated.
A server **MUST** send `notifications/cancelled`
diff --git a/docs/specification/draft/basic/patterns/subscriptions.mdx b/docs/specification/draft/basic/patterns/subscriptions.mdx
index 1b97088dc..9cf43a049 100644
--- a/docs/specification/draft/basic/patterns/subscriptions.mdx
+++ b/docs/specification/draft/basic/patterns/subscriptions.mdx
@@ -62,7 +62,7 @@ omitted.
"method": "notifications/subscriptions/acknowledged",
"params": {
"_meta": {
- "io.modelcontextprotocol/subscriptionId": "1"
+ "io.modelcontextprotocol/subscriptionId": 1
},
"notifications": {
"toolsListChanged": true,
@@ -79,11 +79,11 @@ any unsupported types gracefully.
All notifications delivered on the stream carry
`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying the
-`subscriptions/listen` request that opened the stream. The value is always a string,
-derived from the JSON-RPC ID of the `subscriptions/listen` request: the decimal string
-representation of the ID if it is a number, or the ID verbatim if it is a string. In
-the examples above, the request used `"id": 1`, so the acknowledgment and all
-subsequent notifications carry the subscription ID `"1"`. On stdio, where all messages
+`subscriptions/listen` request that opened the stream. The value is the JSON-RPC ID of
+the `subscriptions/listen` request, verbatim: a string ID stays a string, and a
+numeric ID stays a number. In the examples above, the request used `"id": 1`, so the
+acknowledgment and all subsequent notifications carry the subscription ID `1`. On
+stdio, where all messages
share a single channel, clients **MUST** use this field to correlate notifications
with their originating subscription.
@@ -93,7 +93,7 @@ with their originating subscription.
"method": "notifications/resources/updated",
"params": {
"_meta": {
- "io.modelcontextprotocol/subscriptionId": "1"
+ "io.modelcontextprotocol/subscriptionId": 1
},
"uri": "file:///project/config.json"
}
@@ -106,7 +106,7 @@ A client **MAY** have multiple active subscriptions concurrently — for example
one listening for tools-list changes and another for resource updates. Each
subscription is identified by the JSON-RPC request ID of its
`subscriptions/listen` request, and every notification on the stream carries
-that ID (converted to a string as described above) in
+that ID verbatim in
`io.modelcontextprotocol/subscriptionId` so clients can demultiplex them.
## Cancellation
diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx
index 320f21ad8..852d38864 100644
--- a/docs/specification/draft/schema.mdx
+++ b/docs/specification/draft/schema.mdx
@@ -152,14 +152,14 @@ deprecated features registry.
### `NotificationMetaObject`
-"io.modelcontextprotocol/subscriptionId"?: string
diff --git a/docs/specification/draft/server/resources.mdx b/docs/specification/draft/server/resources.mdx
index a211876e9..785e1ae78 100644
--- a/docs/specification/draft/server/resources.mdx
+++ b/docs/specification/draft/server/resources.mdx
@@ -245,7 +245,7 @@ changes.
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
- "_meta": { "io.modelcontextprotocol/subscriptionId": "4" },
+ "_meta": { "io.modelcontextprotocol/subscriptionId": 4 },
"uri": "file:///project/src/main.rs"
}
}
diff --git a/schema/draft/schema.json b/schema/draft/schema.json
index efdc13ff0..49527feb6 100644
--- a/schema/draft/schema.json
+++ b/schema/draft/schema.json
@@ -2254,8 +2254,8 @@
"description": "Extends {@link MetaObject} with additional notification-specific fields. All key naming rules from `MetaObject` apply.",
"properties": {
"io.modelcontextprotocol/subscriptionId": {
- "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\nThe key is absent on notifications not delivered via a subscription\nstream (e.g. progress notifications for an in-flight request), which is\nwhy it is optional here.\n\nThe value is derived from the JSON-RPC ID of the `subscriptions/listen`\nrequest that opened the stream: the decimal string representation of the\nID if it is a number, or the ID verbatim if it is a string.",
- "type": "string"
+ "$ref": "#/$defs/RequestId",
+ "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\nThe key is absent on notifications not delivered via a subscription\nstream (e.g. progress notifications for an in-flight request), which is\nwhy it is optional here.\n\nThe value is the JSON-RPC ID of the `subscriptions/listen` request that\nopened the stream, verbatim: a string ID stays a string, and a numeric\nID stays a number."
}
},
"type": "object"
diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts
index 14b7b7ace..eed0ade14 100644
--- a/schema/draft/schema.ts
+++ b/schema/draft/schema.ts
@@ -127,11 +127,11 @@ export interface NotificationMetaObject extends MetaObject {
* stream (e.g. progress notifications for an in-flight request), which is
* why it is optional here.
*
- * The value is derived from the JSON-RPC ID of the `subscriptions/listen`
- * request that opened the stream: the decimal string representation of the
- * ID if it is a number, or the ID verbatim if it is a string.
+ * The value is the JSON-RPC ID of the `subscriptions/listen` request that
+ * opened the stream, verbatim: a string ID stays a string, and a numeric
+ * ID stays a number.
*/
- "io.modelcontextprotocol/subscriptionId"?: string;
+ "io.modelcontextprotocol/subscriptionId"?: RequestId;
}
/**
From 2eaf7ca74fc1807da6c36c0f0354d8fe00774b50 Mon Sep 17 00:00:00 2001
From: Peter Alexander
Date: Tue, 9 Jun 2026 18:17:02 +0100
Subject: [PATCH 6/6] Simplify subscriptionId description to just the request
ID
---
.../specification/draft/basic/patterns/subscriptions.mdx | 9 ++++-----
docs/specification/draft/schema.mdx | 3 +--
schema/draft/schema.json | 2 +-
schema/draft/schema.ts | 3 +--
4 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/docs/specification/draft/basic/patterns/subscriptions.mdx b/docs/specification/draft/basic/patterns/subscriptions.mdx
index 9cf43a049..b85db7072 100644
--- a/docs/specification/draft/basic/patterns/subscriptions.mdx
+++ b/docs/specification/draft/basic/patterns/subscriptions.mdx
@@ -80,10 +80,9 @@ any unsupported types gracefully.
All notifications delivered on the stream carry
`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying the
`subscriptions/listen` request that opened the stream. The value is the JSON-RPC ID of
-the `subscriptions/listen` request, verbatim: a string ID stays a string, and a
-numeric ID stays a number. In the examples above, the request used `"id": 1`, so the
-acknowledgment and all subsequent notifications carry the subscription ID `1`. On
-stdio, where all messages
+the `subscriptions/listen` request. In the examples above, the request used `"id": 1`,
+so the acknowledgment and all subsequent notifications carry the subscription ID `1`.
+On stdio, where all messages
share a single channel, clients **MUST** use this field to correlate notifications
with their originating subscription.
@@ -106,7 +105,7 @@ A client **MAY** have multiple active subscriptions concurrently — for example
one listening for tools-list changes and another for resource updates. Each
subscription is identified by the JSON-RPC request ID of its
`subscriptions/listen` request, and every notification on the stream carries
-that ID verbatim in
+that ID in
`io.modelcontextprotocol/subscriptionId` so clients can demultiplex them.
## Cancellation
diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx
index 852d38864..7012731b8 100644
--- a/docs/specification/draft/schema.mdx
+++ b/docs/specification/draft/schema.mdx
@@ -158,8 +158,7 @@ client can correlate the notification with the originating subscription.
The key is absent on notifications not delivered via a subscription
stream (e.g. progress notifications for an in-flight request), which is
why it is optional here. The value is the JSON-RPC ID of the subscriptions/listen request that
-opened the stream, verbatim: a string ID stays a string, and a numeric
-ID stays a number.
+opened the stream.
diff --git a/schema/draft/schema.json b/schema/draft/schema.json
index 49527feb6..6b1af2984 100644
--- a/schema/draft/schema.json
+++ b/schema/draft/schema.json
@@ -2255,7 +2255,7 @@
"properties": {
"io.modelcontextprotocol/subscriptionId": {
"$ref": "#/$defs/RequestId",
- "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\nThe key is absent on notifications not delivered via a subscription\nstream (e.g. progress notifications for an in-flight request), which is\nwhy it is optional here.\n\nThe value is the JSON-RPC ID of the `subscriptions/listen` request that\nopened the stream, verbatim: a string ID stays a string, and a numeric\nID stays a number."
+ "description": "Identifies the subscription stream a notification was delivered on. The\nserver MUST include this key on every notification delivered via a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream, so the\nclient can correlate the notification with the originating subscription.\nThe key is absent on notifications not delivered via a subscription\nstream (e.g. progress notifications for an in-flight request), which is\nwhy it is optional here.\n\nThe value is the JSON-RPC ID of the `subscriptions/listen` request that\nopened the stream."
}
},
"type": "object"
diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts
index eed0ade14..2aeafa59c 100644
--- a/schema/draft/schema.ts
+++ b/schema/draft/schema.ts
@@ -128,8 +128,7 @@ export interface NotificationMetaObject extends MetaObject {
* why it is optional here.
*
* The value is the JSON-RPC ID of the `subscriptions/listen` request that
- * opened the stream, verbatim: a string ID stays a string, and a numeric
- * ID stays a number.
+ * opened the stream.
*/
"io.modelcontextprotocol/subscriptionId"?: RequestId;
}
Extends MetaObject with additional notification-specific fields. All key naming rules from
MetaObjectapply.