Skip to content

Align draft schema with spec docs (subscriptionId, HeaderMismatch, notification directions)#2889

Open
pja-ant wants to merge 6 commits into
mainfrom
fix/draft-schema-consistency
Open

Align draft schema with spec docs (subscriptionId, HeaderMismatch, notification directions)#2889
pja-ant wants to merge 6 commits into
mainfrom
fix/draft-schema-consistency

Conversation

@pja-ant

@pja-ant pja-ant commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

What this does

Fixes a set of inconsistencies between schema/draft/schema.ts (the source of truth) and the draft specification prose, found during a pre-release consistency review.

Why now

The draft is heading to release; each of these is a place where an implementer reading the schema and one reading the prose would build incompatible behavior.

Changes

  • Declare the reserved io.modelcontextprotocol/subscriptionId _meta key in a new NotificationMetaObject type, and specify the derivation rule (decimal string of a numeric JSON-RPC ID, verbatim for a string ID). Previously the key existed only in prose and one example, and the docs showed numeric id 1 acknowledged as string "1" with no conversion rule — a strict comparison silently drops all subscription notifications on stdio.
  • Fix the three *ListChangedNotification doc comments that still said notifications "may be issued by servers without any previous subscription", contradicting the opt-in subscriptions model (servers MUST NOT send unrequested types).
  • Add HEADER_MISMATCH = -32001 and a HeaderMismatchError interface. The transport requires servers to emit this code, but the schema had no definition, so generated SDKs could not recognize it.
  • Align the cacheScope: "private" doc comment with caching.mdx's authorization-context model (the two previously described different cache-sharing rules — a security boundary).
  • Make CancelledNotificationParams.requestId required (the optionality was a leftover from removed task cancellation, and an id-less cancellation has no defined meaning).
  • Give ListRootsRequest minimal params instead of RequestParams, which forced servers to fabricate client-identifying _meta on a server-initiated input request.
  • Remove ProgressNotification from ClientNotification (clients no longer receive requests, so they have nothing to report progress on). CancelledNotification stays in ServerNotification but is now scoped: servers send it solely to terminate a subscriptions/listen stream on stdio, matching the teardown rule from (chore): sep-to-spec consistency pass #2863. cancellation.mdx and progress.mdx are rewritten to match the directional model.

Verification

npm run generate:schema artifacts committed; npm run prep and npm run check pass clean.

pja-ant added 2 commits June 7, 2026 13:57
- 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.
- 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)
@mintlify

mintlify Bot commented Jun 7, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mcp-staging 🟢 Ready View Preview Jun 7, 2026, 1:04 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@mintlify

mintlify Bot commented Jun 7, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mcp 🟢 Ready View Preview Jun 7, 2026, 1:04 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@pja-ant pja-ant marked this pull request as ready for review June 7, 2026 13:08
@pja-ant pja-ant requested a review from a team as a code owner June 7, 2026 13:08
@localden localden added spec rc-high-priority Related to an upcoming specification release and needs to be addressed with a high priority. labels Jun 8, 2026
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`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just being explicit about the exception it took me a couple reads to figure it out.

Suggested change
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`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why do we do this? seems a bit odd to only support this in STDIO @kurtisvg is the complexity of the asymmetry high value enough to have this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On HTTP you just close the connection.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in dfb90ad.

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`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why do we do this? seems a bit odd to only support this in STDIO @kurtisvg is the complexity of the asymmetry high value enough to have this?

Comment thread schema/draft/schema.ts Outdated
* 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this optional shouldn't it be required?

Suggested change
"io.modelcontextprotocol/subscriptionId"?: string;
"io.modelcontextprotocol/subscriptionId": string;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all notifications need this (e.g. a progress notification on a regular tool call).

Comment thread schema/draft/schema.ts
… 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
Comment thread docs/specification/draft/basic/patterns/cancellation.mdx Outdated
Comment thread docs/specification/draft/basic/patterns/cancellation.mdx Outdated
- 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a MUST?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe, but things like "afterwards" are always tricky in distributed systems - e.g. sending cancellation at the same time that a response starts its handler.

Comment thread docs/specification/draft/basic/patterns/progress.mdx Outdated
Comment on lines +82 to +86
`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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it alwys a string and not of the same type as the id field (so RequestId = number | string)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah that's probably better. The examples in the spec where using id as number and subscriptionId as string, but those should be updated instead.

Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com>
pja-ant added 2 commits June 9, 2026 18:16
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rc-high-priority Related to an upcoming specification release and needs to be addressed with a high priority. spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants