diff --git a/.changeset/spec-anchor-repin-2fb207da.md b/.changeset/spec-anchor-repin-2fb207da.md new file mode 100644 index 0000000000..b021ff4d77 --- /dev/null +++ b/.changeset/spec-anchor-repin-2fb207da.md @@ -0,0 +1,13 @@ +--- +'@modelcontextprotocol/core': patch +'@modelcontextprotocol/client': patch +'@modelcontextprotocol/server': patch +--- + +Re-pin the 2026-07-28 draft references (spec reference types, vendored schema.json twins, example corpus) to the latest spec commit and align the 2026-era wire surface with it. Deliberate 2026-era wire behavior changes (the released 2025-11-25 surface is untouched): + +- `notifications/elicitation/complete` is no longer part of the 2026-07-28 wire registry (the draft removed the notification together with `elicitationId` on URL-mode elicitation). On connections negotiated at 2026-07-28, sending it — including via `Server.createElicitationCompletionNotifier()` — now fails locally with `SdkErrorCode.MethodNotSupportedByProtocolVersion`, and inbound copies are dropped as unknown notifications. Both remain fully supported on 2025-11-25. +- `notifications/cancelled` on 2026-era connections now parses with a revision-exact schema that requires `requestId` (the draft made it required); the notification `_meta` shape types the `io.modelcontextprotocol/subscriptionId` key. 2025-era parsing is unchanged. +- The error code `-32001` emitted for HTTP header/body mismatches is now defined by the draft schema (`HEADER_MISMATCH`); the emitted behavior is unchanged (documentation only). + +No public API surface changes; the regenerated reference artifacts are internal/test-only. diff --git a/packages/core/src/shared/inboundClassification.ts b/packages/core/src/shared/inboundClassification.ts index 02e332236f..b90efbcb91 100644 --- a/packages/core/src/shared/inboundClassification.ts +++ b/packages/core/src/shared/inboundClassification.ts @@ -177,8 +177,9 @@ export type InboundClassificationOutcome = InboundLegacyRoute | InboundModernRou * with the body's classification), and the `Mcp-Method` header disagreeing * with the body method. * - * `-32001` is the SEP-2243 `HeaderMismatch` code, as asserted by the published - * conformance suite for header-validation failures. It has no + * `-32001` is the draft schema's `HEADER_MISMATCH` constant (the SEP-2243 + * `HeaderMismatch` code; the spec requires HTTP 400 for it), as also asserted + * by the published conformance suite for header-validation failures. It has no * {@linkcode ProtocolErrorCode} member because it is not part of the 2025-era * wire vocabulary; the validation ladder is its only emitter. */ diff --git a/packages/core/src/types/spec.types.2026-07-28.ts b/packages/core/src/types/spec.types.2026-07-28.ts index 1b222b9896..bf5ad3e095 100644 --- a/packages/core/src/types/spec.types.2026-07-28.ts +++ b/packages/core/src/types/spec.types.2026-07-28.ts @@ -3,7 +3,7 @@ * * Source: https://github.com/modelcontextprotocol/modelcontextprotocol * Pulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts - * Last updated from commit: 77cb26481e439d3437bc2bd6ccd19fcae86bb1ec + * Last updated from commit: 91b403f8d8e7bb545b51bf45b1e26c6c889cc328 * * DO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates. * To update this file, run: pnpm run fetch:spec-types 2026-07-28 @@ -110,6 +110,29 @@ 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 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. + */ + 'io.modelcontextprotocol/subscriptionId'?: RequestId; +} + /** * A progress token, used to associate progress notifications with the original request. * @@ -147,7 +170,7 @@ export interface Request { * @category Common Types */ export interface NotificationParams { - _meta?: MetaObject; + _meta?: NotificationMetaObject; } /** @internal */ @@ -357,6 +380,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 +405,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`. + * + * @example Header mismatch + * {@includeCode ./examples/HeaderMismatchError/header-mismatch.json} + * + * @category Errors + */ +export interface HeaderMismatchError extends Omit { + 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 @@ -517,9 +566,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. @@ -528,7 +577,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. * @@ -995,11 +1046,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'; @@ -1140,7 +1193,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} @@ -1584,7 +1637,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} @@ -1726,7 +1779,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} @@ -1828,6 +1881,11 @@ export interface Tool extends BaseMetadata, Icons { * (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other * standard validation or annotation keywords. * + * Property schemas may carry an `x-mcp-header` annotation to mirror the + * argument value into an HTTP header on the Streamable HTTP transport. See + * the Streamable HTTP transport specification for the validity and + * extraction rules. + * * Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. */ inputSchema: { $schema?: string; type: 'object'; [key: string]: unknown }; @@ -2539,7 +2597,9 @@ export interface PromptReference extends BaseMetadata { */ export interface ListRootsRequest { method: 'roots/list'; - params?: RequestParams; + params?: { + _meta?: MetaObject; + }; } /** @@ -2649,12 +2709,6 @@ export interface ElicitRequestURLParams { */ message: string; - /** - * The ID of the elicitation, which must be unique within the context of the server. - * The client MUST treat this ID as an opaque value. - */ - elicitationId: string; - /** * The URL that the user should navigate to. * @@ -2969,31 +3023,6 @@ export interface ElicitResult { content?: { [key: string]: string | number | boolean | string[] }; } -/** - * Parameters for a {@link ElicitationCompleteNotification | notifications/elicitation/complete} notification. - * - * @category `notifications/elicitation/complete` - */ -export interface ElicitationCompleteNotificationParams extends NotificationParams { - /** - * The ID of the elicitation that completed. - */ - elicitationId: string; -} - -/** - * An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. - * - * @example Elicitation complete - * {@includeCode ./examples/ElicitationCompleteNotification/elicitation-complete.json} - * - * @category `notifications/elicitation/complete` - */ -export interface ElicitationCompleteNotification extends JSONRPCNotification { - method: 'notifications/elicitation/complete'; - params: ElicitationCompleteNotificationParams; -} - /* Client messages */ /** @internal */ export type ClientRequest = @@ -3009,7 +3038,7 @@ export type ClientRequest = | ListToolsRequest; /** @internal */ -export type ClientNotification = CancelledNotification | ProgressNotification; +export type ClientNotification = CancelledNotification; /** @internal */ export type ClientResult = EmptyResult; @@ -3025,7 +3054,6 @@ export type ServerNotification = | ResourceListChangedNotification | ToolListChangedNotification | PromptListChangedNotification - | ElicitationCompleteNotification | SubscriptionsAcknowledgedNotification; /** @internal */ diff --git a/packages/core/src/wire/rev2026-07-28/registry.ts b/packages/core/src/wire/rev2026-07-28/registry.ts index e361e65eff..bad5190407 100644 --- a/packages/core/src/wire/rev2026-07-28/registry.ts +++ b/packages/core/src/wire/rev2026-07-28/registry.ts @@ -4,9 +4,11 @@ * Registry membership IS the deletion story: there are NO entries for * `initialize`, `notifications/initialized`, `ping`, `logging/setLevel`, * `resources/subscribe`, `resources/unsubscribe`, - * `notifications/roots/list_changed`, the task family, or the server→client - * wire-request channel — so an era-mismatched method falls to −32601 by - * absence inbound and a typed local error outbound, with no table to forget. + * `notifications/roots/list_changed`, `notifications/elicitation/complete` + * (removed from the draft schema; 2025-11-25-only vocabulary), the task + * family, or the server→client wire-request channel — so an era-mismatched + * method falls to −32601 by absence inbound and a typed local error outbound, + * with no table to forget. * * HAND-REGISTRY SEED DECISIONS (pinned by the CI registry-diff oracle, which * fails LOUD if this list and the anchor diff ever disagree): diff --git a/packages/core/src/wire/rev2026-07-28/schemas.ts b/packages/core/src/wire/rev2026-07-28/schemas.ts index 510cd399ef..d6393d9f55 100644 --- a/packages/core/src/wire/rev2026-07-28/schemas.ts +++ b/packages/core/src/wire/rev2026-07-28/schemas.ts @@ -25,11 +25,9 @@ import { AudioContentSchema, BaseMetadataSchema, BlobResourceContentsSchema, - CancelledNotificationSchema, ClientCapabilitiesSchema, ContentBlockSchema, CursorSchema, - ElicitationCompleteNotificationSchema, IconsSchema, ImageContentSchema, ImplementationSchema, @@ -41,6 +39,7 @@ import { PromptMessageSchema, PromptReferenceSchema, PromptSchema, + RequestIdSchema, ResourceContentsSchema, ResourceListChangedNotificationSchema, ResourceSchema, @@ -434,11 +433,57 @@ export const dispatchResultSchemas: { readonly [M in Rev2026RequestMethod]: z.Zo /* ------------------------------------------------------------------------ * * Notifications. The 2026 notification set: cancelled, progress, message, * resources/updated, resources/list_changed, tools/list_changed, - * prompts/list_changed, elicitation/complete. Deleted: initialized, - * roots/list_changed, tasks/status. The shapes are revision-identical to the - * shared schemas, which are composed by reference. (The 2026-only - * subscriptions/acknowledged notification is #14 scope — see registry.ts.) + * prompts/list_changed. Deleted: initialized, roots/list_changed, + * tasks/status, elicitation/complete (removed from the draft together with + * URL-elicitation's elicitationId — both remain 2025-11-25 vocabulary only). + * The shapes are revision-identical to the shared schemas, which are + * composed by reference, EXCEPT cancelled, which forks below: this revision + * requires `requestId`. (The 2026-only subscriptions/acknowledged + * notification is #14 scope — see registry.ts.) * ------------------------------------------------------------------------ */ + +/** + * Notification `_meta` (anchor `NotificationMetaObject`): loose, with the + * subscriptions/listen demux key typed when present. Only the anchor-exact + * SHAPE is modeled here — listen delivery itself (filter gating, demux, + * teardown) is #14 scope and not implemented by this module. + */ +export const NotificationMetaSchema = z.looseObject({ + /** + * The JSON-RPC ID of the `subscriptions/listen` request that opened the + * stream a notification was delivered on; absent on notifications not + * delivered via a subscription stream. + */ + 'io.modelcontextprotocol/subscriptionId': RequestIdSchema.optional() +}); + +/** + * 2026-era `notifications/cancelled` params (anchor-exact fork): `requestId` + * is REQUIRED on this revision — the shared schema keeps it optional because + * the frozen 2025-11-25 shape declares it optional (task cancellation goes + * through `tasks/cancel` there). Requiredness is bare because no 2025-era + * traffic touches this module. + */ +export const CancelledNotificationParamsSchema = z.object({ + _meta: NotificationMetaSchema.optional(), + /** + * The ID of the request to cancel. This MUST correspond to the ID of a + * request the client previously issued. + */ + requestId: RequestIdSchema, + /** + * An optional string describing the reason for the cancellation. This MAY + * be logged or presented to the user. + */ + reason: z.string().optional() +}); + +/** 2026-era `notifications/cancelled` (see the params fork above). */ +export const CancelledNotificationSchema = z.object({ + method: z.literal('notifications/cancelled'), + params: CancelledNotificationParamsSchema +}); + /** The 2026-era notification-method set (the hand-registry seed; see the deletion list above). */ export type Rev2026NotificationMethod = | 'notifications/cancelled' @@ -447,8 +492,7 @@ export type Rev2026NotificationMethod = | 'notifications/resources/updated' | 'notifications/resources/list_changed' | 'notifications/tools/list_changed' - | 'notifications/prompts/list_changed' - | 'notifications/elicitation/complete'; + | 'notifications/prompts/list_changed'; export const notificationSchemas2026: { readonly [M in Rev2026NotificationMethod]: z.ZodType<{ method: M }> } = { 'notifications/cancelled': CancelledNotificationSchema, @@ -457,8 +501,7 @@ export const notificationSchemas2026: { readonly [M in Rev2026NotificationMethod 'notifications/resources/updated': ResourceUpdatedNotificationSchema, 'notifications/resources/list_changed': ResourceListChangedNotificationSchema, 'notifications/tools/list_changed': ToolListChangedNotificationSchema, - 'notifications/prompts/list_changed': PromptListChangedNotificationSchema, - 'notifications/elicitation/complete': ElicitationCompleteNotificationSchema + 'notifications/prompts/list_changed': PromptListChangedNotificationSchema }; /* ------------------------------------------------------------------------ * diff --git a/packages/core/test/corpus/fixtures/2026-07-28/ElicitRequestURLParams/elicit-sensitive-data.json b/packages/core/test/corpus/fixtures/2026-07-28/ElicitRequestURLParams/elicit-sensitive-data.json index cf791ee3fe..0742eb9974 100644 --- a/packages/core/test/corpus/fixtures/2026-07-28/ElicitRequestURLParams/elicit-sensitive-data.json +++ b/packages/core/test/corpus/fixtures/2026-07-28/ElicitRequestURLParams/elicit-sensitive-data.json @@ -1,6 +1,5 @@ { "mode": "url", - "elicitationId": "550e8400-e29b-41d4-a716-446655440000", "url": "https://mcp.example.com/ui/set_api_key", "message": "Please provide your API key to continue." } diff --git a/packages/core/test/corpus/fixtures/2026-07-28/ElicitationCompleteNotification/elicitation-complete.json b/packages/core/test/corpus/fixtures/2026-07-28/ElicitationCompleteNotification/elicitation-complete.json deleted file mode 100644 index bb6d564585..0000000000 --- a/packages/core/test/corpus/fixtures/2026-07-28/ElicitationCompleteNotification/elicitation-complete.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "notifications/elicitation/complete", - "params": { - "elicitationId": "550e8400-e29b-41d4-a716-446655440000" - } -} diff --git a/packages/core/test/corpus/fixtures/2026-07-28/HeaderMismatchError/header-mismatch.json b/packages/core/test/corpus/fixtures/2026-07-28/HeaderMismatchError/header-mismatch.json new file mode 100644 index 0000000000..a0f2e569c1 --- /dev/null +++ b/packages/core/test/corpus/fixtures/2026-07-28/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/packages/core/test/corpus/fixtures/2026-07-28/manifest.json b/packages/core/test/corpus/fixtures/2026-07-28/manifest.json index 8aa8155edd..c0b9610818 100644 --- a/packages/core/test/corpus/fixtures/2026-07-28/manifest.json +++ b/packages/core/test/corpus/fixtures/2026-07-28/manifest.json @@ -3,7 +3,7 @@ "source": { "repo": "modelcontextprotocol/modelcontextprotocol", "path": "schema/draft/examples", - "commit": "0168c57fc74aba6e6dcf8f0b7191db3caaa5ad65" + "commit": "2fb207da428f43a4981513ec2aef218b7533c5b3" }, "regenerate": "pnpm fetch:spec-examples --spec-dir # or [sha] to fetch from GitHub", "directoryCount": 86, @@ -85,9 +85,6 @@ "DiscoverResultResponse": [ "discover-result-response.json" ], - "ElicitationCompleteNotification": [ - "elicitation-complete.json" - ], "ElicitRequest": [ "elicitation-request.json" ], @@ -118,6 +115,9 @@ "GetPromptResultResponse": [ "get-prompt-result-response.json" ], + "HeaderMismatchError": [ + "header-mismatch.json" + ], "ImageContent": [ "image-png-content-with-annotations.json" ], diff --git a/packages/core/test/corpus/schema-twins/2026-07-28.schema.json b/packages/core/test/corpus/schema-twins/2026-07-28.schema.json index 5ce9df12e4..058eddf93a 100644 --- a/packages/core/test/corpus/schema-twins/2026-07-28.schema.json +++ b/packages/core/test/corpus/schema-twins/2026-07-28.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" @@ -874,10 +889,6 @@ "ElicitRequestURLParams": { "description": "The parameters for a request to elicit information from the user via a URL in the client.", "properties": { - "elicitationId": { - "description": "The ID of the elicitation, which must be unique within the context of the server.\nThe client MUST treat this ID as an opaque value.", - "type": "string" - }, "message": { "description": "The message to present to the user explaining why the interaction is needed.", "type": "string" @@ -894,7 +905,6 @@ } }, "required": [ - "elicitationId", "message", "mode", "url" @@ -940,44 +950,6 @@ ], "type": "object" }, - "ElicitationCompleteNotification": { - "description": "An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.", - "properties": { - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "const": "notifications/elicitation/complete", - "type": "string" - }, - "params": { - "$ref": "#/$defs/ElicitationCompleteNotificationParams" - } - }, - "required": [ - "jsonrpc", - "method", - "params" - ], - "type": "object" - }, - "ElicitationCompleteNotificationParams": { - "description": "Parameters for a {@link ElicitationCompleteNotificationnotifications/elicitation/complete} notification.", - "properties": { - "_meta": { - "$ref": "#/$defs/MetaObject" - }, - "elicitationId": { - "description": "The ID of the elicitation that completed.", - "type": "string" - } - }, - "required": [ - "elicitationId" - ], - "type": "object" - }, "EmbeddedResource": { "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", "properties": { @@ -1163,6 +1135,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 +1647,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 +1736,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 +1825,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 +1889,12 @@ "type": "string" }, "params": { - "$ref": "#/$defs/RequestParams" + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + } + }, + "type": "object" } }, "required": [ @@ -1937,7 +1950,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 +2046,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 +2207,21 @@ ], "type": "object" }, + "NotificationMetaObject": { + "description": "Extends {@link MetaObject} with additional notification-specific fields. All key naming rules from `MetaObject` apply.", + "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." + } + }, + "type": "object" + }, "NotificationParams": { "description": "Common params for any notification.", "properties": { "_meta": { - "$ref": "#/$defs/MetaObject" + "$ref": "#/$defs/NotificationMetaObject" } }, "type": "object" @@ -2369,7 +2392,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 +2488,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 +2603,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 +2859,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 +2987,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.", @@ -3174,9 +3197,6 @@ }, { "$ref": "#/$defs/LoggingMessageNotification" - }, - { - "$ref": "#/$defs/ElicitationCompleteNotification" } ] }, @@ -3314,7 +3334,7 @@ "description": "Parameters for a {@link SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged} notification.", "properties": { "_meta": { - "$ref": "#/$defs/MetaObject" + "$ref": "#/$defs/NotificationMetaObject" }, "notifications": { "$ref": "#/$defs/SubscriptionFilter", @@ -3556,7 +3576,7 @@ }, "inputSchema": { "additionalProperties": {}, - "description": "A JSON Schema object defining the expected parameters for the tool.\n\nTool arguments are always JSON objects, so `type: \"object\"` is required at the root.\nBeyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including\ncomposition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords\n(`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other\nstandard validation or annotation keywords.\n\nDefaults to JSON Schema 2020-12 when no explicit `$schema` is provided.", + "description": "A JSON Schema object defining the expected parameters for the tool.\n\nTool arguments are always JSON objects, so `type: \"object\"` is required at the root.\nBeyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including\ncomposition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords\n(`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other\nstandard validation or annotation keywords.\n\nProperty schemas may carry an `x-mcp-header` annotation to mirror the\nargument value into an HTTP header on the Streamable HTTP transport. See\nthe Streamable HTTP transport specification for the validity and\nextraction rules.\n\nDefaults to JSON Schema 2020-12 when no explicit `$schema` is provided.", "properties": { "$schema": { "type": "string" @@ -3638,7 +3658,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/packages/core/test/corpus/schema-twins/manifest.json b/packages/core/test/corpus/schema-twins/manifest.json index 1b21d36b3d..1311e833b9 100644 --- a/packages/core/test/corpus/schema-twins/manifest.json +++ b/packages/core/test/corpus/schema-twins/manifest.json @@ -2,12 +2,12 @@ "comment": "Vendored schema.json twins (TEST-ONLY conformance oracles; never bundled, never runtime). RAW upstream bytes - never reformat: each file is locked to the sha256/bytes below by schemaTwinConformance. Refresh via `pnpm fetch:schema-twins [sha]`, ATOMICALLY with the matching spec.types anchor (see packages/core/src/types/README.md lifecycle rule 4).", "source": { "repository": "modelcontextprotocol/modelcontextprotocol", - "commit": "0168c57fc74aba6e6dcf8f0b7191db3caaa5ad65" + "commit": "2fb207da428f43a4981513ec2aef218b7533c5b3" }, "files": { "2026-07-28": { - "sha256": "afaf886c06dd8d3cbdd556d81b6483b9018112aaf7ee284fa116eca58baf54fc", - "bytes": 172822, + "sha256": "f1a3de0e522d3247f3c48e9603379d9dfc8a31b18f429e88ad816593d1d0c23c", + "bytes": 176374, "upstreamPath": "schema/draft/schema.json" }, "2025-11-25": { diff --git a/packages/core/test/corpus/specCorpus.test.ts b/packages/core/test/corpus/specCorpus.test.ts index f36586f9e7..d5bcdecdbb 100644 --- a/packages/core/test/corpus/specCorpus.test.ts +++ b/packages/core/test/corpus/specCorpus.test.ts @@ -49,6 +49,7 @@ const FIXTURES_ROOT = join(__dirname, 'fixtures'); /** JSON-RPC error-object example directories (bare `{code, message, data?}` shapes). */ const ERROR_OBJECT_DIRS = new Set([ + 'HeaderMismatchError', 'InternalError', 'InvalidParamsError', 'MethodNotFoundError', @@ -78,9 +79,13 @@ const PENDING_2026: Record = { * parse, so the entry is removed the moment the widening lands. */ const PENDING_2026_FILES: Record = { - // (empty — the SEP-2549 array-shape widenings burned when the 2026-era - // wire module landed anchor-exact Tool/CallToolResult forks; the two - // examples are real pins now.) + // The draft removed elicitationId from ElicitRequestURLParams; the SDK's + // shared schema keeps it (it is required on the frozen 2025-11-25 + // revision), and the 2026-era in-band elicitation surface that will model + // the new shape is MRTR scope (#13). Until then the upstream example + // (which carries no elicitationId) does not parse. + 'ElicitRequestURLParams/elicit-sensitive-data.json': + 'URL-mode elicitation without elicitationId is modeled with the MRTR in-band surface (SEP-2322, #13)' }; type AnyZod = z.ZodType; diff --git a/packages/core/test/spec.types.2026-07-28.test.ts b/packages/core/test/spec.types.2026-07-28.test.ts index 5bf80604c6..02ab034651 100644 --- a/packages/core/test/spec.types.2026-07-28.test.ts +++ b/packages/core/test/spec.types.2026-07-28.test.ts @@ -77,6 +77,11 @@ type WCompleteRequestParams = WCompleteRequest['params']; // PaginatedRequest in the anchor keeps `method: string` (it is the base, not // a concrete method) — composed from the derived params shape. type WPaginatedRequest = WithJSONRPCRequest<{ method: string; params: WPaginatedRequestParams }>; +// 2026-era cancelled fork (requestId required on this revision) and the +// notification `_meta` shape (anchor NotificationMetaObject). +type WCancelledNotification = z4.infer; +type WCancelledNotificationParams = z4.infer; +type WNotificationMeta = z4.infer; const sdkTypeChecks = { JSONValue: (sdk: SDKTypes.JSONValue, spec: SpecTypes.JSONValue) => { @@ -155,14 +160,6 @@ const sdkTypeChecks = { sdk = spec; spec = sdk; }, - CancelledNotificationParams: (sdk: SDKTypes.CancelledNotificationParams, spec: SpecTypes.CancelledNotificationParams) => { - sdk = spec; - spec = sdk; - }, - CancelledNotification: (sdk: WithJSONRPC, spec: SpecTypes.CancelledNotification) => { - sdk = spec; - spec = sdk; - }, ClientCapabilities: (sdk: SDKTypes.ClientCapabilities, spec: SpecTypes.ClientCapabilities) => { sdk = spec; spec = sdk; @@ -339,18 +336,6 @@ const sdkTypeChecks = { sdk = spec; spec = sdk; }, - ElicitRequestURLParams: (sdk: SDKTypes.ElicitRequestURLParams, spec: SpecTypes.ElicitRequestURLParams) => { - sdk = spec; - spec = sdk; - }, - ElicitRequestParams: (sdk: SDKTypes.ElicitRequestParams, spec: SpecTypes.ElicitRequestParams) => { - sdk = spec; - spec = sdk; - }, - ElicitRequest: (sdk: SDKTypes.ElicitRequest, spec: SpecTypes.ElicitRequest) => { - sdk = spec; - spec = sdk; - }, PrimitiveSchemaDefinition: (sdk: SDKTypes.PrimitiveSchemaDefinition, spec: SpecTypes.PrimitiveSchemaDefinition) => { sdk = spec; spec = sdk; @@ -398,30 +383,37 @@ const sdkTypeChecks = { EnumSchema: (sdk: SDKTypes.EnumSchema, spec: SpecTypes.EnumSchema) => { sdk = spec; spec = sdk; + } +}; + +/* 2026-era wire parity checks (Q1 increment 2) — appended to sdkTypeChecks. */ +const wireParityChecks = { + Result: (sdk: WResult, spec: SpecTypes.Result) => { + sdk = spec; + spec = sdk; }, - ElicitationCompleteNotificationParams: ( - sdk: SDKTypes.ElicitationCompleteNotificationParams, - spec: SpecTypes.ElicitationCompleteNotificationParams - ) => { + // Cancelled is the one notification this era forks (requestId is REQUIRED + // on 2026-07-28; the shared schema keeps the frozen 2025-11-25 optional + // shape) — compared against the fork, not the neutral type. + CancelledNotificationParams: (sdk: WCancelledNotificationParams, spec: SpecTypes.CancelledNotificationParams) => { sdk = spec; spec = sdk; }, - ElicitationCompleteNotification: ( - sdk: WithJSONRPC, - spec: SpecTypes.ElicitationCompleteNotification - ) => { + CancelledNotification: (sdk: WithJSONRPC, spec: SpecTypes.CancelledNotification) => { sdk = spec; spec = sdk; }, - ClientNotification: (sdk: WithJSONRPC, spec: SpecTypes.ClientNotification) => { + // The 2026 client-sent notification set is exactly `notifications/cancelled` + // (progress is server→client only on this revision), so the union compares + // against the cancelled fork; HTTP-side cancellation semantics (close the + // stream) are #14 scope and not asserted here. + ClientNotification: (sdk: WithJSONRPC, spec: SpecTypes.ClientNotification) => { sdk = spec; spec = sdk; - } -}; - -/* 2026-era wire parity checks (Q1 increment 2) — appended to sdkTypeChecks. */ -const wireParityChecks = { - Result: (sdk: WResult, spec: SpecTypes.Result) => { + }, + // Notification `_meta` (anchor NotificationMetaObject): the typed + // subscriptions/listen demux key — shape only; listen delivery is #14. + NotificationMetaObject: (sdk: WNotificationMeta, spec: SpecTypes.NotificationMetaObject) => { sdk = spec; spec = sdk; }, @@ -629,6 +621,10 @@ const FEATURE_OWNED_PENDING_2026: Record = { CreateMessageRequest: 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026', CreateMessageRequestParams: 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026', CreateMessageResult: 'M4.1 MRTR (#13) — in-band response shape', + ElicitRequest: 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026', + ElicitRequestParams: 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026', + ElicitRequestURLParams: + 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026; the draft also removed elicitationId from the URL-mode shape', ElicitResult: 'M4.1 MRTR (#13) — in-band response shape', ListRootsRequest: 'M4.1 MRTR (#13) — demoted to an in-band payload in 2026', ListRootsResult: 'M4.1 MRTR (#13) — in-band response shape', @@ -653,6 +649,7 @@ const FEATURE_OWNED_PENDING_2026: Record = { ServerNotification: 'M6.1 subscriptions/listen (#14) — the union gains the acknowledged notification', // M1.2 validation ladder (#8): the per-code error response envelopes: + HeaderMismatchError: 'M1.2 validation ladder (#8)', MissingRequiredClientCapabilityError: 'M1.2 validation ladder (#8)', UnsupportedProtocolVersionError: 'M1.2 validation ladder (#8)' }; diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index a0cd296ddb..5e3633a6ff 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -755,6 +755,11 @@ export class Server extends Protocol { * Creates a reusable callback that, when invoked, will send a `notifications/elicitation/complete` * notification for the specified elicitation ID. * + * The notification (and the `elicitationId` it references) exists only on protocol revision + * 2025-11-25 — the 2026-07-28 draft removed both. On a connection negotiated at 2026-07-28 the + * returned callback rejects with a typed local error before anything reaches the transport + * (the method is not part of that revision's wire registry). + * * @param elicitationId The ID of the elicitation to mark as complete. * @param options Optional notification options. Useful when the completion notification should be related to a prior request. * @returns A function that emits the completion notification when awaited. diff --git a/test/e2e/requirements.ts b/test/e2e/requirements.ts index d75c799c5f..2c919fbc2d 100644 --- a/test/e2e/requirements.ts +++ b/test/e2e/requirements.ts @@ -1060,7 +1060,14 @@ export const REQUIREMENTS: Record = { 'elicitation:url:complete-unknown-ignored': { source: 'https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#completion-notifications-for-url-mode-elicitation', behavior: - 'The client ignores an elicitation/complete notification referencing an unknown or already-completed elicitationId without error.' + 'The client ignores an elicitation/complete notification referencing an unknown or already-completed elicitationId without error.', + entryExclusions: [ + { + arm: 'entryModern', + reason: 'method-not-in-modern-registry', + note: 'notifications/elicitation/complete was removed from the 2026-07-28 draft; on that revision the client drops it as an unknown notification (the row asserts ignored-without-error against received copies, which never arrive)' + } + ] }, 'elicitation:url:required-error': { source: 'https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-elicitation-required-error',