From ca30fd76018e12a8bac49cf7f156150dbc44c8bc Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:33:22 -0600 Subject: [PATCH 01/69] chore: migrate SEP-1442 into PR-based SEP format Move the stateless-by-default MCP proposal into seps/0000-stateless-mcp.md with proper frontmatter and heading levels per SEP-1850. --- seps/0000-stateless-mcp.md | 825 +++++++++++++++++++++++++++++++++++++ 1 file changed, 825 insertions(+) create mode 100644 seps/0000-stateless-mcp.md diff --git a/seps/0000-stateless-mcp.md b/seps/0000-stateless-mcp.md new file mode 100644 index 000000000..db88953d4 --- /dev/null +++ b/seps/0000-stateless-mcp.md @@ -0,0 +1,825 @@ +# SEP-0000: Stateless-by-Default MCP + +- **Status**: Draft +- **Type**: Standards Track +- **Created**: 2025-06-18 +- **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) +- **Sponsor**: None (seeking sponsor) +- **PR**: TBD + +## Abstract + +A truly stateless protocol, where every request is self-contained and can be +understood in isolation, is highly desirable for its inherent simplicity, +scalability, and reliability. The current Model Context Protocol (MCP) is not +stateless by default. The specification requires an initialization handshake +that establishes a session state between the client and server, which persists +for the duration of the connection. + +This inherent statefulness makes it difficult to run MCP at scale. Placing an +MCP server behind a standard load balancer, for example, is challenging because +a client's session is coupled to the specific server instance holding its state. + +This proposal outlines a series of changes to **enable stateless MCP as the +default**, embracing a "pay as you go" model for protocol complexity and state. +Under this model, we provide simple, stateless features by default and only +introduce the overhead of stateful, long-lived connections for cases where that +functionality is actually required. + +Specifically, this SEP proposes removing the state-establishing initialization +handshake and replacing it with discrete, stateless alternatives. This initial +step allows each request to be processed independently, simplifying server-side +logic and paving the way for robust, scalable deployments. + + +## Motivation + +The Model Context Protocol (MCP) specification currently mandates a stateful +initialization handshake. This design choice creates significant challenges for +scalability, reliability, and implementation simplicity. This SEP is motivated +by the need to address these shortcomings. + + +### The Problem with Statefulness + +The core issue is that a server must retain session state from previous requests +to understand subsequent ones. This is in direct opposition to the design of +modern, cloud-native systems which favor stateless services for their resilience +and scalability. + + + +1. **Impediment to Scalability:** The most critical issue is the difficulty of + load balancing stateful MCP. A simple stateless load balancer (e.g., L4/L7 + round-robin) cannot be used, as it would route a client's requests to + different backend servers, none of which would have the correct session + state. Operators are forced to implement complex and fragile solutions like + sticky sessions, which bind a client to a specific server. This complicates + infrastructure, can lead to uneven load distribution, and makes horizontally + scaling the service non-trivial. +2. **Poor Resilience and Fault Tolerance:** In a stateful model, if the specific + server instance handling a client session fails, that session state is lost. + The client must detect the connection failure, re-establish a connection + (likely to a new server instance via the load balancer), and perform the + entire initialization handshake again. This process is disruptive and + inefficient, adding complexity around "resumability". +3. **Increased Implementation Complexity:** The current model imposes a + significant burden on developers. + * **Server-side:** Developers must implement logic to create, manage, and + eventually garbage-collect per-client session state. This is a common + source of bugs and memory leaks. + * **Client-side:** Developers must write complex code to manage a + persistent connection and handle the inevitable network failures and + reconnections, including the logic to resynchronize state after a + disconnect. + + +### Guiding Principles + +This proposal is the first step toward establishing a "pay as you go" model for +protocol complexity. We will be guided by the following principles, in order of +preference: + + + +1. **Prioritize Stateless-ness:** Whenever possible, a request must be + self-contained, providing all information the server needs to process it + without relying on session state from previous requests. +2. **Prefer State References:** If a fully stateless exchange is not practical, + references to state should be passed in every request. +3. **Treat Statefulness as a Last Resort:** The complexity of stateful logic and + long-lived streaming connections should only be accepted when no simpler + alternative exists to solve a critical use case. + + +### The Impact on Key Transports + +It is critical that these stateless principles are applied consistently across +all transports. Keeping the `stdio` and `http` implementations in sync ensures a +**unified developer experience**, allowing the core protocol semantics to be +learned once and applied everywhere. This consistency simplifies the creation of +transport-agnostic libraries and tooling, and prevents protocol fragmentation +where different transports behave in fundamentally different ways. A single, +coherent protocol model is essential for a healthy ecosystem. + + +## Specification + + +### Overview + +This specification fundamentally refactors the MCP interaction model to be +**stateless-first**. Currently, MCP requires a mandatory 3-way initialization +handshake before any resources can be exchanged. This handshake negotiates and +establishes several key pieces of information: + + + +1. MCP Protocol Version +2. Session ID (if the server supports sessions) +3. Server Capabilities (and + [serverInfo](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/69a292b16a64e086add82fd76fc6aaed68e47de0/schema/draft/schema.ts#L196)) +4. Client Capabilities (and + [clientInfo](?tab=t.xcggn0fi5c29#:~:text=also%20includes%20%60clientInfo%60%20(-,https%3A//github.com/modelcontextprotocol/modelcontextprotocol/blob/69a292b16a64e086add82fd76fc6aaed68e47de0/schema/draft/schema.ts%23L181,-)%20and%20%60serverInfo%60%20()) + +The requirement of this initialization handshake **enforces the establishment of +a state** that is expected to persist for subsequent communication between +client and server. Furthermore, by bundling these negotiations into a single +initialization phase, the specification creates an implied link between them, +particularly between stateful sessions and the exchange of capabilities. + +This proposal is to **deprecate the initialization handshake** and "unbundle" +its functions into discrete, stateless components. We will provide new, more +clearly defined mechanisms for clients and servers to exchange this information +without a mandatory state-creating cycle. + + +### Protocol Version + +To make requests self-contained, metadata previously negotiated during the +handshake must now be included with **every request**. + +--- + + + +#### HTTP + +For the HTTP transport, protocol version MUST be passed as **HTTP header**. For +the HTTP transport, the headers MUST be treated as the source of truth over the +request payload. + + + +* `MCP-Protocol-Version: 2025-06-18` + * **Purpose**: To inform the server which version of the MCP specification + the client is using for this specific request. + * **Requirement**: This header is **MANDATORY**. Servers should reject + requests with a missing or unsupported version. + * This header MUST match the value provided in the Request as specified + below. + + + +--- + + + +#### Per-request Version + +The `protocol-version` MUST be embedded directly within the `_meta` field of the +request payload. For HTTP, this _meta MUST match the associated HTTP header, or +else the server should return a 400 Bad Request. + +The following diff illustrates the required changes to the `Request` interface: + + +```ts +export interface Request { + method: string; + params?: { + /** + * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { ++ /** ++ * The MCP Protocol Version being used for this request. ++ */ ++ modelcontextprotocol.io/mcpProtocolVersion: string; + + /** + * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + */ + progressToken?: ProgressToken; + [key: string]: unknown; + }; + [key: string]: unknown; + }; + } +``` + + + +#### Unsupported Protocol Versions + +If a server receives a request with an unsupported protocol version, it MUST +return a JSON-RPC error response (400 Bad Request for HTTP). This response MUST +conform to the following interface: \ + + + +```ts +/** + * Defines the JSON-RPC error object returned for an + * unsupported protocol version. + */ +export interface UnsupportedVersionError extends Result { + error: { + /** + * MUST be -32000. + */ + code: -32000; + /** + * MUST be "Unsupported protocol version". + */ + message: "Unsupported protocol version"; + /** + * MUST contain an array of strings listing the + * protocol versions supported by the server. + * Example: ["2025-06-18", "2025-03-26"] + */ + data: { + supportedVersions: ["2025-06-18", "2025-03-26"] + }; + }; +} +``` + + + +### Optional Discovery for Server Capabilities + +To allow clients to adapt to different server implementations, this +specification introduces a **discovery RPC**. This provides a standard mechanism +for a server to advertise its supported protocol versions and capabilities. + +This discovery step is **OPTIONAL**. A client is free to invoke any RPC without +first calling the discovery endpoint. If a client calls an unsupported RPC, the +server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a +`Method not found` JSON-RPC error (`-32601`). + + + +--- + + + +#### `server/discover` RPC + + + +* **Purpose**: To allow a client to query the server for its supported + protocol versions, capabilities, and other metadata. + +**Request Schema:** + + +```ts +export interface DiscoveryRequest extends Request { + method: "server/discover"; + params?: {}; // No parameters are needed for a discovery request. +} +``` + + +**Response Schema:** + + +```ts +export interface DiscoveryResult extends Result { + /** + * A list of MCP Protocol Version strings that this server supports. + * The client should choose a version from this list for use in + * subsequent requests. + */ + supportedVersions: string[]; + + /** + * An object detailing the capabilities of the server. + */ + capabilities: ServerCapabilities; + + /** + * Information about the server software implementation. + */ + serverInfo: Implementation; + + /** + * Natural language instructions describing how to use the server and + * its features. This can be used by clients to improve an LLM's + * understanding of available tools (e.g., by including it in a system prompt). + */ + instructions?: string; +} +``` + + + + +--- + + + +### Specify Client Capabilities Per-Request + +To complete the decoupling from the initial handshake, client capabilities are +no longer negotiated once per session. Instead, a client **MAY** specify its +capabilities on a per-request basis. This allows the server to know what +optional features the client can handle for a specific transaction, such as +streaming responses. + +A server **SHOULD** only send requests that match a client's provided +capabilities. If a client's capabilities change and it wishes to update the +server, a client **SHOULD** send a new RPC. + +If a server sends a request that erroneously calls a client capability it +doesn't support, a client MUST return a Method not found JSON-RPC error +(-32601). + +The primary capability defined in this proposal is the ability to handle +streaming responses, which is supported through two distinct models: +server-initiated and client-initiated. + + +#### Schema Changes + + +##### Server-Initiated Streaming (Response Stream) + +This model applies when a client makes a standard RPC call and the server +responds back with an SSE stream. Rather than associate the client capabilities +with a session, the client may specify supported capabilities in the request. + +The client adds an optional `clientCapabilities` field to the `_meta` object of +its request. For the HTTP transport, a server that supports this **MAY** then +respond with an SSE stream for that transaction. + + +```ts + export interface Request { + // ... + _meta?: { + // ... other meta fields ++ /** ++ * Optional capabilities of the client for this specific request. ++ */ ++ modelcontextprotocol.io/clientCapabilities?: ClientCapabilities; + roots: [ + // ... list of roots + ], +"logLevel": "info" + // ... other meta fields + }; + // ... + } +``` + + + +##### Client-Initiated Streaming (Background Streaming) + +This model applies when a client wants to proactively open a persistent SSE +stream to receive multiple or unsolicited events. + +This is achieved using a **dedicated messages/listen RPC. +For the HTTP transport, the client sends this request via POST, and +the server's response is an open SSE stream, with a +MessagesListenNotification sent as the first event. For the STDIO +transport, this RPC is used for a simple request/response capabilities check. +This RPC replaces the existing /GET endpoint behavior for Streamable HTTP today. +\ + \ +Client-initiated streaming MAY be associated with a session. If +no session is provided, it's assumed the server is using them for unassociated +or unsolicited requests. + +**Request Schema:** + + +```ts +export interface MessagesListenRequest extends Request { + method: "messages/listen"; + params: { + _meta?: { + modelcontextprotocol.io/mcpProtocolVersion: string; + modelcontextprotocol.io/sessionId?: string; + modelcontextprotocol.io/clientCapabilities?: ClientCapabilities; + modelcontextprotocol.io/roots: [ + // ... list of roots + ], +"logLevel": "info" + // ... other meta fields + }; + }; +} +``` + + +**Response Schema:** + +```ts +export interface MessagesListenNotification extends Notification { + method: "notifications/messages/listen"; +} +``` + + + + +--- + + + +#### STDIO Transport Behavior + +For STDIO's simple request/response model, a client **MAY** send a +`MessagesListenRequest` at any time. The server **MUST** process it and reply +with a `MessagesListenNotification`. The interaction is complete after the +response is sent. + +The server **MAY** send any server to client messages or notifications for the +duration of the connection. + + + +--- + + + +#### Streamable HTTP Transport Behavior + +For HTTP, there are two distinct models for handling streaming: + +**1. Server-Initiated Streaming** + +To receive a streaming response for a single RPC call, the client **augments the +standard request** by including the `clientCapabilities` object in the `_meta` +field. The server **MAY** then respond with an SSE stream for that transaction. + +**2. Client-Initiated Streaming** + +To proactively open a persistent SSE stream, the client sends the dedicated +`MessagesListenRequest` via `POST`. The server's response **is an open SSE +stream** (`Content-Type: text/event-stream`), and the **first request** on this +stream **MUST** be an event containing the `MessagesListenNotification`. + + +### Changes to Initialization + +Initialization is no longer required to be the first interaction between client +and server. + +A server **MAY** use a stateful session associated with a client, to associate +arbitrary state across multiple interactions. A session does not provide any +specific guarantees on the state (e.g. protocol version and capabilities may +change at any time during the session) unless the server opts to do so. The +server **SHOULD** document or otherwise communicate to clients when they should +use sessions, and which state (if any) can be relied on by client behavior for +the duration of that session. + + +#### Initizalization + +A client MAY decide to perform the initialization phase to start a session. If a +server supports sessions, it MUST respond back with a `sessionId`. + + +``` +{ + "jsonrpc": "2.0", + "id": 1, + "result": { ++ "sessionId": "$SESSION_ID" + "protocolVersion": "2025-03-26", + "capabilities": { + "logging": {}, + "prompts": { + "listChanged": true + }, + "resources": { + "subscribe": true, + "listChanged": true + }, + "tools": { + "listChanged": true + } + }, + "serverInfo": { + "name": "ExampleServer", + "version": "1.0.0" + }, + "instructions": "Optional instructions for the client" + } +} +``` + + + +#### Per-request session + +A client **MUST** attach this to future requests associated with that session. +Sessions do not need to be associated with any particular connection. + + + +##### HTTP + +For the HTTP transport, session-id MUST be passed as **HTTP headers**. For the +HTTP transport, the headers MUST be treated as the source of truth over the +request payload. + + + +* `MCP-Session-Id: ` + * **Purpose**: To associate a request with an optional, logical session + that has been explicitly created on the server. + * **Requirement**: This header is **OPTIONAL**. Servers **MAY** reject + requests without this header if they require a session for a particular + request. + + +##### STDIO + +For the STDIO transport, where headers are not available, this metadata MUST be +embedded directly within the `_meta` field of the request payload. + +The following diff illustrates the required changes to the `Request` interface: + + +``` +export interface Request { + method: string; + params?: { + /** + * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { ++ * The optional ID for a logical session. ++ */ ++ modelcontextprotocol.io/sessionId?: string; + /** + * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + */ + progressToken?: ProgressToken; + [key: string]: unknown; + }; + [key: string]: unknown; + }; + } +``` + + + +#### Errors + +There are two new errors introduced by this SEP. + + +##### Invalid Sessions + +If a server receives a request with an invalid session id, it MUST return a +JSON-RPC error response (400 Bad Request for HTTP). This response MUST conform +to the following interface: \ + + + +``` +/** + * Defines the JSON-RPC error object returned for an + * invalid session ID. + */ +export interface UnsupportedVersionError extends Result { + error: { + /** + * MUST be -32001. + */ + code: -32001; + /** + * MUST be "Invalid Session ID". + */ + message: "Invalid Session ID"; + }; +} +``` + + + +##### Session Required + +If a server requires a valid session to respond to a specific request, it MUST +return a JSON-RPC error response (400 bad request). This response MUST conform +to the following interface: \ + + + +``` +/** + * Defines the JSON-RPC error object returned for an + * invalid session ID. + */ +export interface UnsupportedVersionError extends Result { + error: { + /** + * MUST be -32001. + */ + code: -32001; + /** + * MUST be "Invalid Session ID". + */ + message: "Invalid Session ID"; + }; +} +``` + + + +### Deprecated and Removed RPCs + +To simplify the protocol and align with the move to per-request capabilities, +the following RPC methods and notifications are deprecated and will be removed: + + + +* `logging/setLevel`: This method is removed. Log levels should now be + specified on a per-request basis using the + `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. +* `notifications/roots/list_changed`: This notification is removed. + Functionality requiring proactive updates from the server (like root list + changes) will be handled by server-initiated streaming. +* `notifications/initialized`: This notification is defined as a "no-op" (no + operation). Servers compliant with this SEP should accept and ignore this + notification without error to maintain backward compatibility with clients + that may send it. + + +## Rationale + + +### Stateless-First by Default + +The primary design decision of this SEP is to make the mandatory initialization +handshake optional, making stateless interaction the default model for the +protocol. This choice is rooted in the "pay as you go" principle and the desire +to align MCP with modern, cloud-native architecture. By making the simplest +interaction model the default, we lower the barrier to entry and reduce +implementation complexity for the most common use cases. This immediately +enables straightforward horizontal scaling and improves resilience, as any +request can be handled by any server instance. + + +#### Alternative Considered: Optional Handshake + +An alternative we considered was to keep the existing stateful handshake but +make it optional. In this model, a client could choose to either perform the +handshake to establish a persistent session or skip it and send self-contained +requests. + + +#### Why it was rejected: + +Supporting two parallel interaction models would have dramatically increased the +complexity of the protocol and every implementation. Servers and clients would +need to build, test, and maintain two separate logic paths, leading to a larger +surface area for bugs. It also violates the design principle of having one +clear, obvious way to perform a core function. By making a clean break, we +ensure the entire ecosystem can move forward and benefit from a simpler, more +scalable, and more robust foundation. + + +### Explicit Session Management + +This proposal originally included dedicated `sessions/create` and +`sessions/delete` RPCs to manage the lifecycle of a logical session. + +Due to a lack of consensus on this specific approach, these changes have been +removed from this SEP to allow the other core stateless-first changes to +proceed. Explicit session management will be revisited in a future SEP. + + +### Separation of Concerns + +A core principle of this proposal is the "unbundling" of the monolithic +initialization handshake into a suite of discrete, single-purpose RPCs. The +original handshake mixed the concerns of protocol negotiation, capability +discovery, and session management into a single, complex interaction. The new +design explicitly separates these: + + + +* **Discovery**: Handled exclusively by `server/discover`. +* **Capabilities**: Handled on a per-request basis via the `_meta` field or + the `messages/listen` RPC. + +The rationale for this is to create a more modular, flexible, and understandable +protocol. Each component now has a single, well-defined responsibility. This +allows clients to use only the parts of the protocol they need, adhering to our +"pay as you go" principle. + + +#### Alternative Considered: A Monolithic Handshake + +We could have kept a single, monolithic handshake RPC and simply added more +parameters and complex logic to it to support the stateless-first model. + + +#### Why it was rejected: + +A single, do-it-all RPC is difficult to implement, test, and evolve. It forces +all clients, even the simplest ones, to be aware of the protocol's most complex +features. By separating these concerns, we've made the protocol easier to learn +and implement correctly, while also making it more flexible and extensible for +the future. + + +## Backward Compatibility + +While this proposal attempts to preserve existing functionality and use-cases, +this proposal introduces a **fundamental, backward-incompatible change**. Thus, +it will require a new version of the protocol.. + + +### Supporting Multiple Versions + +While this SEP deprecates the `initialize` handshake, a server that wishes to +support both old and new clients **MAY** do so. Such a server can continue to +implement the old `initialize` RPC to handle legacy clients, while also exposing +the new stateless RPCs (`server/discover`, `sessions/create`, etc.) for updated +clients. + +Both servers and clients should be able to handle changes in the versions +appropriately. Two example scenarios are outlined below, where vPrev indicates +the version prior to the SEP, and vAfter indicates a version after it. + + +#### Client (supporting vPrev) → Server (vPrev, vPost) + + + +1. Client sends initialization +2. Server supports vPrev, so initialization is returned per spec +3. Client and server communicate per`vPrev`. + + +#### Client (supporting vPrev. vPost) → Server (vPrev) + + + +4. Client sends a request (e.g. list/tools) with MCP Protocol Version header + 1. HTTP: Server says "400 bad request" + 2. STDIO: returns error indicate initialization was required +5. Client falls back to vPrev (and makes initialization) for future requests + + +## Reference Implementation + +// TODO + + +## Security Implications + +While this proposal improves the protocol's clarity, implementations **may still +be vulnerable** to common exploits if not secured correctly. The following +points should be considered: + + + +* **Session Hijacking**: The `sessionId` acts as a bearer token. To prevent + interception and session hijacking, all communication **MUST** occur over an + encrypted transport like **TLS**. +* **Resource Exhaustion**: The `sessions/create` RPC is a potential vector for + Denial-of-Service attacks. Servers **SHOULD** protect this endpoint with + **rate-limiting and resource quotas**. + + +## FAQ + + +### What is protocol level statelessness? + +[Wikipedia](https://en.wikipedia.org/wiki/Stateless_protocol) defines a +stateless protocol as: + +> A stateless protocol is a communication protocol in which the receiver must +> not retain session state from previous requests. The sender transfers relevant +> session state to the receiver in such a way that every request can be +> understood in isolation, that is without reference to session state from +> previous requests retained by the receiver. + +This does NOT mean that you can't build stateful applications on top of a +stateless protocol. HTTP is an example of a stateless protocol, which most of +the web is built on today. However it does mean that the state cannot exist_ in +the protocol itself_, and should instead specify the state in the request (or +failing that, a reference to the state for the server or client to track). + + +### Does this make MCP a fully stateless protocol? + +Not entirely (hence 'by default'). Depending on your interpretation of +"requests", the SSE streams mentioned (both client-initiated and +server-initiated) tend to have multiple requests within a context of a stream. +However, these streams are constrained to a single HTTP request and optional to +use, meaning that the complexity is both constrained and optional to use when +the situation requires it. + + +### What is it important for STDIO to be stateless as well? + +The transport MCP is using should be an implementation detail only. If one +version of a protocol supports functionality that doesn't cleanly map over to +another version of the protocol, they are really two different protocols. + +This makes it easy for developers to switch their services from one transport to +another without needing to make significant changes to the behavior of their +applications, and easier to proxy between different transports correctly. +Otherwise, there will continue to be feature gaps and division between these +different implementations, leading to both confusion and incompatibility. From 8266c2ae64b4ffe58c02bb8028c222af36ab0867 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:22:14 -0600 Subject: [PATCH 02/69] chore: assign PR number 2575 to SEP --- seps/{0000-stateless-mcp.md => 2575-stateless-mcp.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename seps/{0000-stateless-mcp.md => 2575-stateless-mcp.md} (99%) diff --git a/seps/0000-stateless-mcp.md b/seps/2575-stateless-mcp.md similarity index 99% rename from seps/0000-stateless-mcp.md rename to seps/2575-stateless-mcp.md index db88953d4..8ab48c290 100644 --- a/seps/0000-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -1,11 +1,11 @@ -# SEP-0000: Stateless-by-Default MCP +# SEP-2575: Stateless-by-Default MCP - **Status**: Draft - **Type**: Standards Track - **Created**: 2025-06-18 - **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) - **Sponsor**: None (seeking sponsor) -- **PR**: TBD +- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575 ## Abstract From ef60c1100585807fe48d5d798a7b853c724ca27c Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:23:38 -0600 Subject: [PATCH 03/69] chore: set sponsor --- seps/2575-stateless-mcp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 8ab48c290..e0f748a9a 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -4,7 +4,7 @@ - **Type**: Standards Track - **Created**: 2025-06-18 - **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) -- **Sponsor**: None (seeking sponsor) +- **Sponsor**: Kurtis Van Gent (@kurtisvg) - **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575 ## Abstract From d02d153bf03eb4769d8cccf20e4d7200944b6bd1 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:46:22 -0600 Subject: [PATCH 04/69] feat: remove session content and clean up SEP Remove session-related material now covered by SEP-2322 and SEP-2567. Add version negotiation flow, per-request clientInfo, and updated security considerations. Fix formatting, typos, and code examples. --- seps/2575-stateless-mcp.md | 473 +++++++++++-------------------------- 1 file changed, 136 insertions(+), 337 deletions(-) diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index e0f748a9a..2489929dd 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -47,8 +47,6 @@ to understand subsequent ones. This is in direct opposition to the design of modern, cloud-native systems which favor stateless services for their resilience and scalability. - - 1. **Impediment to Scalability:** The most critical issue is the difficulty of load balancing stateful MCP. A simple stateless load balancer (e.g., L4/L7 round-robin) cannot be used, as it would route a client's requests to @@ -74,25 +72,21 @@ and scalability. disconnect. -### Guiding Principles - -This proposal is the first step toward establishing a "pay as you go" model for -protocol complexity. We will be guided by the following principles, in order of -preference: - +## Design Principles +This proposal establishes a "pay as you go" model for protocol complexity, +guided by the following principles in order of preference: 1. **Prioritize Stateless-ness:** Whenever possible, a request must be self-contained, providing all information the server needs to process it - without relying on session state from previous requests. + without relying on state from previous requests. 2. **Prefer State References:** If a fully stateless exchange is not practical, - references to state should be passed in every request. + references to state should be passed in every request. 3. **Treat Statefulness as a Last Resort:** The complexity of stateful logic and long-lived streaming connections should only be accepted when no simpler alternative exists to solve a critical use case. - -### The Impact on Key Transports +### Transport Consistency It is critical that these stateless principles are applied consistently across all transports. Keeping the `stdio` and `http` implementations in sync ensures a @@ -105,7 +99,6 @@ coherent protocol model is essential for a healthy ecosystem. ## Specification - ### Overview This specification fundamentally refactors the MCP interaction model to be @@ -113,43 +106,43 @@ This specification fundamentally refactors the MCP interaction model to be handshake before any resources can be exchanged. This handshake negotiates and establishes several key pieces of information: - - 1. MCP Protocol Version -2. Session ID (if the server supports sessions) -3. Server Capabilities (and - [serverInfo](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/69a292b16a64e086add82fd76fc6aaed68e47de0/schema/draft/schema.ts#L196)) -4. Client Capabilities (and - [clientInfo](?tab=t.xcggn0fi5c29#:~:text=also%20includes%20%60clientInfo%60%20(-,https%3A//github.com/modelcontextprotocol/modelcontextprotocol/blob/69a292b16a64e086add82fd76fc6aaed68e47de0/schema/draft/schema.ts%23L181,-)%20and%20%60serverInfo%60%20()) +2. Server Capabilities and `serverInfo` +3. Client Capabilities and `clientInfo` The requirement of this initialization handshake **enforces the establishment of a state** that is expected to persist for subsequent communication between client and server. Furthermore, by bundling these negotiations into a single initialization phase, the specification creates an implied link between them, -particularly between stateful sessions and the exchange of capabilities. +particularly between the exchange of capabilities and a mandatory connection +lifecycle. -This proposal is to **deprecate the initialization handshake** and "unbundle" +This proposal is to **remove the initialization handshake** and "unbundle" its functions into discrete, stateless components. We will provide new, more clearly defined mechanisms for clients and servers to exchange this information without a mandatory state-creating cycle. +> **Note:** Session management (both transport-level and application-level) is +> addressed separately by +> [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) +> and +> [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567). +> This SEP focuses exclusively on removing the initialization handshake and +> providing stateless alternatives for version negotiation, discovery, and +> capabilities. + ### Protocol Version To make requests self-contained, metadata previously negotiated during the -handshake must now be included with **every request**. - ---- - +handshake must now be included with **every request**. -#### HTTP +#### HTTP For the HTTP transport, protocol version MUST be passed as **HTTP header**. For the HTTP transport, the headers MUST be treated as the source of truth over the -request payload. - - +request payload. * `MCP-Protocol-Version: 2025-06-18` * **Purpose**: To inform the server which version of the MCP specification @@ -157,12 +150,7 @@ request payload. * **Requirement**: This header is **MANDATORY**. Servers should reject requests with a missing or unsupported version. * This header MUST match the value provided in the Request as specified - below. - - - ---- - + below. #### Per-request Version @@ -204,39 +192,54 @@ export interface Request { If a server receives a request with an unsupported protocol version, it MUST return a JSON-RPC error response (400 Bad Request for HTTP). This response MUST -conform to the following interface: \ - - +conform to the following structure: ```ts /** - * Defines the JSON-RPC error object returned for an - * unsupported protocol version. + * JSON-RPC error response returned when the client requests + * an unsupported protocol version. */ -export interface UnsupportedVersionError extends Result { - error: { +{ + "jsonrpc": "2.0", + "id": 1, + "error": { /** * MUST be -32000. */ - code: -32000; + "code": -32000, /** * MUST be "Unsupported protocol version". */ - message: "Unsupported protocol version"; + "message": "Unsupported protocol version", /** * MUST contain an array of strings listing the * protocol versions supported by the server. - * Example: ["2025-06-18", "2025-03-26"] */ - data: { - supportedVersions: ["2025-06-18", "2025-03-26"] - }; - }; + "data": { + "supportedVersions": ["2025-06-18", "2025-03-26"] + } + } } ``` +#### Version Negotiation Flow + +Without an initialization handshake, version negotiation happens inline: + +1. The client sends a request with its preferred protocol version in the + `MCP-Protocol-Version` header and + `modelcontextprotocol.io/mcpProtocolVersion` `_meta` field. +2. If the server supports that version, it processes the request normally. +3. If the server does not support the requested version, it returns an + `UnsupportedVersionError` containing its list of `supportedVersions`. +4. The client selects a mutually supported version from the list and retries. + +Alternatively, a client **MAY** call `server/discover` first to learn the +server's supported versions before sending any other requests. + + ### Optional Discovery for Server Capabilities To allow clients to adapt to different server implementations, this @@ -249,15 +252,8 @@ server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a `Method not found` JSON-RPC error (`-32601`). - ---- - - - #### `server/discover` RPC - - * **Purpose**: To allow a client to query the server for its supported protocol versions, capabilities, and other metadata. @@ -304,16 +300,10 @@ export interface DiscoveryResult extends Result { ``` - - ---- - - - -### Specify Client Capabilities Per-Request +### Per-Request Client Capabilities To complete the decoupling from the initial handshake, client capabilities are -no longer negotiated once per session. Instead, a client **MAY** specify its +no longer negotiated once at initialization. Instead, a client **MAY** specify its capabilities on a per-request basis. This allows the server to know what optional features the client can handle for a specific transaction, such as streaming responses. @@ -323,22 +313,26 @@ capabilities. If a client's capabilities change and it wishes to update the server, a client **SHOULD** send a new RPC. If a server sends a request that erroneously calls a client capability it -doesn't support, a client MUST return a Method not found JSON-RPC error -(-32601). +doesn't support, a client MUST return a `Method not found` JSON-RPC error +(`-32601`). + +Client identity information (`clientInfo`) **MAY** also be included in the +per-request `_meta` field, allowing servers to identify the client without +requiring an initialization handshake. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: server-initiated and client-initiated. -#### Schema Changes +#### Streaming Models ##### Server-Initiated Streaming (Response Stream) This model applies when a client makes a standard RPC call and the server -responds back with an SSE stream. Rather than associate the client capabilities -with a session, the client may specify supported capabilities in the request. +responds back with an SSE stream. The client specifies supported capabilities +directly in the request. The client adds an optional `clientCapabilities` field to the `_meta` object of its request. For the HTTP transport, a server that supports this **MAY** then @@ -346,22 +340,18 @@ respond with an SSE stream for that transaction. ```ts - export interface Request { - // ... - _meta?: { - // ... other meta fields -+ /** -+ * Optional capabilities of the client for this specific request. -+ */ -+ modelcontextprotocol.io/clientCapabilities?: ClientCapabilities; - roots: [ - // ... list of roots - ], -"logLevel": "info" - // ... other meta fields - }; - // ... - } +export interface Request { + // ... + _meta?: { + // ... other meta fields ++ /** ++ * Optional capabilities of the client for this specific request. ++ */ ++ "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; + // ... other meta fields + }; + // ... +} ``` @@ -371,17 +361,12 @@ respond with an SSE stream for that transaction. This model applies when a client wants to proactively open a persistent SSE stream to receive multiple or unsolicited events. -This is achieved using a **dedicated messages/listen RPC. -For the HTTP transport, the client sends this request via POST, and -the server's response is an open SSE stream, with a -MessagesListenNotification sent as the first event. For the STDIO -transport, this RPC is used for a simple request/response capabilities check. -This RPC replaces the existing /GET endpoint behavior for Streamable HTTP today. -\ - \ -Client-initiated streaming MAY be associated with a session. If -no session is provided, it's assumed the server is using them for unassociated -or unsolicited requests. +This is achieved using a dedicated `messages/listen` RPC. For the HTTP +transport, the client sends this request via `POST`, and the server's response +is an open SSE stream, with a `MessagesListenNotification` sent as the first +event. For the STDIO transport, this RPC is used for a simple request/response +capabilities check. This RPC replaces the existing GET endpoint behavior for +Streamable HTTP today. **Request Schema:** @@ -391,13 +376,9 @@ export interface MessagesListenRequest extends Request { method: "messages/listen"; params: { _meta?: { - modelcontextprotocol.io/mcpProtocolVersion: string; - modelcontextprotocol.io/sessionId?: string; - modelcontextprotocol.io/clientCapabilities?: ClientCapabilities; - modelcontextprotocol.io/roots: [ - // ... list of roots - ], -"logLevel": "info" + "modelcontextprotocol.io/mcpProtocolVersion": string; + "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; + "modelcontextprotocol.io/roots"?: Root[]; // ... other meta fields }; }; @@ -414,12 +395,6 @@ export interface MessagesListenNotification extends Notification { ``` - - ---- - - - #### STDIO Transport Behavior For STDIO's simple request/response model, a client **MAY** send a @@ -428,12 +403,7 @@ with a `MessagesListenNotification`. The interaction is complete after the response is sent. The server **MAY** send any server to client messages or notifications for the -duration of the connection. - - - ---- - +duration of the connection. #### Streamable HTTP Transport Behavior @@ -454,203 +424,33 @@ stream** (`Content-Type: text/event-stream`), and the **first request** on this stream **MUST** be an event containing the `MessagesListenNotification`. -### Changes to Initialization - -Initialization is no longer required to be the first interaction between client -and server. - -A server **MAY** use a stateful session associated with a client, to associate -arbitrary state across multiple interactions. A session does not provide any -specific guarantees on the state (e.g. protocol version and capabilities may -change at any time during the session) unless the server opts to do so. The -server **SHOULD** document or otherwise communicate to clients when they should -use sessions, and which state (if any) can be relied on by client behavior for -the duration of that session. - - -#### Initizalization - -A client MAY decide to perform the initialization phase to start a session. If a -server supports sessions, it MUST respond back with a `sessionId`. - - -``` -{ - "jsonrpc": "2.0", - "id": 1, - "result": { -+ "sessionId": "$SESSION_ID" - "protocolVersion": "2025-03-26", - "capabilities": { - "logging": {}, - "prompts": { - "listChanged": true - }, - "resources": { - "subscribe": true, - "listChanged": true - }, - "tools": { - "listChanged": true - } - }, - "serverInfo": { - "name": "ExampleServer", - "version": "1.0.0" - }, - "instructions": "Optional instructions for the client" - } -} -``` - - - -#### Per-request session - -A client **MUST** attach this to future requests associated with that session. -Sessions do not need to be associated with any particular connection. - - - -##### HTTP - -For the HTTP transport, session-id MUST be passed as **HTTP headers**. For the -HTTP transport, the headers MUST be treated as the source of truth over the -request payload. - - - -* `MCP-Session-Id: ` - * **Purpose**: To associate a request with an optional, logical session - that has been explicitly created on the server. - * **Requirement**: This header is **OPTIONAL**. Servers **MAY** reject - requests without this header if they require a session for a particular - request. - - -##### STDIO - -For the STDIO transport, where headers are not available, this metadata MUST be -embedded directly within the `_meta` field of the request payload. - -The following diff illustrates the required changes to the `Request` interface: - - -``` -export interface Request { - method: string; - params?: { - /** - * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. - */ - _meta?: { -+ * The optional ID for a logical session. -+ */ -+ modelcontextprotocol.io/sessionId?: string; - /** - * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. - */ - progressToken?: ProgressToken; - [key: string]: unknown; - }; - [key: string]: unknown; - }; - } -``` - - - -#### Errors - -There are two new errors introduced by this SEP. - - -##### Invalid Sessions - -If a server receives a request with an invalid session id, it MUST return a -JSON-RPC error response (400 Bad Request for HTTP). This response MUST conform -to the following interface: \ - - - -``` -/** - * Defines the JSON-RPC error object returned for an - * invalid session ID. - */ -export interface UnsupportedVersionError extends Result { - error: { - /** - * MUST be -32001. - */ - code: -32001; - /** - * MUST be "Invalid Session ID". - */ - message: "Invalid Session ID"; - }; -} -``` - - - -##### Session Required - -If a server requires a valid session to respond to a specific request, it MUST -return a JSON-RPC error response (400 bad request). This response MUST conform -to the following interface: \ - - - -``` -/** - * Defines the JSON-RPC error object returned for an - * invalid session ID. - */ -export interface UnsupportedVersionError extends Result { - error: { - /** - * MUST be -32001. - */ - code: -32001; - /** - * MUST be "Invalid Session ID". - */ - message: "Invalid Session ID"; - }; -} -``` - - - ### Deprecated and Removed RPCs To simplify the protocol and align with the move to per-request capabilities, -the following RPC methods and notifications are deprecated and will be removed: - - - +the following RPC methods and notifications are removed: + +* `initialize` / `notifications/initialized`: The initialization handshake is + removed. Version negotiation is handled per-request via + `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is + handled by `server/discover`. Servers compliant with this SEP **SHOULD** + accept and ignore `notifications/initialized` without error to maintain + backward compatibility with clients that may send it. * `logging/setLevel`: This method is removed. Log levels should now be specified on a per-request basis using the `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. -* `notifications/roots/list_changed`: This notification is removed. - Functionality requiring proactive updates from the server (like root list - changes) will be handled by server-initiated streaming. -* `notifications/initialized`: This notification is defined as a "no-op" (no - operation). Servers compliant with this SEP should accept and ignore this - notification without error to maintain backward compatibility with clients - that may send it. +* `notifications/roots/list_changed`: This notification is removed. Clients + now provide their current roots directly in per-request `_meta` fields, + making server-side tracking of root changes unnecessary. ## Rationale - ### Stateless-First by Default -The primary design decision of this SEP is to make the mandatory initialization -handshake optional, making stateless interaction the default model for the -protocol. This choice is rooted in the "pay as you go" principle and the desire -to align MCP with modern, cloud-native architecture. By making the simplest +The primary design decision of this SEP is to remove the mandatory initialization +handshake, making stateless interaction the default model for the protocol. This +choice is rooted in the "pay as you go" principle and the desire to align MCP +with modern, cloud-native architecture. By making the simplest interaction model the default, we lower the barrier to entry and reduce implementation complexity for the most common use cases. This immediately enables straightforward horizontal scaling and improves resilience, as any @@ -681,20 +481,21 @@ scalable, and more robust foundation. This proposal originally included dedicated `sessions/create` and `sessions/delete` RPCs to manage the lifecycle of a logical session. -Due to a lack of consensus on this specific approach, these changes have been -removed from this SEP to allow the other core stateless-first changes to -proceed. Explicit session management will be revisited in a future SEP. +Session management is now addressed separately by +[SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567), +which proposes removing sessions entirely and replacing them with explicit +state handles. This aligns with the +[sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) +made by the Transports Working Group. ### Separation of Concerns A core principle of this proposal is the "unbundling" of the monolithic initialization handshake into a suite of discrete, single-purpose RPCs. The -original handshake mixed the concerns of protocol negotiation, capability -discovery, and session management into a single, complex interaction. The new -design explicitly separates these: - - +original handshake mixed the concerns of protocol negotiation and capability +discovery into a single, complex interaction. The new design explicitly +separates these: * **Discovery**: Handled exclusively by `server/discover`. * **Capabilities**: Handled on a per-request basis via the `_meta` field or @@ -725,16 +526,15 @@ the future. While this proposal attempts to preserve existing functionality and use-cases, this proposal introduces a **fundamental, backward-incompatible change**. Thus, -it will require a new version of the protocol.. +it will require a new version of the protocol. ### Supporting Multiple Versions -While this SEP deprecates the `initialize` handshake, a server that wishes to +While this SEP removes the `initialize` handshake, a server that wishes to support both old and new clients **MAY** do so. Such a server can continue to implement the old `initialize` RPC to handle legacy clients, while also exposing -the new stateless RPCs (`server/discover`, `sessions/create`, etc.) for updated -clients. +the new stateless RPCs (`server/discover`, etc.) for updated clients. Both servers and clients should be able to handle changes in the versions appropriately. Two example scenarios are outlined below, where vPrev indicates @@ -743,26 +543,17 @@ the version prior to the SEP, and vAfter indicates a version after it. #### Client (supporting vPrev) → Server (vPrev, vPost) - - -1. Client sends initialization +1. Client sends initialization 2. Server supports vPrev, so initialization is returned per spec -3. Client and server communicate per`vPrev`. +3. Client and server communicate per `vPrev`. -#### Client (supporting vPrev. vPost) → Server (vPrev) +#### Client (supporting vPrev, vPost) → Server (vPrev) - - -4. Client sends a request (e.g. list/tools) with MCP Protocol Version header +1. Client sends a request (e.g. tools/list) with MCP Protocol Version header 1. HTTP: Server says "400 bad request" - 2. STDIO: returns error indicate initialization was required -5. Client falls back to vPrev (and makes initialization) for future requests - - -## Reference Implementation - -// TODO + 2. STDIO: returns error indicating initialization was required +2. Client falls back to vPrev (and makes initialization) for future requests ## Security Implications @@ -771,14 +562,22 @@ While this proposal improves the protocol's clarity, implementations **may still be vulnerable** to common exploits if not secured correctly. The following points should be considered: +* **Per-request Authentication**: Without a session handshake, every request + must be independently authenticated and authorized. Implementations + **MUST** ensure that authentication is not bypassed by the removal of the + initialization phase. +* **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used + for reconnaissance. Servers **SHOULD** protect this endpoint with + **rate-limiting**. +* **Protocol Version Downgrade**: An attacker could forge + `UnsupportedVersionError` responses to force a client to use an older, + potentially less secure protocol version. All communication **MUST** occur + over an encrypted transport like **TLS** to prevent this. -* **Session Hijacking**: The `sessionId` acts as a bearer token. To prevent - interception and session hijacking, all communication **MUST** occur over an - encrypted transport like **TLS**. -* **Resource Exhaustion**: The `sessions/create` RPC is a potential vector for - Denial-of-Service attacks. Servers **SHOULD** protect this endpoint with - **rate-limiting and resource quotas**. +## Reference Implementation + +// TODO ## FAQ @@ -797,8 +596,8 @@ stateless protocol as: This does NOT mean that you can't build stateful applications on top of a stateless protocol. HTTP is an example of a stateless protocol, which most of -the web is built on today. However it does mean that the state cannot exist_ in -the protocol itself_, and should instead specify the state in the request (or +the web is built on today. However it does mean that the state cannot exist *in +the protocol itself*, and should instead specify the state in the request (or failing that, a reference to the state for the server or client to track). @@ -812,7 +611,7 @@ use, meaning that the complexity is both constrained and optional to use when the situation requires it. -### What is it important for STDIO to be stateless as well? +### Why is it important for STDIO to be stateless as well? The transport MCP is using should be an implementation detail only. If one version of a protocol supports functionality that doesn't cleanly map over to From 74f0418f96668c17505076a5ea9f303c11cee603 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:48:07 -0600 Subject: [PATCH 05/69] chore: generate SEP docs --- docs/docs.json | 6 + docs/seps/2575-stateless-mcp.mdx | 598 +++++++++++++++++++++++++++++++ docs/seps/index.mdx | 2 + 3 files changed, 606 insertions(+) create mode 100644 docs/seps/2575-stateless-mcp.mdx diff --git a/docs/docs.json b/docs/docs.json index 0201f80a3..1f721a3db 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -431,6 +431,12 @@ "seps/2260-Require-Server-requests-to-be-associated-with-Client-requests" ] }, + { + "group": "Draft", + "pages": [ + "seps/2575-stateless-mcp" + ] + }, { "group": "Approved", "pages": [ diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx new file mode 100644 index 000000000..dcede133e --- /dev/null +++ b/docs/seps/2575-stateless-mcp.mdx @@ -0,0 +1,598 @@ +--- +title: "SEP-2575: Stateless-by-Default MCP" +sidebarTitle: "SEP-2575: Stateless-by-Default MCP" +description: "Stateless-by-Default MCP" +--- + +
+ + Draft + + + Standards Track + +
+ +| Field | Value | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **SEP** | 2575 | +| **Title** | Stateless-by-Default MCP | +| **Status** | Draft | +| **Type** | Standards Track | +| **Created** | 2025-06-18 | +| **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Mark Roth ([@markdroth](https://github.com/markdroth)), Shaun Smith ([@evalstate](https://github.com/evalstate)), Harvey Tuch ([@htuch](https://github.com/htuch)), Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) | +| **Sponsor** | Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) | +| **PR** | [#2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) | + +--- + +## Abstract + +A truly stateless protocol, where every request is self-contained and can be +understood in isolation, is highly desirable for its inherent simplicity, +scalability, and reliability. The current Model Context Protocol (MCP) is not +stateless by default. The specification requires an initialization handshake +that establishes a session state between the client and server, which persists +for the duration of the connection. + +This inherent statefulness makes it difficult to run MCP at scale. Placing an +MCP server behind a standard load balancer, for example, is challenging because +a client's session is coupled to the specific server instance holding its state. + +This proposal outlines a series of changes to **enable stateless MCP as the +default**, embracing a "pay as you go" model for protocol complexity and state. +Under this model, we provide simple, stateless features by default and only +introduce the overhead of stateful, long-lived connections for cases where that +functionality is actually required. + +Specifically, this SEP proposes removing the state-establishing initialization +handshake and replacing it with discrete, stateless alternatives. This initial +step allows each request to be processed independently, simplifying server-side +logic and paving the way for robust, scalable deployments. + +## Motivation + +The Model Context Protocol (MCP) specification currently mandates a stateful +initialization handshake. This design choice creates significant challenges for +scalability, reliability, and implementation simplicity. This SEP is motivated +by the need to address these shortcomings. + +### The Problem with Statefulness + +The core issue is that a server must retain session state from previous requests +to understand subsequent ones. This is in direct opposition to the design of +modern, cloud-native systems which favor stateless services for their resilience +and scalability. + +1. **Impediment to Scalability:** The most critical issue is the difficulty of + load balancing stateful MCP. A simple stateless load balancer (e.g., L4/L7 + round-robin) cannot be used, as it would route a client's requests to + different backend servers, none of which would have the correct session + state. Operators are forced to implement complex and fragile solutions like + sticky sessions, which bind a client to a specific server. This complicates + infrastructure, can lead to uneven load distribution, and makes horizontally + scaling the service non-trivial. +2. **Poor Resilience and Fault Tolerance:** In a stateful model, if the specific + server instance handling a client session fails, that session state is lost. + The client must detect the connection failure, re-establish a connection + (likely to a new server instance via the load balancer), and perform the + entire initialization handshake again. This process is disruptive and + inefficient, adding complexity around "resumability". +3. **Increased Implementation Complexity:** The current model imposes a + significant burden on developers. + - **Server-side:** Developers must implement logic to create, manage, and + eventually garbage-collect per-client session state. This is a common + source of bugs and memory leaks. + - **Client-side:** Developers must write complex code to manage a + persistent connection and handle the inevitable network failures and + reconnections, including the logic to resynchronize state after a + disconnect. + +## Design Principles + +This proposal establishes a "pay as you go" model for protocol complexity, +guided by the following principles in order of preference: + +1. **Prioritize Stateless-ness:** Whenever possible, a request must be + self-contained, providing all information the server needs to process it + without relying on state from previous requests. +2. **Prefer State References:** If a fully stateless exchange is not practical, + references to state should be passed in every request. +3. **Treat Statefulness as a Last Resort:** The complexity of stateful logic and + long-lived streaming connections should only be accepted when no simpler + alternative exists to solve a critical use case. + +### Transport Consistency + +It is critical that these stateless principles are applied consistently across +all transports. Keeping the `stdio` and `http` implementations in sync ensures a +**unified developer experience**, allowing the core protocol semantics to be +learned once and applied everywhere. This consistency simplifies the creation of +transport-agnostic libraries and tooling, and prevents protocol fragmentation +where different transports behave in fundamentally different ways. A single, +coherent protocol model is essential for a healthy ecosystem. + +## Specification + +### Overview + +This specification fundamentally refactors the MCP interaction model to be +**stateless-first**. Currently, MCP requires a mandatory 3-way initialization +handshake before any resources can be exchanged. This handshake negotiates and +establishes several key pieces of information: + +1. MCP Protocol Version +2. Server Capabilities and `serverInfo` +3. Client Capabilities and `clientInfo` + +The requirement of this initialization handshake **enforces the establishment of +a state** that is expected to persist for subsequent communication between +client and server. Furthermore, by bundling these negotiations into a single +initialization phase, the specification creates an implied link between them, +particularly between the exchange of capabilities and a mandatory connection +lifecycle. + +This proposal is to **remove the initialization handshake** and "unbundle" +its functions into discrete, stateless components. We will provide new, more +clearly defined mechanisms for clients and servers to exchange this information +without a mandatory state-creating cycle. + +> **Note:** Session management (both transport-level and application-level) is +> addressed separately by +> [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) +> and +> [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567). +> This SEP focuses exclusively on removing the initialization handshake and +> providing stateless alternatives for version negotiation, discovery, and +> capabilities. + +### Protocol Version + +To make requests self-contained, metadata previously negotiated during the +handshake must now be included with **every request**. + +#### HTTP + +For the HTTP transport, protocol version MUST be passed as **HTTP header**. For +the HTTP transport, the headers MUST be treated as the source of truth over the +request payload. + +- `MCP-Protocol-Version: 2025-06-18` + - **Purpose**: To inform the server which version of the MCP specification + the client is using for this specific request. + - **Requirement**: This header is **MANDATORY**. Servers should reject + requests with a missing or unsupported version. + - This header MUST match the value provided in the Request as specified + below. + +#### Per-request Version + +The `protocol-version` MUST be embedded directly within the `_meta` field of the +request payload. For HTTP, this \_meta MUST match the associated HTTP header, or +else the server should return a 400 Bad Request. + +The following diff illustrates the required changes to the `Request` interface: + +```ts +export interface Request { + method: string; + params?: { + /** + * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { ++ /** ++ * The MCP Protocol Version being used for this request. ++ */ ++ modelcontextprotocol.io/mcpProtocolVersion: string; + + /** + * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + */ + progressToken?: ProgressToken; + [key: string]: unknown; + }; + [key: string]: unknown; + }; + } +``` + +#### Unsupported Protocol Versions + +If a server receives a request with an unsupported protocol version, it MUST +return a JSON-RPC error response (400 Bad Request for HTTP). This response MUST +conform to the following structure: + +```ts +/** + * JSON-RPC error response returned when the client requests + * an unsupported protocol version. + */ +{ + "jsonrpc": "2.0", + "id": 1, + "error": { + /** + * MUST be -32000. + */ + "code": -32000, + /** + * MUST be "Unsupported protocol version". + */ + "message": "Unsupported protocol version", + /** + * MUST contain an array of strings listing the + * protocol versions supported by the server. + */ + "data": { + "supportedVersions": ["2025-06-18", "2025-03-26"] + } + } +} +``` + +#### Version Negotiation Flow + +Without an initialization handshake, version negotiation happens inline: + +1. The client sends a request with its preferred protocol version in the + `MCP-Protocol-Version` header and + `modelcontextprotocol.io/mcpProtocolVersion` `_meta` field. +2. If the server supports that version, it processes the request normally. +3. If the server does not support the requested version, it returns an + `UnsupportedVersionError` containing its list of `supportedVersions`. +4. The client selects a mutually supported version from the list and retries. + +Alternatively, a client **MAY** call `server/discover` first to learn the +server's supported versions before sending any other requests. + +### Optional Discovery for Server Capabilities + +To allow clients to adapt to different server implementations, this +specification introduces a **discovery RPC**. This provides a standard mechanism +for a server to advertise its supported protocol versions and capabilities. + +This discovery step is **OPTIONAL**. A client is free to invoke any RPC without +first calling the discovery endpoint. If a client calls an unsupported RPC, the +server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a +`Method not found` JSON-RPC error (`-32601`). + +#### `server/discover` RPC + +- **Purpose**: To allow a client to query the server for its supported + protocol versions, capabilities, and other metadata. + +**Request Schema:** + +```ts +export interface DiscoveryRequest extends Request { + method: "server/discover"; + params?: {}; // No parameters are needed for a discovery request. +} +``` + +**Response Schema:** + +```ts +export interface DiscoveryResult extends Result { + /** + * A list of MCP Protocol Version strings that this server supports. + * The client should choose a version from this list for use in + * subsequent requests. + */ + supportedVersions: string[]; + + /** + * An object detailing the capabilities of the server. + */ + capabilities: ServerCapabilities; + + /** + * Information about the server software implementation. + */ + serverInfo: Implementation; + + /** + * Natural language instructions describing how to use the server and + * its features. This can be used by clients to improve an LLM's + * understanding of available tools (e.g., by including it in a system prompt). + */ + instructions?: string; +} +``` + +### Per-Request Client Capabilities + +To complete the decoupling from the initial handshake, client capabilities are +no longer negotiated once at initialization. Instead, a client **MAY** specify its +capabilities on a per-request basis. This allows the server to know what +optional features the client can handle for a specific transaction, such as +streaming responses. + +A server **SHOULD** only send requests that match a client's provided +capabilities. If a client's capabilities change and it wishes to update the +server, a client **SHOULD** send a new RPC. + +If a server sends a request that erroneously calls a client capability it +doesn't support, a client MUST return a `Method not found` JSON-RPC error +(`-32601`). + +Client identity information (`clientInfo`) **MAY** also be included in the +per-request `_meta` field, allowing servers to identify the client without +requiring an initialization handshake. + +The primary capability defined in this proposal is the ability to handle +streaming responses, which is supported through two distinct models: +server-initiated and client-initiated. + +#### Streaming Models + +##### Server-Initiated Streaming (Response Stream) + +This model applies when a client makes a standard RPC call and the server +responds back with an SSE stream. The client specifies supported capabilities +directly in the request. + +The client adds an optional `clientCapabilities` field to the `_meta` object of +its request. For the HTTP transport, a server that supports this **MAY** then +respond with an SSE stream for that transaction. + +```ts +export interface Request { + // ... + _meta?: { + // ... other meta fields ++ /** ++ * Optional capabilities of the client for this specific request. ++ */ ++ "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; + // ... other meta fields + }; + // ... +} +``` + +##### Client-Initiated Streaming (Background Streaming) + +This model applies when a client wants to proactively open a persistent SSE +stream to receive multiple or unsolicited events. + +This is achieved using a dedicated `messages/listen` RPC. For the HTTP +transport, the client sends this request via `POST`, and the server's response +is an open SSE stream, with a `MessagesListenNotification` sent as the first +event. For the STDIO transport, this RPC is used for a simple request/response +capabilities check. This RPC replaces the existing GET endpoint behavior for +Streamable HTTP today. + +**Request Schema:** + +```ts +export interface MessagesListenRequest extends Request { + method: "messages/listen"; + params: { + _meta?: { + "modelcontextprotocol.io/mcpProtocolVersion": string; + "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; + "modelcontextprotocol.io/roots"?: Root[]; + // ... other meta fields + }; + }; +} +``` + +**Response Schema:** + +```ts +export interface MessagesListenNotification extends Notification { + method: "notifications/messages/listen"; +} +``` + +#### STDIO Transport Behavior + +For STDIO's simple request/response model, a client **MAY** send a +`MessagesListenRequest` at any time. The server **MUST** process it and reply +with a `MessagesListenNotification`. The interaction is complete after the +response is sent. + +The server **MAY** send any server to client messages or notifications for the +duration of the connection. + +#### Streamable HTTP Transport Behavior + +For HTTP, there are two distinct models for handling streaming: + +**1. Server-Initiated Streaming** + +To receive a streaming response for a single RPC call, the client **augments the +standard request** by including the `clientCapabilities` object in the `_meta` +field. The server **MAY** then respond with an SSE stream for that transaction. + +**2. Client-Initiated Streaming** + +To proactively open a persistent SSE stream, the client sends the dedicated +`MessagesListenRequest` via `POST`. The server's response **is an open SSE +stream** (`Content-Type: text/event-stream`), and the **first request** on this +stream **MUST** be an event containing the `MessagesListenNotification`. + +### Deprecated and Removed RPCs + +To simplify the protocol and align with the move to per-request capabilities, +the following RPC methods and notifications are removed: + +- `initialize` / `notifications/initialized`: The initialization handshake is + removed. Version negotiation is handled per-request via + `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is + handled by `server/discover`. Servers compliant with this SEP **SHOULD** + accept and ignore `notifications/initialized` without error to maintain + backward compatibility with clients that may send it. +- `logging/setLevel`: This method is removed. Log levels should now be + specified on a per-request basis using the + `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. +- `notifications/roots/list_changed`: This notification is removed. Clients + now provide their current roots directly in per-request `_meta` fields, + making server-side tracking of root changes unnecessary. + +## Rationale + +### Stateless-First by Default + +The primary design decision of this SEP is to remove the mandatory initialization +handshake, making stateless interaction the default model for the protocol. This +choice is rooted in the "pay as you go" principle and the desire to align MCP +with modern, cloud-native architecture. By making the simplest +interaction model the default, we lower the barrier to entry and reduce +implementation complexity for the most common use cases. This immediately +enables straightforward horizontal scaling and improves resilience, as any +request can be handled by any server instance. + +#### Alternative Considered: Optional Handshake + +An alternative we considered was to keep the existing stateful handshake but +make it optional. In this model, a client could choose to either perform the +handshake to establish a persistent session or skip it and send self-contained +requests. + +#### Why it was rejected: + +Supporting two parallel interaction models would have dramatically increased the +complexity of the protocol and every implementation. Servers and clients would +need to build, test, and maintain two separate logic paths, leading to a larger +surface area for bugs. It also violates the design principle of having one +clear, obvious way to perform a core function. By making a clean break, we +ensure the entire ecosystem can move forward and benefit from a simpler, more +scalable, and more robust foundation. + +### Explicit Session Management + +This proposal originally included dedicated `sessions/create` and +`sessions/delete` RPCs to manage the lifecycle of a logical session. + +Session management is now addressed separately by +[SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567), +which proposes removing sessions entirely and replacing them with explicit +state handles. This aligns with the +[sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) +made by the Transports Working Group. + +### Separation of Concerns + +A core principle of this proposal is the "unbundling" of the monolithic +initialization handshake into a suite of discrete, single-purpose RPCs. The +original handshake mixed the concerns of protocol negotiation and capability +discovery into a single, complex interaction. The new design explicitly +separates these: + +- **Discovery**: Handled exclusively by `server/discover`. +- **Capabilities**: Handled on a per-request basis via the `_meta` field or + the `messages/listen` RPC. + +The rationale for this is to create a more modular, flexible, and understandable +protocol. Each component now has a single, well-defined responsibility. This +allows clients to use only the parts of the protocol they need, adhering to our +"pay as you go" principle. + +#### Alternative Considered: A Monolithic Handshake + +We could have kept a single, monolithic handshake RPC and simply added more +parameters and complex logic to it to support the stateless-first model. + +#### Why it was rejected: + +A single, do-it-all RPC is difficult to implement, test, and evolve. It forces +all clients, even the simplest ones, to be aware of the protocol's most complex +features. By separating these concerns, we've made the protocol easier to learn +and implement correctly, while also making it more flexible and extensible for +the future. + +## Backward Compatibility + +While this proposal attempts to preserve existing functionality and use-cases, +this proposal introduces a **fundamental, backward-incompatible change**. Thus, +it will require a new version of the protocol. + +### Supporting Multiple Versions + +While this SEP removes the `initialize` handshake, a server that wishes to +support both old and new clients **MAY** do so. Such a server can continue to +implement the old `initialize` RPC to handle legacy clients, while also exposing +the new stateless RPCs (`server/discover`, etc.) for updated clients. + +Both servers and clients should be able to handle changes in the versions +appropriately. Two example scenarios are outlined below, where vPrev indicates +the version prior to the SEP, and vAfter indicates a version after it. + +#### Client (supporting vPrev) → Server (vPrev, vPost) + +1. Client sends initialization +2. Server supports vPrev, so initialization is returned per spec +3. Client and server communicate per `vPrev`. + +#### Client (supporting vPrev, vPost) → Server (vPrev) + +1. Client sends a request (e.g. tools/list) with MCP Protocol Version header + 1. HTTP: Server says "400 bad request" + 2. STDIO: returns error indicating initialization was required +2. Client falls back to vPrev (and makes initialization) for future requests + +## Security Implications + +While this proposal improves the protocol's clarity, implementations **may still +be vulnerable** to common exploits if not secured correctly. The following +points should be considered: + +- **Per-request Authentication**: Without a session handshake, every request + must be independently authenticated and authorized. Implementations + **MUST** ensure that authentication is not bypassed by the removal of the + initialization phase. +- **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used + for reconnaissance. Servers **SHOULD** protect this endpoint with + **rate-limiting**. +- **Protocol Version Downgrade**: An attacker could forge + `UnsupportedVersionError` responses to force a client to use an older, + potentially less secure protocol version. All communication **MUST** occur + over an encrypted transport like **TLS** to prevent this. + +## Reference Implementation + +// TODO + +## FAQ + +### What is protocol level statelessness? + +[Wikipedia](https://en.wikipedia.org/wiki/Stateless_protocol) defines a +stateless protocol as: + +> A stateless protocol is a communication protocol in which the receiver must +> not retain session state from previous requests. The sender transfers relevant +> session state to the receiver in such a way that every request can be +> understood in isolation, that is without reference to session state from +> previous requests retained by the receiver. + +This does NOT mean that you can't build stateful applications on top of a +stateless protocol. HTTP is an example of a stateless protocol, which most of +the web is built on today. However it does mean that the state cannot exist _in +the protocol itself_, and should instead specify the state in the request (or +failing that, a reference to the state for the server or client to track). + +### Does this make MCP a fully stateless protocol? + +Not entirely (hence 'by default'). Depending on your interpretation of +"requests", the SSE streams mentioned (both client-initiated and +server-initiated) tend to have multiple requests within a context of a stream. +However, these streams are constrained to a single HTTP request and optional to +use, meaning that the complexity is both constrained and optional to use when +the situation requires it. + +### Why is it important for STDIO to be stateless as well? + +The transport MCP is using should be an implementation detail only. If one +version of a protocol supports functionality that doesn't cleanly map over to +another version of the protocol, they are really two different protocols. + +This makes it easy for developers to switch their services from one transport to +another without needing to make significant changes to the behavior of their +applications, and easier to proxy between different transports correctly. +Otherwise, there will continue to be feature gaps and division between these +different implementations, leading to both confusion and incompatibility. diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index 76d5f9665..c7d8522cd 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -13,6 +13,7 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary - **Final**: 29 +- **Draft**: 1 - **Approved**: 1 - **Accepted**: 2 @@ -20,6 +21,7 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi | SEP | Title | Status | Type | Created | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------- | ---------------- | ---------- | +| [SEP-2575](/seps/2575-stateless-mcp) | Stateless-by-Default MCP | Draft | Standards Track | 2025-06-18 | | [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | | [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Approved | Standards Track | 2026-02-03 | | [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Accepted | Standards Track | 2026-02-16 | From 59e31ebed808af3c20588188030f2de4308a1a7e Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:20:43 -0600 Subject: [PATCH 06/69] feat: address review feedback on SEP-2575 Fix TypeScript syntax, clarify messages/listen semantics for both transports, update error codes, specify per-request _meta fields with types, and clarify capability delivery model. --- seps/2575-stateless-mcp.md | 80 ++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 2489929dd..406eec5c9 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -173,7 +173,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ modelcontextprotocol.io/mcpProtocolVersion: string; ++ "modelcontextprotocol.io/mcpProtocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -191,8 +191,8 @@ export interface Request { #### Unsupported Protocol Versions If a server receives a request with an unsupported protocol version, it MUST -return a JSON-RPC error response (400 Bad Request for HTTP). This response MUST -conform to the following structure: +return a JSON-RPC error response. For HTTP, the response status code MUST be +`400 Bad Request`. The error MUST conform to the following structure: ```ts /** @@ -204,9 +204,9 @@ conform to the following structure: "id": 1, "error": { /** - * MUST be -32000. + * MUST be -32001. */ - "code": -32000, + "code": -32001, /** * MUST be "Unsupported protocol version". */ @@ -248,8 +248,8 @@ for a server to advertise its supported protocol versions and capabilities. This discovery step is **OPTIONAL**. A client is free to invoke any RPC without first calling the discovery endpoint. If a client calls an unsupported RPC, the -server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a -`Method not found` JSON-RPC error (`-32601`). +server **MUST** return a `Method not found` JSON-RPC error (`-32601`). For HTTP, +the response status code MUST be `404 Not Found`. #### `server/discover` RPC @@ -263,7 +263,7 @@ server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a ```ts export interface DiscoveryRequest extends Request { method: "server/discover"; - params?: {}; // No parameters are needed for a discovery request. + params?: {}; } ``` @@ -309,16 +309,28 @@ optional features the client can handle for a specific transaction, such as streaming responses. A server **SHOULD** only send requests that match a client's provided -capabilities. If a client's capabilities change and it wishes to update the -server, a client **SHOULD** send a new RPC. +capabilities. The server may send these requests in two ways: + +1. **Inline**: As notifications within an SSE stream response to a triggering + RPC (e.g., `notifications/progress` within a `tools/call` response stream). +2. **On the listen stream**: As events on an open `messages/listen` SSE stream. + +In both cases, the server uses the `clientCapabilities` from the request's +`_meta` to determine what it is allowed to send. If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). -Client identity information (`clientInfo`) **MAY** also be included in the -per-request `_meta` field, allowing servers to identify the client without -requiring an initialization handshake. +In addition to `clientCapabilities`, the following fields previously exchanged +during initialization **MAY** be included in per-request `_meta` fields: + +* `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the + client software without requiring an initialization handshake. +* `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root + URIs, replacing the need for `notifications/roots/list_changed`. +* `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log + level for this request, replacing the `logging/setLevel` RPC. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -386,7 +398,12 @@ export interface MessagesListenRequest extends Request { ``` -**Response Schema:** +**Acknowledgment Notification:** + +The server sends this notification as the first event on the stream to +acknowledge that the listen stream has been established. For HTTP, this is the +first SSE event. The stream remains open for subsequent server-to-client +messages until the server sends a final `Result` to close it. ```ts export interface MessagesListenNotification extends Notification { @@ -397,13 +414,14 @@ export interface MessagesListenNotification extends Notification { #### STDIO Transport Behavior -For STDIO's simple request/response model, a client **MAY** send a -`MessagesListenRequest` at any time. The server **MUST** process it and reply -with a `MessagesListenNotification`. The interaction is complete after the -response is sent. +For STDIO, a client **MAY** send a `MessagesListenRequest` at any time to +declare its capabilities and the messages it is interested in receiving. The +server **MUST** acknowledge it by sending a `MessagesListenNotification`. -The server **MAY** send any server to client messages or notifications for the -duration of the connection. +The server **MAY** then send server-to-client messages and notifications for +the duration of the connection. If the connection is terminated (e.g., the +server crashes and restarts), the client **MUST** re-send `MessagesListenRequest` +to re-establish its declared capabilities. #### Streamable HTTP Transport Behavior @@ -439,8 +457,9 @@ the following RPC methods and notifications are removed: specified on a per-request basis using the `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. * `notifications/roots/list_changed`: This notification is removed. Clients - now provide their current roots directly in per-request `_meta` fields, - making server-side tracking of root changes unnecessary. + now provide their current roots directly in per-request `_meta` fields. + Since the server receives the current roots with each request, there is no + need for a separate change notification. ## Rationale @@ -486,7 +505,7 @@ Session management is now addressed separately by which proposes removing sessions entirely and replacing them with explicit state handles. This aligns with the [sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) -made by the Transports Working Group. +made by the Core Maintainers. ### Separation of Concerns @@ -621,4 +640,17 @@ This makes it easy for developers to switch their services from one transport to another without needing to make significant changes to the behavior of their applications, and easier to proxy between different transports correctly. Otherwise, there will continue to be feature gaps and division between these -different implementations, leading to both confusion and incompatibility. +different implementations, leading to both confusion and incompatibility. + + +## Open Questions + +### Should `clientInfo` be part of `ClientCapabilities`? + +Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` +(`ClientCapabilities` type) are separate fields. In a per-request model, having +a single field for all client metadata would reduce overhead. However, +`clientInfo` serves a different purpose (identity/UI) than capabilities +(feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, +remain a separate per-request `_meta` field, or be handled through a different +mechanism entirely (e.g., only sent via `messages/listen`)? From 953523096face49644060e9e0062fb4bbbaad211 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:25:06 -0600 Subject: [PATCH 07/69] chore: regenerate and format SEP docs --- docs/seps/2575-stateless-mcp.mdx | 77 ++++++++++---- seps/2575-stateless-mcp.md | 174 ++++++++++++------------------- 2 files changed, 118 insertions(+), 133 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index dcede133e..71196fa15 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -184,7 +184,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ modelcontextprotocol.io/mcpProtocolVersion: string; ++ "modelcontextprotocol.io/mcpProtocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -200,8 +200,8 @@ export interface Request { #### Unsupported Protocol Versions If a server receives a request with an unsupported protocol version, it MUST -return a JSON-RPC error response (400 Bad Request for HTTP). This response MUST -conform to the following structure: +return a JSON-RPC error response. For HTTP, the response status code MUST be +`400 Bad Request`. The error MUST conform to the following structure: ```ts /** @@ -213,9 +213,9 @@ conform to the following structure: "id": 1, "error": { /** - * MUST be -32000. + * MUST be -32001. */ - "code": -32000, + "code": -32001, /** * MUST be "Unsupported protocol version". */ @@ -254,8 +254,8 @@ for a server to advertise its supported protocol versions and capabilities. This discovery step is **OPTIONAL**. A client is free to invoke any RPC without first calling the discovery endpoint. If a client calls an unsupported RPC, the -server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a -`Method not found` JSON-RPC error (`-32601`). +server **MUST** return a `Method not found` JSON-RPC error (`-32601`). For HTTP, +the response status code MUST be `404 Not Found`. #### `server/discover` RPC @@ -267,7 +267,7 @@ server **MUST** reject the request with a `404 Not Found` error (for HTTP) or a ```ts export interface DiscoveryRequest extends Request { method: "server/discover"; - params?: {}; // No parameters are needed for a discovery request. + params?: {}; } ``` @@ -310,16 +310,28 @@ optional features the client can handle for a specific transaction, such as streaming responses. A server **SHOULD** only send requests that match a client's provided -capabilities. If a client's capabilities change and it wishes to update the -server, a client **SHOULD** send a new RPC. +capabilities. The server may send these requests in two ways: + +1. **Inline**: As notifications within an SSE stream response to a triggering + RPC (e.g., `notifications/progress` within a `tools/call` response stream). +2. **On the listen stream**: As events on an open `messages/listen` SSE stream. + +In both cases, the server uses the `clientCapabilities` from the request's +`_meta` to determine what it is allowed to send. If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). -Client identity information (`clientInfo`) **MAY** also be included in the -per-request `_meta` field, allowing servers to identify the client without -requiring an initialization handshake. +In addition to `clientCapabilities`, the following fields previously exchanged +during initialization **MAY** be included in per-request `_meta` fields: + +- `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the + client software without requiring an initialization handshake. +- `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root + URIs, replacing the need for `notifications/roots/list_changed`. +- `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log + level for this request, replacing the `logging/setLevel` RPC. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -380,7 +392,12 @@ export interface MessagesListenRequest extends Request { } ``` -**Response Schema:** +**Acknowledgment Notification:** + +The server sends this notification as the first event on the stream to +acknowledge that the listen stream has been established. For HTTP, this is the +first SSE event. The stream remains open for subsequent server-to-client +messages until the server sends a final `Result` to close it. ```ts export interface MessagesListenNotification extends Notification { @@ -390,13 +407,14 @@ export interface MessagesListenNotification extends Notification { #### STDIO Transport Behavior -For STDIO's simple request/response model, a client **MAY** send a -`MessagesListenRequest` at any time. The server **MUST** process it and reply -with a `MessagesListenNotification`. The interaction is complete after the -response is sent. +For STDIO, a client **MAY** send a `MessagesListenRequest` at any time to +declare its capabilities and the messages it is interested in receiving. The +server **MUST** acknowledge it by sending a `MessagesListenNotification`. -The server **MAY** send any server to client messages or notifications for the -duration of the connection. +The server **MAY** then send server-to-client messages and notifications for +the duration of the connection. If the connection is terminated (e.g., the +server crashes and restarts), the client **MUST** re-send `MessagesListenRequest` +to re-establish its declared capabilities. #### Streamable HTTP Transport Behavior @@ -430,8 +448,9 @@ the following RPC methods and notifications are removed: specified on a per-request basis using the `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. - `notifications/roots/list_changed`: This notification is removed. Clients - now provide their current roots directly in per-request `_meta` fields, - making server-side tracking of root changes unnecessary. + now provide their current roots directly in per-request `_meta` fields. + Since the server receives the current roots with each request, there is no + need for a separate change notification. ## Rationale @@ -473,7 +492,7 @@ Session management is now addressed separately by which proposes removing sessions entirely and replacing them with explicit state handles. This aligns with the [sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) -made by the Transports Working Group. +made by the Core Maintainers. ### Separation of Concerns @@ -596,3 +615,15 @@ another without needing to make significant changes to the behavior of their applications, and easier to proxy between different transports correctly. Otherwise, there will continue to be feature gaps and division between these different implementations, leading to both confusion and incompatibility. + +## Open Questions + +### Should `clientInfo` be part of `ClientCapabilities`? + +Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` +(`ClientCapabilities` type) are separate fields. In a per-request model, having +a single field for all client metadata would reduce overhead. However, +`clientInfo` serves a different purpose (identity/UI) than capabilities +(feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, +remain a separate per-request `_meta` field, or be handled through a different +mechanism entirely (e.g., only sent via `messages/listen`)? diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 406eec5c9..b88086eca 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -18,7 +18,7 @@ for the duration of the connection. This inherent statefulness makes it difficult to run MCP at scale. Placing an MCP server behind a standard load balancer, for example, is challenging because -a client's session is coupled to the specific server instance holding its state. +a client's session is coupled to the specific server instance holding its state. This proposal outlines a series of changes to **enable stateless MCP as the default**, embracing a "pay as you go" model for protocol complexity and state. @@ -31,7 +31,6 @@ handshake and replacing it with discrete, stateless alternatives. This initial step allows each request to be processed independently, simplifying server-side logic and paving the way for robust, scalable deployments. - ## Motivation The Model Context Protocol (MCP) specification currently mandates a stateful @@ -39,7 +38,6 @@ initialization handshake. This design choice creates significant challenges for scalability, reliability, and implementation simplicity. This SEP is motivated by the need to address these shortcomings. - ### The Problem with Statefulness The core issue is that a server must retain session state from previous requests @@ -63,14 +61,13 @@ and scalability. inefficient, adding complexity around "resumability". 3. **Increased Implementation Complexity:** The current model imposes a significant burden on developers. - * **Server-side:** Developers must implement logic to create, manage, and - eventually garbage-collect per-client session state. This is a common - source of bugs and memory leaks. - * **Client-side:** Developers must write complex code to manage a - persistent connection and handle the inevitable network failures and - reconnections, including the logic to resynchronize state after a - disconnect. - + - **Server-side:** Developers must implement logic to create, manage, and + eventually garbage-collect per-client session state. This is a common + source of bugs and memory leaks. + - **Client-side:** Developers must write complex code to manage a + persistent connection and handle the inevitable network failures and + reconnections, including the logic to resynchronize state after a + disconnect. ## Design Principles @@ -96,7 +93,6 @@ transport-agnostic libraries and tooling, and prevents protocol fragmentation where different transports behave in fundamentally different ways. A single, coherent protocol model is essential for a healthy ecosystem. - ## Specification ### Overview @@ -131,37 +127,33 @@ without a mandatory state-creating cycle. > providing stateless alternatives for version negotiation, discovery, and > capabilities. - ### Protocol Version To make requests self-contained, metadata previously negotiated during the handshake must now be included with **every request**. - #### HTTP For the HTTP transport, protocol version MUST be passed as **HTTP header**. For the HTTP transport, the headers MUST be treated as the source of truth over the request payload. -* `MCP-Protocol-Version: 2025-06-18` - * **Purpose**: To inform the server which version of the MCP specification - the client is using for this specific request. - * **Requirement**: This header is **MANDATORY**. Servers should reject - requests with a missing or unsupported version. - * This header MUST match the value provided in the Request as specified - below. - +- `MCP-Protocol-Version: 2025-06-18` + - **Purpose**: To inform the server which version of the MCP specification + the client is using for this specific request. + - **Requirement**: This header is **MANDATORY**. Servers should reject + requests with a missing or unsupported version. + - This header MUST match the value provided in the Request as specified + below. #### Per-request Version The `protocol-version` MUST be embedded directly within the `_meta` field of the -request payload. For HTTP, this _meta MUST match the associated HTTP header, or -else the server should return a 400 Bad Request. +request payload. For HTTP, this \_meta MUST match the associated HTTP header, or +else the server should return a 400 Bad Request. The following diff illustrates the required changes to the `Request` interface: - ```ts export interface Request { method: string; @@ -186,8 +178,6 @@ export interface Request { } ``` - - #### Unsupported Protocol Versions If a server receives a request with an unsupported protocol version, it MUST @@ -222,8 +212,6 @@ return a JSON-RPC error response. For HTTP, the response status code MUST be } ``` - - #### Version Negotiation Flow Without an initialization handshake, version negotiation happens inline: @@ -239,8 +227,7 @@ Without an initialization handshake, version negotiation happens inline: Alternatively, a client **MAY** call `server/discover` first to learn the server's supported versions before sending any other requests. - -### Optional Discovery for Server Capabilities +### Optional Discovery for Server Capabilities To allow clients to adapt to different server implementations, this specification introduces a **discovery RPC**. This provides a standard mechanism @@ -251,15 +238,13 @@ first calling the discovery endpoint. If a client calls an unsupported RPC, the server **MUST** return a `Method not found` JSON-RPC error (`-32601`). For HTTP, the response status code MUST be `404 Not Found`. - #### `server/discover` RPC -* **Purpose**: To allow a client to query the server for its supported - protocol versions, capabilities, and other metadata. +- **Purpose**: To allow a client to query the server for its supported + protocol versions, capabilities, and other metadata. **Request Schema:** - ```ts export interface DiscoveryRequest extends Request { method: "server/discover"; @@ -267,10 +252,8 @@ export interface DiscoveryRequest extends Request { } ``` - **Response Schema:** - ```ts export interface DiscoveryResult extends Result { /** @@ -299,14 +282,13 @@ export interface DiscoveryResult extends Result { } ``` - ### Per-Request Client Capabilities To complete the decoupling from the initial handshake, client capabilities are no longer negotiated once at initialization. Instead, a client **MAY** specify its capabilities on a per-request basis. This allows the server to know what optional features the client can handle for a specific transaction, such as -streaming responses. +streaming responses. A server **SHOULD** only send requests that match a client's provided capabilities. The server may send these requests in two ways: @@ -325,32 +307,29 @@ doesn't support, a client MUST return a `Method not found` JSON-RPC error In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: -* `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the - client software without requiring an initialization handshake. -* `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root - URIs, replacing the need for `notifications/roots/list_changed`. -* `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log - level for this request, replacing the `logging/setLevel` RPC. +- `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the + client software without requiring an initialization handshake. +- `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root + URIs, replacing the need for `notifications/roots/list_changed`. +- `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log + level for this request, replacing the `logging/setLevel` RPC. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: server-initiated and client-initiated. - #### Streaming Models - ##### Server-Initiated Streaming (Response Stream) This model applies when a client makes a standard RPC call and the server responds back with an SSE stream. The client specifies supported capabilities -directly in the request. +directly in the request. The client adds an optional `clientCapabilities` field to the `_meta` object of its request. For the HTTP transport, a server that supports this **MAY** then respond with an SSE stream for that transaction. - ```ts export interface Request { // ... @@ -366,8 +345,6 @@ export interface Request { } ``` - - ##### Client-Initiated Streaming (Background Streaming) This model applies when a client wants to proactively open a persistent SSE @@ -382,7 +359,6 @@ Streamable HTTP today. **Request Schema:** - ```ts export interface MessagesListenRequest extends Request { method: "messages/listen"; @@ -397,7 +373,6 @@ export interface MessagesListenRequest extends Request { } ``` - **Acknowledgment Notification:** The server sends this notification as the first event on the stream to @@ -411,7 +386,6 @@ export interface MessagesListenNotification extends Notification { } ``` - #### STDIO Transport Behavior For STDIO, a client **MAY** send a `MessagesListenRequest` at any time to @@ -423,7 +397,6 @@ the duration of the connection. If the connection is terminated (e.g., the server crashes and restarts), the client **MUST** re-send `MessagesListenRequest` to re-establish its declared capabilities. - #### Streamable HTTP Transport Behavior For HTTP, there are two distinct models for handling streaming: @@ -441,26 +414,24 @@ To proactively open a persistent SSE stream, the client sends the dedicated stream** (`Content-Type: text/event-stream`), and the **first request** on this stream **MUST** be an event containing the `MessagesListenNotification`. - ### Deprecated and Removed RPCs To simplify the protocol and align with the move to per-request capabilities, the following RPC methods and notifications are removed: -* `initialize` / `notifications/initialized`: The initialization handshake is - removed. Version negotiation is handled per-request via - `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is - handled by `server/discover`. Servers compliant with this SEP **SHOULD** - accept and ignore `notifications/initialized` without error to maintain - backward compatibility with clients that may send it. -* `logging/setLevel`: This method is removed. Log levels should now be - specified on a per-request basis using the - `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. -* `notifications/roots/list_changed`: This notification is removed. Clients - now provide their current roots directly in per-request `_meta` fields. - Since the server receives the current roots with each request, there is no - need for a separate change notification. - +- `initialize` / `notifications/initialized`: The initialization handshake is + removed. Version negotiation is handled per-request via + `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is + handled by `server/discover`. Servers compliant with this SEP **SHOULD** + accept and ignore `notifications/initialized` without error to maintain + backward compatibility with clients that may send it. +- `logging/setLevel`: This method is removed. Log levels should now be + specified on a per-request basis using the + `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. +- `notifications/roots/list_changed`: This notification is removed. Clients + now provide their current roots directly in per-request `_meta` fields. + Since the server receives the current roots with each request, there is no + need for a separate change notification. ## Rationale @@ -475,7 +446,6 @@ implementation complexity for the most common use cases. This immediately enables straightforward horizontal scaling and improves resilience, as any request can be handled by any server instance. - #### Alternative Considered: Optional Handshake An alternative we considered was to keep the existing stateful handshake but @@ -483,7 +453,6 @@ make it optional. In this model, a client could choose to either perform the handshake to establish a persistent session or skip it and send self-contained requests. - #### Why it was rejected: Supporting two parallel interaction models would have dramatically increased the @@ -494,7 +463,6 @@ clear, obvious way to perform a core function. By making a clean break, we ensure the entire ecosystem can move forward and benefit from a simpler, more scalable, and more robust foundation. - ### Explicit Session Management This proposal originally included dedicated `sessions/create` and @@ -507,7 +475,6 @@ state handles. This aligns with the [sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) made by the Core Maintainers. - ### Separation of Concerns A core principle of this proposal is the "unbundling" of the monolithic @@ -516,22 +483,20 @@ original handshake mixed the concerns of protocol negotiation and capability discovery into a single, complex interaction. The new design explicitly separates these: -* **Discovery**: Handled exclusively by `server/discover`. -* **Capabilities**: Handled on a per-request basis via the `_meta` field or - the `messages/listen` RPC. +- **Discovery**: Handled exclusively by `server/discover`. +- **Capabilities**: Handled on a per-request basis via the `_meta` field or + the `messages/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This allows clients to use only the parts of the protocol they need, adhering to our "pay as you go" principle. - #### Alternative Considered: A Monolithic Handshake We could have kept a single, monolithic handshake RPC and simply added more parameters and complex logic to it to support the stateless-first model. - #### Why it was rejected: A single, do-it-all RPC is difficult to implement, test, and evolve. It forces @@ -540,72 +505,64 @@ features. By separating these concerns, we've made the protocol easier to learn and implement correctly, while also making it more flexible and extensible for the future. - ## Backward Compatibility While this proposal attempts to preserve existing functionality and use-cases, this proposal introduces a **fundamental, backward-incompatible change**. Thus, it will require a new version of the protocol. - ### Supporting Multiple Versions While this SEP removes the `initialize` handshake, a server that wishes to support both old and new clients **MAY** do so. Such a server can continue to implement the old `initialize` RPC to handle legacy clients, while also exposing -the new stateless RPCs (`server/discover`, etc.) for updated clients. +the new stateless RPCs (`server/discover`, etc.) for updated clients. Both servers and clients should be able to handle changes in the versions appropriately. Two example scenarios are outlined below, where vPrev indicates the version prior to the SEP, and vAfter indicates a version after it. - #### Client (supporting vPrev) → Server (vPrev, vPost) 1. Client sends initialization 2. Server supports vPrev, so initialization is returned per spec 3. Client and server communicate per `vPrev`. - #### Client (supporting vPrev, vPost) → Server (vPrev) 1. Client sends a request (e.g. tools/list) with MCP Protocol Version header - 1. HTTP: Server says "400 bad request" - 2. STDIO: returns error indicating initialization was required + 1. HTTP: Server says "400 bad request" + 2. STDIO: returns error indicating initialization was required 2. Client falls back to vPrev (and makes initialization) for future requests - ## Security Implications While this proposal improves the protocol's clarity, implementations **may still be vulnerable** to common exploits if not secured correctly. The following points should be considered: -* **Per-request Authentication**: Without a session handshake, every request - must be independently authenticated and authorized. Implementations - **MUST** ensure that authentication is not bypassed by the removal of the - initialization phase. -* **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used - for reconnaissance. Servers **SHOULD** protect this endpoint with - **rate-limiting**. -* **Protocol Version Downgrade**: An attacker could forge - `UnsupportedVersionError` responses to force a client to use an older, - potentially less secure protocol version. All communication **MUST** occur - over an encrypted transport like **TLS** to prevent this. - +- **Per-request Authentication**: Without a session handshake, every request + must be independently authenticated and authorized. Implementations + **MUST** ensure that authentication is not bypassed by the removal of the + initialization phase. +- **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used + for reconnaissance. Servers **SHOULD** protect this endpoint with + **rate-limiting**. +- **Protocol Version Downgrade**: An attacker could forge + `UnsupportedVersionError` responses to force a client to use an older, + potentially less secure protocol version. All communication **MUST** occur + over an encrypted transport like **TLS** to prevent this. ## Reference Implementation // TODO - ## FAQ - -### What is protocol level statelessness? +### What is protocol level statelessness? [Wikipedia](https://en.wikipedia.org/wiki/Stateless_protocol) defines a -stateless protocol as: +stateless protocol as: > A stateless protocol is a communication protocol in which the receiver must > not retain session state from previous requests. The sender transfers relevant @@ -615,10 +572,9 @@ stateless protocol as: This does NOT mean that you can't build stateful applications on top of a stateless protocol. HTTP is an example of a stateless protocol, which most of -the web is built on today. However it does mean that the state cannot exist *in -the protocol itself*, and should instead specify the state in the request (or -failing that, a reference to the state for the server or client to track). - +the web is built on today. However it does mean that the state cannot exist _in +the protocol itself_, and should instead specify the state in the request (or +failing that, a reference to the state for the server or client to track). ### Does this make MCP a fully stateless protocol? @@ -627,8 +583,7 @@ Not entirely (hence 'by default'). Depending on your interpretation of server-initiated) tend to have multiple requests within a context of a stream. However, these streams are constrained to a single HTTP request and optional to use, meaning that the complexity is both constrained and optional to use when -the situation requires it. - +the situation requires it. ### Why is it important for STDIO to be stateless as well? @@ -642,7 +597,6 @@ applications, and easier to proxy between different transports correctly. Otherwise, there will continue to be feature gaps and division between these different implementations, leading to both confusion and incompatibility. - ## Open Questions ### Should `clientInfo` be part of `ClientCapabilities`? From bf5ed883b8e4eb6f77415fed05b59ba9c20b013b Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:58:22 -0600 Subject: [PATCH 08/69] fix: use io.modelcontextprotocol/ prefix for _meta keys --- docs/seps/2575-stateless-mcp.mdx | 20 ++++++++++---------- seps/2575-stateless-mcp.md | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 71196fa15..6013bcde4 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -184,7 +184,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ "modelcontextprotocol.io/mcpProtocolVersion": string; ++ "io.modelcontextprotocol/mcpProtocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -237,7 +237,7 @@ Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the `MCP-Protocol-Version` header and - `modelcontextprotocol.io/mcpProtocolVersion` `_meta` field. + `io.modelcontextprotocol/mcpProtocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an `UnsupportedVersionError` containing its list of `supportedVersions`. @@ -326,11 +326,11 @@ doesn't support, a client MUST return a `Method not found` JSON-RPC error In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: -- `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the +- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the client software without requiring an initialization handshake. -- `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root +- `"io.modelcontextprotocol/roots"`: `Root[]` — the client's current root URIs, replacing the need for `notifications/roots/list_changed`. -- `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log +- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. The primary capability defined in this proposal is the ability to handle @@ -357,7 +357,7 @@ export interface Request { + /** + * Optional capabilities of the client for this specific request. + */ -+ "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; ++ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; // ... other meta fields }; // ... @@ -383,9 +383,9 @@ export interface MessagesListenRequest extends Request { method: "messages/listen"; params: { _meta?: { - "modelcontextprotocol.io/mcpProtocolVersion": string; - "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; - "modelcontextprotocol.io/roots"?: Root[]; + "io.modelcontextprotocol/mcpProtocolVersion": string; + "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; + "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields }; }; @@ -446,7 +446,7 @@ the following RPC methods and notifications are removed: backward compatibility with clients that may send it. - `logging/setLevel`: This method is removed. Log levels should now be specified on a per-request basis using the - `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. + `'io.modelcontextprotocol/logLevel'` field in the `_meta` object. - `notifications/roots/list_changed`: This notification is removed. Clients now provide their current roots directly in per-request `_meta` fields. Since the server receives the current roots with each request, there is no diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index b88086eca..6ee304fc7 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -165,7 +165,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ "modelcontextprotocol.io/mcpProtocolVersion": string; ++ "io.modelcontextprotocol/mcpProtocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -218,7 +218,7 @@ Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the `MCP-Protocol-Version` header and - `modelcontextprotocol.io/mcpProtocolVersion` `_meta` field. + `io.modelcontextprotocol/mcpProtocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an `UnsupportedVersionError` containing its list of `supportedVersions`. @@ -307,11 +307,11 @@ doesn't support, a client MUST return a `Method not found` JSON-RPC error In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: -- `"modelcontextprotocol.io/clientInfo"`: `Implementation` — identifies the +- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the client software without requiring an initialization handshake. -- `"modelcontextprotocol.io/roots"`: `Root[]` — the client's current root +- `"io.modelcontextprotocol/roots"`: `Root[]` — the client's current root URIs, replacing the need for `notifications/roots/list_changed`. -- `"modelcontextprotocol.io/logLevel"`: `LoggingLevel` — the desired log +- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. The primary capability defined in this proposal is the ability to handle @@ -338,7 +338,7 @@ export interface Request { + /** + * Optional capabilities of the client for this specific request. + */ -+ "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; ++ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; // ... other meta fields }; // ... @@ -364,9 +364,9 @@ export interface MessagesListenRequest extends Request { method: "messages/listen"; params: { _meta?: { - "modelcontextprotocol.io/mcpProtocolVersion": string; - "modelcontextprotocol.io/clientCapabilities"?: ClientCapabilities; - "modelcontextprotocol.io/roots"?: Root[]; + "io.modelcontextprotocol/mcpProtocolVersion": string; + "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; + "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields }; }; @@ -427,7 +427,7 @@ the following RPC methods and notifications are removed: backward compatibility with clients that may send it. - `logging/setLevel`: This method is removed. Log levels should now be specified on a per-request basis using the - `'modelcontextprotocol.io/logLevel'` field in the `_meta` object. + `'io.modelcontextprotocol/logLevel'` field in the `_meta` object. - `notifications/roots/list_changed`: This notification is removed. Clients now provide their current roots directly in per-request `_meta` fields. Since the server receives the current roots with each request, there is no From 9692bc0e2843453291a25b33b83277bf575da227 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:47:04 -0600 Subject: [PATCH 09/69] fix: simplify security implications section --- docs/seps/2575-stateless-mcp.mdx | 21 ++++++--------------- seps/2575-stateless-mcp.md | 21 ++++++--------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 6013bcde4..1a2dfe4cf 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -556,21 +556,12 @@ the version prior to the SEP, and vAfter indicates a version after it. ## Security Implications -While this proposal improves the protocol's clarity, implementations **may still -be vulnerable** to common exploits if not secured correctly. The following -points should be considered: - -- **Per-request Authentication**: Without a session handshake, every request - must be independently authenticated and authorized. Implementations - **MUST** ensure that authentication is not bypassed by the removal of the - initialization phase. -- **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used - for reconnaissance. Servers **SHOULD** protect this endpoint with - **rate-limiting**. -- **Protocol Version Downgrade**: An attacker could forge - `UnsupportedVersionError` responses to force a client to use an older, - potentially less secure protocol version. All communication **MUST** occur - over an encrypted transport like **TLS** to prevent this. +Without a session handshake, every request must be independently authenticated +and authorized. Implementations **MUST** ensure that authentication is not +bypassed by the removal of the initialization phase. + +Beyond per-request authentication, this proposal does not introduce additional +security concerns. ## Reference Implementation diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 6ee304fc7..a4c6c1c5a 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -537,21 +537,12 @@ the version prior to the SEP, and vAfter indicates a version after it. ## Security Implications -While this proposal improves the protocol's clarity, implementations **may still -be vulnerable** to common exploits if not secured correctly. The following -points should be considered: - -- **Per-request Authentication**: Without a session handshake, every request - must be independently authenticated and authorized. Implementations - **MUST** ensure that authentication is not bypassed by the removal of the - initialization phase. -- **Discovery Endpoint Abuse**: The `server/discover` endpoint could be used - for reconnaissance. Servers **SHOULD** protect this endpoint with - **rate-limiting**. -- **Protocol Version Downgrade**: An attacker could forge - `UnsupportedVersionError` responses to force a client to use an older, - potentially less secure protocol version. All communication **MUST** occur - over an encrypted transport like **TLS** to prevent this. +Without a session handshake, every request must be independently authenticated +and authorized. Implementations **MUST** ensure that authentication is not +bypassed by the removal of the initialization phase. + +Beyond per-request authentication, this proposal does not introduce additional +security concerns. ## Reference Implementation From a17e8c1fcaf700d882a44e623d454e2c196fa07f Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:59:53 -0600 Subject: [PATCH 10/69] feat: rename to notifications/listen, remove GET endpoint, deprecate resource subscriptions Rename messages/listen to notifications/listen per SEP-2260 (only notifications on background stream). Explicitly remove HTTP GET endpoint. Add resources/subscribe and resources/unsubscribe to deprecated RPCs with per-request resourceSubscriptions replacement. --- docs/seps/2575-stateless-mcp.mdx | 50 +++++++++++++++++++++----------- seps/2575-stateless-mcp.md | 50 +++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 1a2dfe4cf..6e4c20c39 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -314,7 +314,7 @@ capabilities. The server may send these requests in two ways: 1. **Inline**: As notifications within an SSE stream response to a triggering RPC (e.g., `notifications/progress` within a `tools/call` response stream). -2. **On the listen stream**: As events on an open `messages/listen` SSE stream. +2. **On the listen stream**: As events on an open `notifications/listen` SSE stream. In both cases, the server uses the `clientCapabilities` from the request's `_meta` to determine what it is allowed to send. @@ -332,6 +332,10 @@ during initialization **MAY** be included in per-request `_meta` fields: URIs, replacing the need for `notifications/roots/list_changed`. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. +- `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` — an array + of resource URIs the client is interested in, replacing + `resources/subscribe`. The server sends `notifications/resources/updated` + on the `notifications/listen` stream for matching resources. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -369,18 +373,22 @@ export interface Request { This model applies when a client wants to proactively open a persistent SSE stream to receive multiple or unsolicited events. -This is achieved using a dedicated `messages/listen` RPC. For the HTTP +This is achieved using a dedicated `notifications/listen` RPC. For the HTTP transport, the client sends this request via `POST`, and the server's response -is an open SSE stream, with a `MessagesListenNotification` sent as the first -event. For the STDIO transport, this RPC is used for a simple request/response -capabilities check. This RPC replaces the existing GET endpoint behavior for -Streamable HTTP today. +is an open SSE stream, with a `NotificationsListenNotification` sent as the +first event. For the STDIO transport, this RPC is used to declare the client's +capabilities and interests. + +This RPC replaces the existing HTTP GET endpoint for Streamable HTTP. The GET +endpoint is removed; all communication uses POST. Only notifications (not +requests) may be sent on the listen stream, per +[SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260). **Request Schema:** ```ts -export interface MessagesListenRequest extends Request { - method: "messages/listen"; +export interface NotificationsListenRequest extends Request { + method: "notifications/listen"; params: { _meta?: { "io.modelcontextprotocol/mcpProtocolVersion": string; @@ -400,20 +408,20 @@ first SSE event. The stream remains open for subsequent server-to-client messages until the server sends a final `Result` to close it. ```ts -export interface MessagesListenNotification extends Notification { - method: "notifications/messages/listen"; +export interface NotificationsListenNotification extends Notification { + method: "notifications/listen/acknowledged"; } ``` #### STDIO Transport Behavior -For STDIO, a client **MAY** send a `MessagesListenRequest` at any time to +For STDIO, a client **MAY** send a `NotificationsListenRequest` at any time to declare its capabilities and the messages it is interested in receiving. The -server **MUST** acknowledge it by sending a `MessagesListenNotification`. +server **MUST** acknowledge it by sending a `NotificationsListenNotification`. The server **MAY** then send server-to-client messages and notifications for the duration of the connection. If the connection is terminated (e.g., the -server crashes and restarts), the client **MUST** re-send `MessagesListenRequest` +server crashes and restarts), the client **MUST** re-send `NotificationsListenRequest` to re-establish its declared capabilities. #### Streamable HTTP Transport Behavior @@ -429,9 +437,9 @@ field. The server **MAY** then respond with an SSE stream for that transaction. **2. Client-Initiated Streaming** To proactively open a persistent SSE stream, the client sends the dedicated -`MessagesListenRequest` via `POST`. The server's response **is an open SSE +`NotificationsListenRequest` via `POST`. The server's response **is an open SSE stream** (`Content-Type: text/event-stream`), and the **first request** on this -stream **MUST** be an event containing the `MessagesListenNotification`. +stream **MUST** be an event containing the `NotificationsListenNotification`. ### Deprecated and Removed RPCs @@ -451,6 +459,14 @@ the following RPC methods and notifications are removed: now provide their current roots directly in per-request `_meta` fields. Since the server receives the current roots with each request, there is no need for a separate change notification. +- `resources/subscribe` / `resources/unsubscribe`: These methods are removed. + Resource subscriptions are inherently stateful — the server must remember + which resources each client has subscribed to. Instead, clients specify + the resources they are interested in via per-request `_meta` fields using + `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` (an array of + resource URIs). The server sends `notifications/resources/updated` + notifications on the `notifications/listen` stream for any matching + resources. ## Rationale @@ -504,7 +520,7 @@ separates these: - **Discovery**: Handled exclusively by `server/discover`. - **Capabilities**: Handled on a per-request basis via the `_meta` field or - the `messages/listen` RPC. + the `notifications/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -617,4 +633,4 @@ a single field for all client metadata would reduce overhead. However, `clientInfo` serves a different purpose (identity/UI) than capabilities (feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a separate per-request `_meta` field, or be handled through a different -mechanism entirely (e.g., only sent via `messages/listen`)? +mechanism entirely (e.g., only sent via `notifications/listen`)? diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index a4c6c1c5a..cc4d1f7b0 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -295,7 +295,7 @@ capabilities. The server may send these requests in two ways: 1. **Inline**: As notifications within an SSE stream response to a triggering RPC (e.g., `notifications/progress` within a `tools/call` response stream). -2. **On the listen stream**: As events on an open `messages/listen` SSE stream. +2. **On the listen stream**: As events on an open `notifications/listen` SSE stream. In both cases, the server uses the `clientCapabilities` from the request's `_meta` to determine what it is allowed to send. @@ -313,6 +313,10 @@ during initialization **MAY** be included in per-request `_meta` fields: URIs, replacing the need for `notifications/roots/list_changed`. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. +- `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` — an array + of resource URIs the client is interested in, replacing + `resources/subscribe`. The server sends `notifications/resources/updated` + on the `notifications/listen` stream for matching resources. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -350,18 +354,22 @@ export interface Request { This model applies when a client wants to proactively open a persistent SSE stream to receive multiple or unsolicited events. -This is achieved using a dedicated `messages/listen` RPC. For the HTTP +This is achieved using a dedicated `notifications/listen` RPC. For the HTTP transport, the client sends this request via `POST`, and the server's response -is an open SSE stream, with a `MessagesListenNotification` sent as the first -event. For the STDIO transport, this RPC is used for a simple request/response -capabilities check. This RPC replaces the existing GET endpoint behavior for -Streamable HTTP today. +is an open SSE stream, with a `NotificationsListenNotification` sent as the +first event. For the STDIO transport, this RPC is used to declare the client's +capabilities and interests. + +This RPC replaces the existing HTTP GET endpoint for Streamable HTTP. The GET +endpoint is removed; all communication uses POST. Only notifications (not +requests) may be sent on the listen stream, per +[SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260). **Request Schema:** ```ts -export interface MessagesListenRequest extends Request { - method: "messages/listen"; +export interface NotificationsListenRequest extends Request { + method: "notifications/listen"; params: { _meta?: { "io.modelcontextprotocol/mcpProtocolVersion": string; @@ -381,20 +389,20 @@ first SSE event. The stream remains open for subsequent server-to-client messages until the server sends a final `Result` to close it. ```ts -export interface MessagesListenNotification extends Notification { - method: "notifications/messages/listen"; +export interface NotificationsListenNotification extends Notification { + method: "notifications/listen/acknowledged"; } ``` #### STDIO Transport Behavior -For STDIO, a client **MAY** send a `MessagesListenRequest` at any time to +For STDIO, a client **MAY** send a `NotificationsListenRequest` at any time to declare its capabilities and the messages it is interested in receiving. The -server **MUST** acknowledge it by sending a `MessagesListenNotification`. +server **MUST** acknowledge it by sending a `NotificationsListenNotification`. The server **MAY** then send server-to-client messages and notifications for the duration of the connection. If the connection is terminated (e.g., the -server crashes and restarts), the client **MUST** re-send `MessagesListenRequest` +server crashes and restarts), the client **MUST** re-send `NotificationsListenRequest` to re-establish its declared capabilities. #### Streamable HTTP Transport Behavior @@ -410,9 +418,9 @@ field. The server **MAY** then respond with an SSE stream for that transaction. **2. Client-Initiated Streaming** To proactively open a persistent SSE stream, the client sends the dedicated -`MessagesListenRequest` via `POST`. The server's response **is an open SSE +`NotificationsListenRequest` via `POST`. The server's response **is an open SSE stream** (`Content-Type: text/event-stream`), and the **first request** on this -stream **MUST** be an event containing the `MessagesListenNotification`. +stream **MUST** be an event containing the `NotificationsListenNotification`. ### Deprecated and Removed RPCs @@ -432,6 +440,14 @@ the following RPC methods and notifications are removed: now provide their current roots directly in per-request `_meta` fields. Since the server receives the current roots with each request, there is no need for a separate change notification. +- `resources/subscribe` / `resources/unsubscribe`: These methods are removed. + Resource subscriptions are inherently stateful — the server must remember + which resources each client has subscribed to. Instead, clients specify + the resources they are interested in via per-request `_meta` fields using + `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` (an array of + resource URIs). The server sends `notifications/resources/updated` + notifications on the `notifications/listen` stream for any matching + resources. ## Rationale @@ -485,7 +501,7 @@ separates these: - **Discovery**: Handled exclusively by `server/discover`. - **Capabilities**: Handled on a per-request basis via the `_meta` field or - the `messages/listen` RPC. + the `notifications/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -598,4 +614,4 @@ a single field for all client metadata would reduce overhead. However, `clientInfo` serves a different purpose (identity/UI) than capabilities (feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a separate per-request `_meta` field, or be handled through a different -mechanism entirely (e.g., only sent via `messages/listen`)? +mechanism entirely (e.g., only sent via `notifications/listen`)? From 16c83ea926f9ecf40308313c32891ccf40e8de23 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:26:43 -0600 Subject: [PATCH 11/69] fix: rename mcpProtocolVersion to protocolVersion --- docs/seps/2575-stateless-mcp.mdx | 6 +++--- seps/2575-stateless-mcp.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 6e4c20c39..c6d998d9f 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -184,7 +184,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ "io.modelcontextprotocol/mcpProtocolVersion": string; ++ "io.modelcontextprotocol/protocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -237,7 +237,7 @@ Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the `MCP-Protocol-Version` header and - `io.modelcontextprotocol/mcpProtocolVersion` `_meta` field. + `io.modelcontextprotocol/protocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an `UnsupportedVersionError` containing its list of `supportedVersions`. @@ -391,7 +391,7 @@ export interface NotificationsListenRequest extends Request { method: "notifications/listen"; params: { _meta?: { - "io.modelcontextprotocol/mcpProtocolVersion": string; + "io.modelcontextprotocol/protocolVersion": string; "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index cc4d1f7b0..72beb65b5 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -165,7 +165,7 @@ export interface Request { + /** + * The MCP Protocol Version being used for this request. + */ -+ "io.modelcontextprotocol/mcpProtocolVersion": string; ++ "io.modelcontextprotocol/protocolVersion": string; /** * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. @@ -218,7 +218,7 @@ Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the `MCP-Protocol-Version` header and - `io.modelcontextprotocol/mcpProtocolVersion` `_meta` field. + `io.modelcontextprotocol/protocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an `UnsupportedVersionError` containing its list of `supportedVersions`. @@ -372,7 +372,7 @@ export interface NotificationsListenRequest extends Request { method: "notifications/listen"; params: { _meta?: { - "io.modelcontextprotocol/mcpProtocolVersion": string; + "io.modelcontextprotocol/protocolVersion": string; "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields From a00274565bb5bba375ad30d5f74b68f2cf2bac5d Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:33:30 -0600 Subject: [PATCH 12/69] feat: move notification subscriptions into notifications/listen params --- docs/seps/2575-stateless-mcp.mdx | 45 +++++++++++++++++++++++++------- seps/2575-stateless-mcp.md | 45 +++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index c6d998d9f..39e4d96dd 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -332,10 +332,6 @@ during initialization **MAY** be included in per-request `_meta` fields: URIs, replacing the need for `notifications/roots/list_changed`. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. -- `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` — an array - of resource URIs the client is interested in, replacing - `resources/subscribe`. The server sends `notifications/resources/updated` - on the `notifications/listen` stream for matching resources. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -396,10 +392,42 @@ export interface NotificationsListenRequest extends Request { "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields }; + + /** + * Optional filter for which notifications the client wants to receive. + * If omitted, the server SHOULD send all notifications the client's + * capabilities support. + */ + notifications?: { + /** + * If true, receive notifications/tools/list_changed. + */ + toolsListChanged?: boolean; + + /** + * If true, receive notifications/prompts/list_changed. + */ + promptsListChanged?: boolean; + + /** + * If true, receive notifications/resources/list_changed. + */ + resourcesListChanged?: boolean; + + /** + * Subscribe to notifications/resources/updated for specific + * resource URIs. Replaces the resources/subscribe RPC. + */ + resourceSubscriptions?: string[]; + }; }; } ``` +If `notifications` is omitted entirely, the server **SHOULD** send all +notifications the client's declared capabilities support. If provided, only +the specified notification types are delivered. + **Acknowledgment Notification:** The server sends this notification as the first event on the stream to @@ -461,11 +489,10 @@ the following RPC methods and notifications are removed: need for a separate change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember - which resources each client has subscribed to. Instead, clients specify - the resources they are interested in via per-request `_meta` fields using - `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` (an array of - resource URIs). The server sends `notifications/resources/updated` - notifications on the `notifications/listen` stream for any matching + which resources each client has subscribed to. Instead, clients declare + the resources they want updates for in the `notifications` param of the + `notifications/listen` request. The server sends + `notifications/resources/updated` on the listen stream for matching resources. ## Rationale diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 72beb65b5..19cb2233c 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -313,10 +313,6 @@ during initialization **MAY** be included in per-request `_meta` fields: URIs, replacing the need for `notifications/roots/list_changed`. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level for this request, replacing the `logging/setLevel` RPC. -- `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` — an array - of resource URIs the client is interested in, replacing - `resources/subscribe`. The server sends `notifications/resources/updated` - on the `notifications/listen` stream for matching resources. The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -377,10 +373,42 @@ export interface NotificationsListenRequest extends Request { "io.modelcontextprotocol/roots"?: Root[]; // ... other meta fields }; + + /** + * Optional filter for which notifications the client wants to receive. + * If omitted, the server SHOULD send all notifications the client's + * capabilities support. + */ + notifications?: { + /** + * If true, receive notifications/tools/list_changed. + */ + toolsListChanged?: boolean; + + /** + * If true, receive notifications/prompts/list_changed. + */ + promptsListChanged?: boolean; + + /** + * If true, receive notifications/resources/list_changed. + */ + resourcesListChanged?: boolean; + + /** + * Subscribe to notifications/resources/updated for specific + * resource URIs. Replaces the resources/subscribe RPC. + */ + resourceSubscriptions?: string[]; + }; }; } ``` +If `notifications` is omitted entirely, the server **SHOULD** send all +notifications the client's declared capabilities support. If provided, only +the specified notification types are delivered. + **Acknowledgment Notification:** The server sends this notification as the first event on the stream to @@ -442,11 +470,10 @@ the following RPC methods and notifications are removed: need for a separate change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember - which resources each client has subscribed to. Instead, clients specify - the resources they are interested in via per-request `_meta` fields using - `"io.modelcontextprotocol/resourceSubscriptions"`: `string[]` (an array of - resource URIs). The server sends `notifications/resources/updated` - notifications on the `notifications/listen` stream for any matching + which resources each client has subscribed to. Instead, clients declare + the resources they want updates for in the `notifications` param of the + `notifications/listen` request. The server sends + `notifications/resources/updated` on the listen stream for matching resources. ## Rationale From 38a8ba5032e4e043f4faa40303a0b47fa35a10d4 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:48:34 -0600 Subject: [PATCH 13/69] feat: add MissingRequiredClientCapability error, use TS interfaces for errors --- docs/seps/2575-stateless-mcp.mdx | 64 ++++++++++++++++++++------------ seps/2575-stateless-mcp.md | 64 ++++++++++++++++++++------------ 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 39e4d96dd..d81b73cdd 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -204,30 +204,21 @@ return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: ```ts -/** - * JSON-RPC error response returned when the client requests - * an unsupported protocol version. - */ -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - /** - * MUST be -32001. - */ - "code": -32001, - /** - * MUST be "Unsupported protocol version". - */ - "message": "Unsupported protocol version", - /** - * MUST contain an array of strings listing the - * protocol versions supported by the server. - */ - "data": { - "supportedVersions": ["2025-06-18", "2025-03-26"] - } - } +export const UNSUPPORTED_PROTOCOL_VERSION = -32001; + +export interface UnsupportedProtocolVersionError extends Omit< + JSONRPCErrorResponse, + "error" +> { + error: Error & { + code: typeof UNSUPPORTED_PROTOCOL_VERSION; + data: { + /** + * An array of protocol version strings that the server supports. + */ + supportedVersions: string[]; + }; + }; } ``` @@ -323,6 +314,31 @@ If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). +If a server requires client capabilities to process a request and the client +has not provided them, the server MUST return a JSON-RPC error. For HTTP, the +response status code MUST be `400 Bad Request`. The client MAY then retry the +request with the required capabilities in `_meta`. + +```ts +export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; + +export interface MissingRequiredClientCapabilityError extends Omit< + JSONRPCErrorResponse, + "error" +> { + error: Error & { + code: typeof MISSING_REQUIRED_CLIENT_CAPABILITY; + data: { + /** + * The capabilities the server requires from the client + * to process this request. + */ + requiredCapabilities: ClientCapabilities; + }; + }; +} +``` + In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 19cb2233c..d3963475e 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -185,30 +185,21 @@ return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: ```ts -/** - * JSON-RPC error response returned when the client requests - * an unsupported protocol version. - */ -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - /** - * MUST be -32001. - */ - "code": -32001, - /** - * MUST be "Unsupported protocol version". - */ - "message": "Unsupported protocol version", - /** - * MUST contain an array of strings listing the - * protocol versions supported by the server. - */ - "data": { - "supportedVersions": ["2025-06-18", "2025-03-26"] - } - } +export const UNSUPPORTED_PROTOCOL_VERSION = -32001; + +export interface UnsupportedProtocolVersionError extends Omit< + JSONRPCErrorResponse, + "error" +> { + error: Error & { + code: typeof UNSUPPORTED_PROTOCOL_VERSION; + data: { + /** + * An array of protocol version strings that the server supports. + */ + supportedVersions: string[]; + }; + }; } ``` @@ -304,6 +295,31 @@ If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). +If a server requires client capabilities to process a request and the client +has not provided them, the server MUST return a JSON-RPC error. For HTTP, the +response status code MUST be `400 Bad Request`. The client MAY then retry the +request with the required capabilities in `_meta`. + +```ts +export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; + +export interface MissingRequiredClientCapabilityError extends Omit< + JSONRPCErrorResponse, + "error" +> { + error: Error & { + code: typeof MISSING_REQUIRED_CLIENT_CAPABILITY; + data: { + /** + * The capabilities the server requires from the client + * to process this request. + */ + requiredCapabilities: ClientCapabilities; + }; + }; +} +``` + In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: From dc7824251724494ee438b279cd2063a88b2546f2 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:55:43 -0600 Subject: [PATCH 14/69] feat: use RequestMetaObject instead of Request for schema diffs --- docs/seps/2575-stateless-mcp.mdx | 54 +++++++++++--------------------- seps/2575-stateless-mcp.md | 54 +++++++++++--------------------- 2 files changed, 36 insertions(+), 72 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index d81b73cdd..b0d33f207 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -171,30 +171,16 @@ The `protocol-version` MUST be embedded directly within the `_meta` field of the request payload. For HTTP, this \_meta MUST match the associated HTTP header, or else the server should return a 400 Bad Request. -The following diff illustrates the required changes to the `Request` interface: +The following diff illustrates the required changes to `RequestMetaObject`: ```ts -export interface Request { - method: string; - params?: { - /** - * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. - */ - _meta?: { -+ /** -+ * The MCP Protocol Version being used for this request. -+ */ -+ "io.modelcontextprotocol/protocolVersion": string; - - /** - * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. - */ - progressToken?: ProgressToken; - [key: string]: unknown; - }; - [key: string]: unknown; - }; - } +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; ++ /** ++ * The MCP Protocol Version being used for this request. ++ */ ++ "io.modelcontextprotocol/protocolVersion": string; +} ``` #### Unsupported Protocol Versions @@ -361,22 +347,18 @@ This model applies when a client makes a standard RPC call and the server responds back with an SSE stream. The client specifies supported capabilities directly in the request. -The client adds an optional `clientCapabilities` field to the `_meta` object of -its request. For the HTTP transport, a server that supports this **MAY** then -respond with an SSE stream for that transaction. +The client adds an optional `clientCapabilities` field to `RequestMetaObject`. +For the HTTP transport, a server that supports this **MAY** then respond with +an SSE stream for that transaction. ```ts -export interface Request { - // ... - _meta?: { - // ... other meta fields -+ /** -+ * Optional capabilities of the client for this specific request. -+ */ -+ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; - // ... other meta fields - }; - // ... +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; + "io.modelcontextprotocol/protocolVersion": string; ++ /** ++ * Optional capabilities of the client for this specific request. ++ */ ++ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; } ``` diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index d3963475e..e5d186a5d 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -152,30 +152,16 @@ The `protocol-version` MUST be embedded directly within the `_meta` field of the request payload. For HTTP, this \_meta MUST match the associated HTTP header, or else the server should return a 400 Bad Request. -The following diff illustrates the required changes to the `Request` interface: +The following diff illustrates the required changes to `RequestMetaObject`: ```ts -export interface Request { - method: string; - params?: { - /** - * See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. - */ - _meta?: { -+ /** -+ * The MCP Protocol Version being used for this request. -+ */ -+ "io.modelcontextprotocol/protocolVersion": string; - - /** - * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. - */ - progressToken?: ProgressToken; - [key: string]: unknown; - }; - [key: string]: unknown; - }; - } +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; ++ /** ++ * The MCP Protocol Version being used for this request. ++ */ ++ "io.modelcontextprotocol/protocolVersion": string; +} ``` #### Unsupported Protocol Versions @@ -342,22 +328,18 @@ This model applies when a client makes a standard RPC call and the server responds back with an SSE stream. The client specifies supported capabilities directly in the request. -The client adds an optional `clientCapabilities` field to the `_meta` object of -its request. For the HTTP transport, a server that supports this **MAY** then -respond with an SSE stream for that transaction. +The client adds an optional `clientCapabilities` field to `RequestMetaObject`. +For the HTTP transport, a server that supports this **MAY** then respond with +an SSE stream for that transaction. ```ts -export interface Request { - // ... - _meta?: { - // ... other meta fields -+ /** -+ * Optional capabilities of the client for this specific request. -+ */ -+ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; - // ... other meta fields - }; - // ... +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; + "io.modelcontextprotocol/protocolVersion": string; ++ /** ++ * Optional capabilities of the client for this specific request. ++ */ ++ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; } ``` From e8356226a291cbe637c1978f14d47c78036fea6a Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:58:50 -0600 Subject: [PATCH 15/69] fix: clarify first SSE event wording --- docs/seps/2575-stateless-mcp.mdx | 4 ++-- seps/2575-stateless-mcp.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index b0d33f207..4b786f0c1 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -464,8 +464,8 @@ field. The server **MAY** then respond with an SSE stream for that transaction. To proactively open a persistent SSE stream, the client sends the dedicated `NotificationsListenRequest` via `POST`. The server's response **is an open SSE -stream** (`Content-Type: text/event-stream`), and the **first request** on this -stream **MUST** be an event containing the `NotificationsListenNotification`. +stream** (`Content-Type: text/event-stream`), and the first JSON-RPC message on +this stream **MUST** be a `NotificationsListenNotification`. ### Deprecated and Removed RPCs diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index e5d186a5d..07beab3d3 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -445,8 +445,8 @@ field. The server **MAY** then respond with an SSE stream for that transaction. To proactively open a persistent SSE stream, the client sends the dedicated `NotificationsListenRequest` via `POST`. The server's response **is an open SSE -stream** (`Content-Type: text/event-stream`), and the **first request** on this -stream **MUST** be an event containing the `NotificationsListenNotification`. +stream** (`Content-Type: text/event-stream`), and the first JSON-RPC message on +this stream **MUST** be a `NotificationsListenNotification`. ### Deprecated and Removed RPCs From f0d64cceab2ad6de56e1f50b214273d7f4fab6f6 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:00:56 -0600 Subject: [PATCH 16/69] chore: regenerate docs after rebase --- docs/docs.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs.json b/docs/docs.json index 1f721a3db..2fa4a8b09 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -434,6 +434,7 @@ { "group": "Draft", "pages": [ + "seps/2243-http-standardization", "seps/2575-stateless-mcp" ] }, From 0a79f5e1c7b1c5c5527d2ba18616d65d637399eb Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:38:02 -0600 Subject: [PATCH 17/69] fix: clarify HTTP header and payload must match --- docs/seps/2575-stateless-mcp.mdx | 15 ++++++++------- seps/2575-stateless-mcp.md | 9 +++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 4b786f0c1..7ebdb70f7 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -1,7 +1,7 @@ --- -title: "SEP-2575: Stateless-by-Default MCP" -sidebarTitle: "SEP-2575: Stateless-by-Default MCP" -description: "Stateless-by-Default MCP" +title: "SEP-2575: Make MCP Stateless" +sidebarTitle: "SEP-2575: Make MCP Stateless" +description: "Make MCP Stateless" ---
@@ -16,7 +16,7 @@ description: "Stateless-by-Default MCP" | Field | Value | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **SEP** | 2575 | -| **Title** | Stateless-by-Default MCP | +| **Title** | Make MCP Stateless | | **Status** | Draft | | **Type** | Standards Track | | **Created** | 2025-06-18 | @@ -153,9 +153,10 @@ handshake must now be included with **every request**. #### HTTP -For the HTTP transport, protocol version MUST be passed as **HTTP header**. For -the HTTP transport, the headers MUST be treated as the source of truth over the -request payload. +For the HTTP transport, protocol version MUST be passed as an **HTTP header**. +The header value MUST match the value provided in the request payload's `_meta` +field; otherwise the server MUST return a `400 Bad Request` (see +[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). - `MCP-Protocol-Version: 2025-06-18` - **Purpose**: To inform the server which version of the MCP specification diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 07beab3d3..123a84907 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -1,4 +1,4 @@ -# SEP-2575: Stateless-by-Default MCP +# SEP-2575: Make MCP Stateless - **Status**: Draft - **Type**: Standards Track @@ -134,9 +134,10 @@ handshake must now be included with **every request**. #### HTTP -For the HTTP transport, protocol version MUST be passed as **HTTP header**. For -the HTTP transport, the headers MUST be treated as the source of truth over the -request payload. +For the HTTP transport, protocol version MUST be passed as an **HTTP header**. +The header value MUST match the value provided in the request payload's `_meta` +field; otherwise the server MUST return a `400 Bad Request` (see +[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). - `MCP-Protocol-Version: 2025-06-18` - **Purpose**: To inform the server which version of the MCP specification From 1ffcaf7364efa092bbfb257bf6c514a9f961f3a4 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:40:14 -0600 Subject: [PATCH 18/69] fix: include roots, logLevel, clientInfo in RequestMetaObject diff --- docs/seps/2575-stateless-mcp.mdx | 15 +++++++++++++++ seps/2575-stateless-mcp.md | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 7ebdb70f7..876f640c3 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -181,6 +181,18 @@ export interface RequestMetaObject extends MetaObject { + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; ++ /** ++ * Identifies the client software. ++ */ ++ "io.modelcontextprotocol/clientInfo"?: Implementation; ++ /** ++ * The client's current root URIs. ++ */ ++ "io.modelcontextprotocol/roots"?: Root[]; ++ /** ++ * The desired log level for this request. ++ */ ++ "io.modelcontextprotocol/logLevel"?: LoggingLevel; } ``` @@ -356,6 +368,9 @@ an SSE stream for that transaction. export interface RequestMetaObject extends MetaObject { progressToken?: ProgressToken; "io.modelcontextprotocol/protocolVersion": string; + "io.modelcontextprotocol/clientInfo"?: Implementation; + "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** + * Optional capabilities of the client for this specific request. + */ diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 123a84907..42699a412 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -162,6 +162,18 @@ export interface RequestMetaObject extends MetaObject { + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; ++ /** ++ * Identifies the client software. ++ */ ++ "io.modelcontextprotocol/clientInfo"?: Implementation; ++ /** ++ * The client's current root URIs. ++ */ ++ "io.modelcontextprotocol/roots"?: Root[]; ++ /** ++ * The desired log level for this request. ++ */ ++ "io.modelcontextprotocol/logLevel"?: LoggingLevel; } ``` @@ -337,6 +349,9 @@ an SSE stream for that transaction. export interface RequestMetaObject extends MetaObject { progressToken?: ProgressToken; "io.modelcontextprotocol/protocolVersion": string; + "io.modelcontextprotocol/clientInfo"?: Implementation; + "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** + * Optional capabilities of the client for this specific request. + */ From 4efd9dbcfcc33acfa2d4ccd097ca45c26ad94b36 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:44:57 -0600 Subject: [PATCH 19/69] chore: add open question on _meta vs top-level fields --- docs/seps/2575-stateless-mcp.mdx | 14 ++++++++++++++ seps/2575-stateless-mcp.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 876f640c3..277eaf3e8 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -666,6 +666,20 @@ different implementations, leading to both confusion and incompatibility. ## Open Questions +### What belongs in `_meta` vs. as a top-level protocol field? + +This SEP places several previously-handshake-negotiated values +(`protocolVersion`, `clientInfo`, `roots`, `logLevel`, `clientCapabilities`) +into per-request `_meta` fields under the `io.modelcontextprotocol/` namespace. +This follows the spec's allowance for "purpose-specific metadata" reserved by +definitions in the schema. + +However, this risks overloading `_meta` over time — at what point do we add +top-level fields again? One possible distinction: required protocol-level +fields (e.g., `protocolVersion`) might better live as top-level fields, while +optional or extension-provided values stay in `_meta`. This question deserves +broader discussion before this SEP is finalized. + ### Should `clientInfo` be part of `ClientCapabilities`? Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 42699a412..1ab823060 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -647,6 +647,20 @@ different implementations, leading to both confusion and incompatibility. ## Open Questions +### What belongs in `_meta` vs. as a top-level protocol field? + +This SEP places several previously-handshake-negotiated values +(`protocolVersion`, `clientInfo`, `roots`, `logLevel`, `clientCapabilities`) +into per-request `_meta` fields under the `io.modelcontextprotocol/` namespace. +This follows the spec's allowance for "purpose-specific metadata" reserved by +definitions in the schema. + +However, this risks overloading `_meta` over time — at what point do we add +top-level fields again? One possible distinction: required protocol-level +fields (e.g., `protocolVersion`) might better live as top-level fields, while +optional or extension-provided values stay in `_meta`. This question deserves +broader discussion before this SEP is finalized. + ### Should `clientInfo` be part of `ClientCapabilities`? Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` From 3a24abc58aeff21d7cec7559b3233f125e9de55f Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:47:03 -0600 Subject: [PATCH 20/69] fix: clarify unsupported protocol version covers known and unknown versions --- docs/seps/2575-stateless-mcp.mdx | 4 +++- seps/2575-stateless-mcp.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 277eaf3e8..2993ef136 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -198,7 +198,9 @@ export interface RequestMetaObject extends MetaObject { #### Unsupported Protocol Versions -If a server receives a request with an unsupported protocol version, it MUST +If a server receives a request with a protocol version it does not implement +(whether the version is unknown to the server or is a known version the server +has chosen not to support, such as an experimental or draft version), it MUST return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 1ab823060..2bea39563 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -179,7 +179,9 @@ export interface RequestMetaObject extends MetaObject { #### Unsupported Protocol Versions -If a server receives a request with an unsupported protocol version, it MUST +If a server receives a request with a protocol version it does not implement +(whether the version is unknown to the server or is a known version the server +has chosen not to support, such as an experimental or draft version), it MUST return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: From 2f8e87de3bc6f158d35056f660c97ca15d6e3dd1 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:55:58 -0600 Subject: [PATCH 21/69] fix: align UnsupportedProtocolVersionError with current spec --- docs/seps/2575-stateless-mcp.mdx | 12 +++++++----- seps/2575-stateless-mcp.md | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 2993ef136..a62643d7a 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -205,19 +205,21 @@ return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: ```ts -export const UNSUPPORTED_PROTOCOL_VERSION = -32001; - export interface UnsupportedProtocolVersionError extends Omit< JSONRPCErrorResponse, "error" > { error: Error & { - code: typeof UNSUPPORTED_PROTOCOL_VERSION; + code: typeof INVALID_PARAMS; data: { /** * An array of protocol version strings that the server supports. */ - supportedVersions: string[]; + supported: string[]; + /** + * The protocol version that was requested by the client. + */ + requested: string; }; }; } @@ -232,7 +234,7 @@ Without an initialization handshake, version negotiation happens inline: `io.modelcontextprotocol/protocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an - `UnsupportedVersionError` containing its list of `supportedVersions`. + `UnsupportedProtocolVersionError` containing its list of `supported` versions. 4. The client selects a mutually supported version from the list and retries. Alternatively, a client **MAY** call `server/discover` first to learn the diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 2bea39563..c7aba4f9a 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -186,19 +186,21 @@ return a JSON-RPC error response. For HTTP, the response status code MUST be `400 Bad Request`. The error MUST conform to the following structure: ```ts -export const UNSUPPORTED_PROTOCOL_VERSION = -32001; - export interface UnsupportedProtocolVersionError extends Omit< JSONRPCErrorResponse, "error" > { error: Error & { - code: typeof UNSUPPORTED_PROTOCOL_VERSION; + code: typeof INVALID_PARAMS; data: { /** * An array of protocol version strings that the server supports. */ - supportedVersions: string[]; + supported: string[]; + /** + * The protocol version that was requested by the client. + */ + requested: string; }; }; } @@ -213,7 +215,7 @@ Without an initialization handshake, version negotiation happens inline: `io.modelcontextprotocol/protocolVersion` `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an - `UnsupportedVersionError` containing its list of `supportedVersions`. + `UnsupportedProtocolVersionError` containing its list of `supported` versions. 4. The client selects a mutually supported version from the list and retries. Alternatively, a client **MAY** call `server/discover` first to learn the From f760e1f653c3777ff69f2de149fb0431d5263686 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:00:49 -0600 Subject: [PATCH 22/69] docs: add FAQ on server/discover vs Server Card --- docs/seps/2575-stateless-mcp.mdx | 11 +++++++++++ seps/2575-stateless-mcp.md | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index a62643d7a..7040d5153 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -668,6 +668,17 @@ applications, and easier to proxy between different transports correctly. Otherwise, there will continue to be feature gaps and division between these different implementations, leading to both confusion and incompatibility. +### How does `server/discover` relate to the MCP Server Card? + +The `server/discover` RPC overlaps with the +[MCP Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) +proposal, which defines a `.well-known/mcp.json` document for HTTP-based +discovery. Both mechanisms are intentionally retained: the Server Card is +well-suited to HTTP (no auth required, cacheable, indexable) while +`server/discover` provides a unified RPC interface that works consistently +across HTTP and STDIO transports. The two should be aligned on content where +applicable. + ## Open Questions ### What belongs in `_meta` vs. as a top-level protocol field? diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index c7aba4f9a..727fb5518 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -649,6 +649,17 @@ applications, and easier to proxy between different transports correctly. Otherwise, there will continue to be feature gaps and division between these different implementations, leading to both confusion and incompatibility. +### How does `server/discover` relate to the MCP Server Card? + +The `server/discover` RPC overlaps with the +[MCP Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) +proposal, which defines a `.well-known/mcp.json` document for HTTP-based +discovery. Both mechanisms are intentionally retained: the Server Card is +well-suited to HTTP (no auth required, cacheable, indexable) while +`server/discover` provides a unified RPC interface that works consistently +across HTTP and STDIO transports. The two should be aligned on content where +applicable. + ## Open Questions ### What belongs in `_meta` vs. as a top-level protocol field? From f39f22afed8105b7c1caacb9416f2f0c5c89fedd Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:24:16 -0600 Subject: [PATCH 23/69] fix: rename DiscoveryRequest/Result to DiscoverRequest/Result --- docs/seps/2575-stateless-mcp.mdx | 4 ++-- seps/2575-stateless-mcp.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 7040d5153..0d08ea9b7 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -259,7 +259,7 @@ the response status code MUST be `404 Not Found`. **Request Schema:** ```ts -export interface DiscoveryRequest extends Request { +export interface DiscoverRequest extends Request { method: "server/discover"; params?: {}; } @@ -268,7 +268,7 @@ export interface DiscoveryRequest extends Request { **Response Schema:** ```ts -export interface DiscoveryResult extends Result { +export interface DiscoverResult extends Result { /** * A list of MCP Protocol Version strings that this server supports. * The client should choose a version from this list for use in diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 727fb5518..91b53bc19 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -240,7 +240,7 @@ the response status code MUST be `404 Not Found`. **Request Schema:** ```ts -export interface DiscoveryRequest extends Request { +export interface DiscoverRequest extends Request { method: "server/discover"; params?: {}; } @@ -249,7 +249,7 @@ export interface DiscoveryRequest extends Request { **Response Schema:** ```ts -export interface DiscoveryResult extends Result { +export interface DiscoverResult extends Result { /** * A list of MCP Protocol Version strings that this server supports. * The client should choose a version from this list for use in From c212b4232f20c925aaf3ed7b0acbe2b31282ad3c Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:31:55 -0600 Subject: [PATCH 24/69] fix: clarify server/discover is required for servers, optional for clients --- docs/seps/2575-stateless-mcp.mdx | 11 ++++++----- seps/2575-stateless-mcp.md | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 0d08ea9b7..969c062c7 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -240,16 +240,17 @@ Without an initialization handshake, version negotiation happens inline: Alternatively, a client **MAY** call `server/discover` first to learn the server's supported versions before sending any other requests. -### Optional Discovery for Server Capabilities +### Discovery for Server Capabilities To allow clients to adapt to different server implementations, this specification introduces a **discovery RPC**. This provides a standard mechanism for a server to advertise its supported protocol versions and capabilities. -This discovery step is **OPTIONAL**. A client is free to invoke any RPC without -first calling the discovery endpoint. If a client calls an unsupported RPC, the -server **MUST** return a `Method not found` JSON-RPC error (`-32601`). For HTTP, -the response status code MUST be `404 Not Found`. +Servers **MUST** implement `server/discover`. Clients **MAY** call it but are +not required to — a client is free to invoke any RPC without first calling the +discovery endpoint. If a client calls an unsupported RPC, the server **MUST** +return a `Method not found` JSON-RPC error (`-32601`). For HTTP, the response +status code MUST be `404 Not Found`. #### `server/discover` RPC diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 91b53bc19..f3ccd1907 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -221,16 +221,17 @@ Without an initialization handshake, version negotiation happens inline: Alternatively, a client **MAY** call `server/discover` first to learn the server's supported versions before sending any other requests. -### Optional Discovery for Server Capabilities +### Discovery for Server Capabilities To allow clients to adapt to different server implementations, this specification introduces a **discovery RPC**. This provides a standard mechanism for a server to advertise its supported protocol versions and capabilities. -This discovery step is **OPTIONAL**. A client is free to invoke any RPC without -first calling the discovery endpoint. If a client calls an unsupported RPC, the -server **MUST** return a `Method not found` JSON-RPC error (`-32601`). For HTTP, -the response status code MUST be `404 Not Found`. +Servers **MUST** implement `server/discover`. Clients **MAY** call it but are +not required to — a client is free to invoke any RPC without first calling the +discovery endpoint. If a client calls an unsupported RPC, the server **MUST** +return a `Method not found` JSON-RPC error (`-32601`). For HTTP, the response +status code MUST be `404 Not Found`. #### `server/discover` RPC From 30c3c2db6c7620620ff56e1f51032e005a664360 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:38:01 -0600 Subject: [PATCH 25/69] fix: require clientCapabilities on every request --- docs/seps/2575-stateless-mcp.mdx | 28 +++++++++++++++------------- seps/2575-stateless-mcp.md | 28 +++++++++++++++------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 969c062c7..47ba7c52c 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -299,10 +299,12 @@ export interface DiscoverResult extends Result { ### Per-Request Client Capabilities To complete the decoupling from the initial handshake, client capabilities are -no longer negotiated once at initialization. Instead, a client **MAY** specify its -capabilities on a per-request basis. This allows the server to know what -optional features the client can handle for a specific transaction, such as -streaming responses. +no longer negotiated once at initialization. Instead, a client **MUST** specify +its capabilities on every request. This ensures the server is always fully +informed about what optional features the client can handle for that specific +transaction, such as streaming responses. An absent or empty capabilities +object means the client supports no optional capabilities — servers **MUST +NOT** infer capabilities from prior requests. A server **SHOULD** only send requests that match a client's provided capabilities. The server may send these requests in two ways: @@ -318,10 +320,10 @@ If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). -If a server requires client capabilities to process a request and the client -has not provided them, the server MUST return a JSON-RPC error. For HTTP, the -response status code MUST be `400 Bad Request`. The client MAY then retry the -request with the required capabilities in `_meta`. +If a server requires client capabilities the client has not declared, the +server MUST return a JSON-RPC error. For HTTP, the response status code MUST +be `400 Bad Request`. The error data identifies the required capabilities so +the client knows which capabilities are missing. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; @@ -365,9 +367,9 @@ This model applies when a client makes a standard RPC call and the server responds back with an SSE stream. The client specifies supported capabilities directly in the request. -The client adds an optional `clientCapabilities` field to `RequestMetaObject`. -For the HTTP transport, a server that supports this **MAY** then respond with -an SSE stream for that transaction. +The client adds a `clientCapabilities` field to `RequestMetaObject`. For the +HTTP transport, a server that supports this **MAY** then respond with an SSE +stream for that transaction. ```ts export interface RequestMetaObject extends MetaObject { @@ -377,9 +379,9 @@ export interface RequestMetaObject extends MetaObject { "io.modelcontextprotocol/roots"?: Root[]; "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** -+ * Optional capabilities of the client for this specific request. ++ * Capabilities of the client for this specific request. + */ -+ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; ++ "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; } ``` diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index f3ccd1907..3d57304cc 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -280,10 +280,12 @@ export interface DiscoverResult extends Result { ### Per-Request Client Capabilities To complete the decoupling from the initial handshake, client capabilities are -no longer negotiated once at initialization. Instead, a client **MAY** specify its -capabilities on a per-request basis. This allows the server to know what -optional features the client can handle for a specific transaction, such as -streaming responses. +no longer negotiated once at initialization. Instead, a client **MUST** specify +its capabilities on every request. This ensures the server is always fully +informed about what optional features the client can handle for that specific +transaction, such as streaming responses. An absent or empty capabilities +object means the client supports no optional capabilities — servers **MUST +NOT** infer capabilities from prior requests. A server **SHOULD** only send requests that match a client's provided capabilities. The server may send these requests in two ways: @@ -299,10 +301,10 @@ If a server sends a request that erroneously calls a client capability it doesn't support, a client MUST return a `Method not found` JSON-RPC error (`-32601`). -If a server requires client capabilities to process a request and the client -has not provided them, the server MUST return a JSON-RPC error. For HTTP, the -response status code MUST be `400 Bad Request`. The client MAY then retry the -request with the required capabilities in `_meta`. +If a server requires client capabilities the client has not declared, the +server MUST return a JSON-RPC error. For HTTP, the response status code MUST +be `400 Bad Request`. The error data identifies the required capabilities so +the client knows which capabilities are missing. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; @@ -346,9 +348,9 @@ This model applies when a client makes a standard RPC call and the server responds back with an SSE stream. The client specifies supported capabilities directly in the request. -The client adds an optional `clientCapabilities` field to `RequestMetaObject`. -For the HTTP transport, a server that supports this **MAY** then respond with -an SSE stream for that transaction. +The client adds a `clientCapabilities` field to `RequestMetaObject`. For the +HTTP transport, a server that supports this **MAY** then respond with an SSE +stream for that transaction. ```ts export interface RequestMetaObject extends MetaObject { @@ -358,9 +360,9 @@ export interface RequestMetaObject extends MetaObject { "io.modelcontextprotocol/roots"?: Root[]; "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** -+ * Optional capabilities of the client for this specific request. ++ * Capabilities of the client for this specific request. + */ -+ "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; ++ "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; } ``` From 496a9c568b3a39c4af0ebb7c5fc58bcf220481b7 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:53:13 -0600 Subject: [PATCH 26/69] fix: explain how SEP-2260 and SEP-2322 eliminate server-to-client requests --- docs/seps/2575-stateless-mcp.mdx | 37 +++++++++++++++++++++----------- seps/2575-stateless-mcp.md | 37 +++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 47ba7c52c..99eced0f8 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -306,19 +306,30 @@ transaction, such as streaming responses. An absent or empty capabilities object means the client supports no optional capabilities — servers **MUST NOT** infer capabilities from prior requests. -A server **SHOULD** only send requests that match a client's provided -capabilities. The server may send these requests in two ways: - -1. **Inline**: As notifications within an SSE stream response to a triggering - RPC (e.g., `notifications/progress` within a `tools/call` response stream). -2. **On the listen stream**: As events on an open `notifications/listen` SSE stream. - -In both cases, the server uses the `clientCapabilities` from the request's -`_meta` to determine what it is allowed to send. - -If a server sends a request that erroneously calls a client capability it -doesn't support, a client MUST return a `Method not found` JSON-RPC error -(`-32601`). +Two related SEPs together eliminate independent server-to-client requests +entirely: + +- [SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260) + restricts the `notifications/listen` SSE stream to notifications only — no + requests flow on this channel. +- [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) + (Multi Round-Trip Requests, MRTR) introduces `IncompleteResult`, which lets + a server embed input requests (elicitation, sampling, roots) within the + response to a specific client request (e.g., `CallTool`, `GetPrompt`, + `ListResources`). The client satisfies these and retries the original + request. + +With both in place, the server never independently calls the client. Instead, +when processing a request, the server inspects the declared +`clientCapabilities` and decides between two paths: + +- **Required capability missing** — the server returns + `MissingRequiredClientCapabilityError` indicating which capability it needs. +- **Required capability present** — the server returns an `IncompleteResult` + containing the relevant input requests, or completes the request directly + if no client interaction is needed. + +A server **MUST NOT** rely on capabilities the client has not declared. If a server requires client capabilities the client has not declared, the server MUST return a JSON-RPC error. For HTTP, the response status code MUST diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 3d57304cc..945102e43 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -287,19 +287,30 @@ transaction, such as streaming responses. An absent or empty capabilities object means the client supports no optional capabilities — servers **MUST NOT** infer capabilities from prior requests. -A server **SHOULD** only send requests that match a client's provided -capabilities. The server may send these requests in two ways: - -1. **Inline**: As notifications within an SSE stream response to a triggering - RPC (e.g., `notifications/progress` within a `tools/call` response stream). -2. **On the listen stream**: As events on an open `notifications/listen` SSE stream. - -In both cases, the server uses the `clientCapabilities` from the request's -`_meta` to determine what it is allowed to send. - -If a server sends a request that erroneously calls a client capability it -doesn't support, a client MUST return a `Method not found` JSON-RPC error -(`-32601`). +Two related SEPs together eliminate independent server-to-client requests +entirely: + +- [SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260) + restricts the `notifications/listen` SSE stream to notifications only — no + requests flow on this channel. +- [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) + (Multi Round-Trip Requests, MRTR) introduces `IncompleteResult`, which lets + a server embed input requests (elicitation, sampling, roots) within the + response to a specific client request (e.g., `CallTool`, `GetPrompt`, + `ListResources`). The client satisfies these and retries the original + request. + +With both in place, the server never independently calls the client. Instead, +when processing a request, the server inspects the declared +`clientCapabilities` and decides between two paths: + +- **Required capability missing** — the server returns + `MissingRequiredClientCapabilityError` indicating which capability it needs. +- **Required capability present** — the server returns an `IncompleteResult` + containing the relevant input requests, or completes the request directly + if no client interaction is needed. + +A server **MUST NOT** rely on capabilities the client has not declared. If a server requires client capabilities the client has not declared, the server MUST return a JSON-RPC error. For HTTP, the response status code MUST From 1529765a9b97c04916bfacfecdec53a228b2e9b6 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:55:40 -0600 Subject: [PATCH 27/69] fix: tighten missing capability error wording --- docs/seps/2575-stateless-mcp.mdx | 7 +++---- seps/2575-stateless-mcp.md | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 99eced0f8..af72a19f8 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -331,10 +331,9 @@ when processing a request, the server inspects the declared A server **MUST NOT** rely on capabilities the client has not declared. -If a server requires client capabilities the client has not declared, the -server MUST return a JSON-RPC error. For HTTP, the response status code MUST -be `400 Bad Request`. The error data identifies the required capabilities so -the client knows which capabilities are missing. +If a server requires client capabilities the client has not provided, the +server MUST return a JSON-RPC error, which specifies the missing capabilities. +For HTTP, the response status code MUST be `400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 945102e43..67ca09915 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -312,10 +312,9 @@ when processing a request, the server inspects the declared A server **MUST NOT** rely on capabilities the client has not declared. -If a server requires client capabilities the client has not declared, the -server MUST return a JSON-RPC error. For HTTP, the response status code MUST -be `400 Bad Request`. The error data identifies the required capabilities so -the client knows which capabilities are missing. +If a server requires client capabilities the client has not provided, the +server MUST return a JSON-RPC error, which specifies the missing capabilities. +For HTTP, the response status code MUST be `400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; From faf8e67b8205f8e63e88df342714ccfeb463afcd Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:06:24 -0600 Subject: [PATCH 28/69] fix: define absence semantics for _meta fields and defer roots to MRTR --- docs/seps/2575-stateless-mcp.mdx | 50 +++++++++++++++++++------------- seps/2575-stateless-mcp.md | 50 +++++++++++++++++++------------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index af72a19f8..4003d714b 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -184,11 +184,7 @@ export interface RequestMetaObject extends MetaObject { + /** + * Identifies the client software. + */ -+ "io.modelcontextprotocol/clientInfo"?: Implementation; -+ /** -+ * The client's current root URIs. -+ */ -+ "io.modelcontextprotocol/roots"?: Root[]; ++ "io.modelcontextprotocol/clientInfo": Implementation; + /** + * The desired log level for this request. + */ @@ -359,11 +355,25 @@ In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: - `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the - client software without requiring an initialization handshake. -- `"io.modelcontextprotocol/roots"`: `Root[]` — the client's current root - URIs, replacing the need for `notifications/roots/list_changed`. + client software. **Required.** The `Implementation` schema requires `name` + and `version`; other fields are optional. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log - level for this request, replacing the `logging/setLevel` RPC. + level for this request. **Optional.** If absent, the server **MUST NOT** + send any log notifications for this request. The client opts in to log + messages by explicitly setting a level. Replaces the `logging/setLevel` + RPC. + +Roots are intentionally not included as a per-request `_meta` field. Servers +that need the client's roots **MUST** request them via the MRTR +`ListRootsRequest` mechanism (see +[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)), +which avoids putting potentially large root lists on every request and +follows the "pay as you go" principle. + +The required fields (`protocolVersion`, `clientCapabilities`, `clientInfo`) +MUST be present on every request. A request missing any of these is +malformed; the server **MUST** reject it with `INVALID_PARAMS` (and `400 Bad +Request` for HTTP). The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -385,8 +395,7 @@ stream for that transaction. export interface RequestMetaObject extends MetaObject { progressToken?: ProgressToken; "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientInfo"?: Implementation; - "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/clientInfo": Implementation; "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** + * Capabilities of the client for this specific request. @@ -419,8 +428,8 @@ export interface NotificationsListenRequest extends Request { params: { _meta?: { "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; - "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/clientInfo": Implementation; + "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; // ... other meta fields }; @@ -511,13 +520,14 @@ the following RPC methods and notifications are removed: handled by `server/discover`. Servers compliant with this SEP **SHOULD** accept and ignore `notifications/initialized` without error to maintain backward compatibility with clients that may send it. -- `logging/setLevel`: This method is removed. Log levels should now be - specified on a per-request basis using the - `'io.modelcontextprotocol/logLevel'` field in the `_meta` object. -- `notifications/roots/list_changed`: This notification is removed. Clients - now provide their current roots directly in per-request `_meta` fields. - Since the server receives the current roots with each request, there is no - need for a separate change notification. +- `logging/setLevel`: Removed. The log level is now specified per-request + via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no + replacement RPC. +- `roots/list`: Removed as a top-level server-to-client RPC. Servers that + need the client's roots **MUST** request them via the MRTR + `ListRootsRequest` mechanism (see SEP-2322). +- `notifications/roots/list_changed`: Removed. Roots are fetched on demand + via MRTR, so there is no need for a change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember which resources each client has subscribed to. Instead, clients declare diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 67ca09915..d0f3a283b 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -165,11 +165,7 @@ export interface RequestMetaObject extends MetaObject { + /** + * Identifies the client software. + */ -+ "io.modelcontextprotocol/clientInfo"?: Implementation; -+ /** -+ * The client's current root URIs. -+ */ -+ "io.modelcontextprotocol/roots"?: Root[]; ++ "io.modelcontextprotocol/clientInfo": Implementation; + /** + * The desired log level for this request. + */ @@ -340,11 +336,25 @@ In addition to `clientCapabilities`, the following fields previously exchanged during initialization **MAY** be included in per-request `_meta` fields: - `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the - client software without requiring an initialization handshake. -- `"io.modelcontextprotocol/roots"`: `Root[]` — the client's current root - URIs, replacing the need for `notifications/roots/list_changed`. + client software. **Required.** The `Implementation` schema requires `name` + and `version`; other fields are optional. - `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log - level for this request, replacing the `logging/setLevel` RPC. + level for this request. **Optional.** If absent, the server **MUST NOT** + send any log notifications for this request. The client opts in to log + messages by explicitly setting a level. Replaces the `logging/setLevel` + RPC. + +Roots are intentionally not included as a per-request `_meta` field. Servers +that need the client's roots **MUST** request them via the MRTR +`ListRootsRequest` mechanism (see +[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)), +which avoids putting potentially large root lists on every request and +follows the "pay as you go" principle. + +The required fields (`protocolVersion`, `clientCapabilities`, `clientInfo`) +MUST be present on every request. A request missing any of these is +malformed; the server **MUST** reject it with `INVALID_PARAMS` (and `400 Bad +Request` for HTTP). The primary capability defined in this proposal is the ability to handle streaming responses, which is supported through two distinct models: @@ -366,8 +376,7 @@ stream for that transaction. export interface RequestMetaObject extends MetaObject { progressToken?: ProgressToken; "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientInfo"?: Implementation; - "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/clientInfo": Implementation; "io.modelcontextprotocol/logLevel"?: LoggingLevel; + /** + * Capabilities of the client for this specific request. @@ -400,8 +409,8 @@ export interface NotificationsListenRequest extends Request { params: { _meta?: { "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientCapabilities"?: ClientCapabilities; - "io.modelcontextprotocol/roots"?: Root[]; + "io.modelcontextprotocol/clientInfo": Implementation; + "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; // ... other meta fields }; @@ -492,13 +501,14 @@ the following RPC methods and notifications are removed: handled by `server/discover`. Servers compliant with this SEP **SHOULD** accept and ignore `notifications/initialized` without error to maintain backward compatibility with clients that may send it. -- `logging/setLevel`: This method is removed. Log levels should now be - specified on a per-request basis using the - `'io.modelcontextprotocol/logLevel'` field in the `_meta` object. -- `notifications/roots/list_changed`: This notification is removed. Clients - now provide their current roots directly in per-request `_meta` fields. - Since the server receives the current roots with each request, there is no - need for a separate change notification. +- `logging/setLevel`: Removed. The log level is now specified per-request + via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no + replacement RPC. +- `roots/list`: Removed as a top-level server-to-client RPC. Servers that + need the client's roots **MUST** request them via the MRTR + `ListRootsRequest` mechanism (see SEP-2322). +- `notifications/roots/list_changed`: Removed. Roots are fetched on demand + via MRTR, so there is no need for a change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember which resources each client has subscribed to. Instead, clients declare From c4a0c19b34e95a4686b0942f7cad468d00e56e1a Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:19:11 -0600 Subject: [PATCH 29/69] fix: restructure Per-Request Client Capabilities section --- docs/seps/2575-stateless-mcp.mdx | 391 ++++++++++++++----------------- docs/seps/index.mdx | 2 +- seps/2575-stateless-mcp.md | 374 ++++++++++++++--------------- 3 files changed, 355 insertions(+), 412 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 4003d714b..782cf01c2 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -13,16 +13,16 @@ description: "Make MCP Stateless"
-| Field | Value | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **SEP** | 2575 | -| **Title** | Make MCP Stateless | -| **Status** | Draft | -| **Type** | Standards Track | -| **Created** | 2025-06-18 | -| **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Mark Roth ([@markdroth](https://github.com/markdroth)), Shaun Smith ([@evalstate](https://github.com/evalstate)), Harvey Tuch ([@htuch](https://github.com/htuch)), Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) | -| **Sponsor** | Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) | -| **PR** | [#2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) | +| Field | Value | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| **SEP** | 2575 | +| **Title** | Make MCP Stateless | +| **Status** | Draft | +| **Type** | Standards Track | +| **Created** | 2025-06-18 | +| **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Mark Roth ([@markdroth](https://github.com/markdroth)), | +| **Sponsor** | Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) | +| **PR** | [#2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) | --- @@ -83,10 +83,9 @@ and scalability. - **Server-side:** Developers must implement logic to create, manage, and eventually garbage-collect per-client session state. This is a common source of bugs and memory leaks. - - **Client-side:** Developers must write complex code to manage a - persistent connection and handle the inevitable network failures and - reconnections, including the logic to resynchronize state after a - disconnect. + - **Client-side:** Developers must write complex code to manage a persistent + connection and handle the inevitable network failures and reconnections, + including the logic to resynchronize state after a disconnect. ## Design Principles @@ -132,19 +131,15 @@ initialization phase, the specification creates an implied link between them, particularly between the exchange of capabilities and a mandatory connection lifecycle. -This proposal is to **remove the initialization handshake** and "unbundle" -its functions into discrete, stateless components. We will provide new, more -clearly defined mechanisms for clients and servers to exchange this information -without a mandatory state-creating cycle. +This proposal is to **remove the initialization handshake** and "unbundle" its +functions into discrete, stateless components. We will provide new, more clearly +defined mechanisms for clients and servers to exchange this information without +a mandatory state-creating cycle. > **Note:** Session management (both transport-level and application-level) is -> addressed separately by -> [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) -> and -> [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567). -> This SEP focuses exclusively on removing the initialization handshake and -> providing stateless alternatives for version negotiation, discovery, and -> capabilities. +> addressed separately by [SEP-2322][SEP-2322] and [SEP-2567][SEP-2567]. This +> SEP focuses exclusively on removing the initialization handshake and providing +> stateless alternatives for version negotiation, discovery, and capabilities. ### Protocol Version @@ -156,15 +151,14 @@ handshake must now be included with **every request**. For the HTTP transport, protocol version MUST be passed as an **HTTP header**. The header value MUST match the value provided in the request payload's `_meta` field; otherwise the server MUST return a `400 Bad Request` (see -[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). +[SEP-2243][SEP-2243]). - `MCP-Protocol-Version: 2025-06-18` - - **Purpose**: To inform the server which version of the MCP specification - the client is using for this specific request. + - **Purpose**: To inform the server which version of the MCP specification the + client is using for this specific request. - **Requirement**: This header is **MANDATORY**. Servers should reject requests with a missing or unsupported version. - - This header MUST match the value provided in the Request as specified - below. + - This header MUST match the value provided in the Request as specified below. #### Per-request Version @@ -181,14 +175,8 @@ export interface RequestMetaObject extends MetaObject { + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; -+ /** -+ * Identifies the client software. -+ */ -+ "io.modelcontextprotocol/clientInfo": Implementation; -+ /** -+ * The desired log level for this request. -+ */ -+ "io.modelcontextprotocol/logLevel"?: LoggingLevel; + // Additional per-request fields (clientInfo, clientCapabilities, logLevel) + // are introduced in the Per-Request Client Capabilities section below. } ``` @@ -226,11 +214,12 @@ export interface UnsupportedProtocolVersionError extends Omit< Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the - `MCP-Protocol-Version` header and - `io.modelcontextprotocol/protocolVersion` `_meta` field. + `MCP-Protocol-Version` header and `io.modelcontextprotocol/protocolVersion` + `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an - `UnsupportedProtocolVersionError` containing its list of `supported` versions. + `UnsupportedProtocolVersionError` containing its list of `supported` + versions. 4. The client selects a mutually supported version from the list and retries. Alternatively, a client **MAY** call `server/discover` first to learn the @@ -250,8 +239,8 @@ status code MUST be `404 Not Found`. #### `server/discover` RPC -- **Purpose**: To allow a client to query the server for its supported - protocol versions, capabilities, and other metadata. +- **Purpose**: To allow a client to query the server for its supported protocol + versions, capabilities, and other metadata. **Request Schema:** @@ -298,38 +287,85 @@ To complete the decoupling from the initial handshake, client capabilities are no longer negotiated once at initialization. Instead, a client **MUST** specify its capabilities on every request. This ensures the server is always fully informed about what optional features the client can handle for that specific -transaction, such as streaming responses. An absent or empty capabilities -object means the client supports no optional capabilities — servers **MUST -NOT** infer capabilities from prior requests. - -Two related SEPs together eliminate independent server-to-client requests -entirely: - -- [SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260) - restricts the `notifications/listen` SSE stream to notifications only — no - requests flow on this channel. -- [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) - (Multi Round-Trip Requests, MRTR) introduces `IncompleteResult`, which lets - a server embed input requests (elicitation, sampling, roots) within the - response to a specific client request (e.g., `CallTool`, `GetPrompt`, - `ListResources`). The client satisfies these and retries the original - request. - -With both in place, the server never independently calls the client. Instead, -when processing a request, the server inspects the declared -`clientCapabilities` and decides between two paths: - -- **Required capability missing** — the server returns - `MissingRequiredClientCapabilityError` indicating which capability it needs. -- **Required capability present** — the server returns an `IncompleteResult` - containing the relevant input requests, or completes the request directly - if no client interaction is needed. - -A server **MUST NOT** rely on capabilities the client has not declared. - -If a server requires client capabilities the client has not provided, the -server MUST return a JSON-RPC error, which specifies the missing capabilities. -For HTTP, the response status code MUST be `400 Bad Request`. +transaction. An absent or empty capabilities object means the client supports no +optional capabilities — servers **MUST NOT** infer capabilities from prior +requests. + +#### Per-Request Metadata Schema + +Every request's `_meta` carries a small set of fields that previously lived in +the initialization handshake. The full `RequestMetaObject` shape: + +```ts +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; + /** + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; + /** + * Identifies the client software. + */ + "io.modelcontextprotocol/clientInfo": Implementation; + /** + * Capabilities of the client for this specific request. + */ + "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; + /** + * The desired log level for this request. + */ + "io.modelcontextprotocol/logLevel"?: LoggingLevel; +} +``` + +Field semantics: + +- `"io.modelcontextprotocol/protocolVersion"`: `string` — the MCP Protocol + Version. **Required.** See the Protocol Version section above for negotiation + details. +- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the + client software. **Required.** The `Implementation` schema requires `name` and + `version`; other fields are optional. +- `"io.modelcontextprotocol/clientCapabilities"`: `ClientCapabilities` — the + client's capabilities for this request. **Required.** +- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level + for this request. **Optional.** If absent, the server **MUST NOT** send any + log notifications for this request. The client opts in to log messages by + explicitly setting a level. Replaces the `logging/setLevel` RPC. + +Roots are intentionally not included as a per-request `_meta` field. Servers +that need the client's roots **MUST** request them via the MRTR +`ListRootsRequest` mechanism (see [SEP-2322][SEP-2322]), which avoids putting +potentially large root lists on every request and follows the "pay as you go" +principle. + +A request missing any required field is malformed; the server **MUST** reject it +with `INVALID_PARAMS` (and `400 Bad Request` for HTTP). + +#### Response Streaming + +These declared capabilities govern what the server may include in the response +stream. [SEP-2322][SEP-2322] (MRTR) defines how server-to-client interactions +are embedded inline within responses via `IncompleteResult`; this SEP specifies +that those interactions are governed by the per-request `clientCapabilities` +declared in `RequestMetaObject`. + +For HTTP, any request's response **MAY** be delivered as an SSE stream +(`Content-Type: text/event-stream`) instead of a single JSON object. Only +notifications (e.g., `notifications/progress`, `notifications/message`) flow as +independent messages on this stream, followed by the final result. +Server-to-client interactions (sampling, elicitation, listRoots) are **not** +sent as independent requests — they are embedded as input requests inside an +`IncompleteResult` returned from specific request paths (e.g., `CallTool`, +`GetPrompt`, `ListResources`). The client satisfies the input requests and +retries the original request. + +#### Missing Required Capabilities + +A server **MUST NOT** rely on capabilities the client has not declared. If a +server requires client capabilities the client has not provided, the server +**MUST** return a JSON-RPC error, which specifies the missing capabilities. For +HTTP, the response status code MUST be `400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; @@ -351,74 +387,19 @@ export interface MissingRequiredClientCapabilityError extends Omit< } ``` -In addition to `clientCapabilities`, the following fields previously exchanged -during initialization **MAY** be included in per-request `_meta` fields: +### `notifications/listen` RPC -- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the - client software. **Required.** The `Implementation` schema requires `name` - and `version`; other fields are optional. -- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log - level for this request. **Optional.** If absent, the server **MUST NOT** - send any log notifications for this request. The client opts in to log - messages by explicitly setting a level. Replaces the `logging/setLevel` - RPC. +This SEP introduces a new `notifications/listen` RPC that replaces the previous +HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. A +client uses it to open a long-lived channel for receiving notifications outside +the context of a specific request. -Roots are intentionally not included as a per-request `_meta` field. Servers -that need the client's roots **MUST** request them via the MRTR -`ListRootsRequest` mechanism (see -[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)), -which avoids putting potentially large root lists on every request and -follows the "pay as you go" principle. - -The required fields (`protocolVersion`, `clientCapabilities`, `clientInfo`) -MUST be present on every request. A request missing any of these is -malformed; the server **MUST** reject it with `INVALID_PARAMS` (and `400 Bad -Request` for HTTP). - -The primary capability defined in this proposal is the ability to handle -streaming responses, which is supported through two distinct models: -server-initiated and client-initiated. - -#### Streaming Models - -##### Server-Initiated Streaming (Response Stream) - -This model applies when a client makes a standard RPC call and the server -responds back with an SSE stream. The client specifies supported capabilities -directly in the request. - -The client adds a `clientCapabilities` field to `RequestMetaObject`. For the -HTTP transport, a server that supports this **MAY** then respond with an SSE -stream for that transaction. - -```ts -export interface RequestMetaObject extends MetaObject { - progressToken?: ProgressToken; - "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientInfo": Implementation; - "io.modelcontextprotocol/logLevel"?: LoggingLevel; -+ /** -+ * Capabilities of the client for this specific request. -+ */ -+ "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; -} -``` - -##### Client-Initiated Streaming (Background Streaming) - -This model applies when a client wants to proactively open a persistent SSE -stream to receive multiple or unsolicited events. - -This is achieved using a dedicated `notifications/listen` RPC. For the HTTP -transport, the client sends this request via `POST`, and the server's response -is an open SSE stream, with a `NotificationsListenNotification` sent as the -first event. For the STDIO transport, this RPC is used to declare the client's -capabilities and interests. +The HTTP GET endpoint used by Streamable HTTP for server-to-client messages is +**removed** in this version of the protocol. All communication uses POST. -This RPC replaces the existing HTTP GET endpoint for Streamable HTTP. The GET -endpoint is removed; all communication uses POST. Only notifications (not -requests) may be sent on the listen stream, per -[SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260). +Per [SEP-2260][SEP-2260], only notifications (not requests) flow on this +channel; server-initiated requests use MRTR (see Response Streaming above) and +are scoped to a specific client request. **Request Schema:** @@ -434,11 +415,11 @@ export interface NotificationsListenRequest extends Request { }; /** - * Optional filter for which notifications the client wants to receive. - * If omitted, the server SHOULD send all notifications the client's - * capabilities support. + * The notifications the client wants to receive on this stream. + * Each notification type is opt-in; the server **MUST NOT** send + * notification types the client has not explicitly requested here. */ - notifications?: { + notifications: { /** * If true, receive notifications/tools/list_changed. */ @@ -464,9 +445,10 @@ export interface NotificationsListenRequest extends Request { } ``` -If `notifications` is omitted entirely, the server **SHOULD** send all -notifications the client's declared capabilities support. If provided, only -the specified notification types are delivered. +The `notifications` field is **required** and the client **MUST** explicitly +opt in to each notification type it wants to receive. If a field within +`notifications` is omitted (or set to `false`), the server **MUST NOT** send +notifications of that type. **Acknowledgment Notification:** @@ -481,33 +463,19 @@ export interface NotificationsListenNotification extends Notification { } ``` -#### STDIO Transport Behavior +#### Transport Behavior -For STDIO, a client **MAY** send a `NotificationsListenRequest` at any time to -declare its capabilities and the messages it is interested in receiving. The -server **MUST** acknowledge it by sending a `NotificationsListenNotification`. +**HTTP.** The client sends `NotificationsListenRequest` via `POST`. The server's +response is an open SSE stream (`Content-Type: text/event-stream`), and the +first JSON-RPC message on this stream **MUST** be a +`NotificationsListenNotification`. -The server **MAY** then send server-to-client messages and notifications for -the duration of the connection. If the connection is terminated (e.g., the -server crashes and restarts), the client **MUST** re-send `NotificationsListenRequest` -to re-establish its declared capabilities. - -#### Streamable HTTP Transport Behavior - -For HTTP, there are two distinct models for handling streaming: - -**1. Server-Initiated Streaming** - -To receive a streaming response for a single RPC call, the client **augments the -standard request** by including the `clientCapabilities` object in the `_meta` -field. The server **MAY** then respond with an SSE stream for that transaction. - -**2. Client-Initiated Streaming** - -To proactively open a persistent SSE stream, the client sends the dedicated -`NotificationsListenRequest` via `POST`. The server's response **is an open SSE -stream** (`Content-Type: text/event-stream`), and the first JSON-RPC message on -this stream **MUST** be a `NotificationsListenNotification`. +**STDIO.** The client sends `NotificationsListenRequest` at any time. The server +**MUST** acknowledge it by sending a `NotificationsListenNotification`. +Subsequent notifications flow on the bidirectional STDIO channel. If the +connection is terminated (e.g., the server crashes and restarts), the client +**MUST** re-send `NotificationsListenRequest` to re-establish its declared +capabilities and interests. ### Deprecated and Removed RPCs @@ -515,35 +483,34 @@ To simplify the protocol and align with the move to per-request capabilities, the following RPC methods and notifications are removed: - `initialize` / `notifications/initialized`: The initialization handshake is - removed. Version negotiation is handled per-request via - `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is - handled by `server/discover`. Servers compliant with this SEP **SHOULD** - accept and ignore `notifications/initialized` without error to maintain - backward compatibility with clients that may send it. -- `logging/setLevel`: Removed. The log level is now specified per-request - via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no + removed. Version negotiation is handled per-request via `MCP-Protocol-Version` + headers and `_meta` fields. Capability discovery is handled by + `server/discover`. Servers compliant with this SEP **SHOULD** accept and + ignore `notifications/initialized` without error to maintain backward + compatibility with clients that may send it. +- `logging/setLevel`: Removed. The log level is now specified per-request via + the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no replacement RPC. -- `roots/list`: Removed as a top-level server-to-client RPC. Servers that - need the client's roots **MUST** request them via the MRTR - `ListRootsRequest` mechanism (see SEP-2322). -- `notifications/roots/list_changed`: Removed. Roots are fetched on demand - via MRTR, so there is no need for a change notification. +- `roots/list`: Removed as a top-level server-to-client RPC. Servers that need + the client's roots **MUST** request them via the MRTR `ListRootsRequest` + mechanism (see SEP-2322). +- `notifications/roots/list_changed`: Removed. Roots are fetched on demand via + MRTR, so there is no need for a change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember - which resources each client has subscribed to. Instead, clients declare - the resources they want updates for in the `notifications` param of the + which resources each client has subscribed to. Instead, clients declare the + resources they want updates for in the `notifications` param of the `notifications/listen` request. The server sends - `notifications/resources/updated` on the listen stream for matching - resources. + `notifications/resources/updated` on the listen stream for matching resources. ## Rationale ### Stateless-First by Default -The primary design decision of this SEP is to remove the mandatory initialization -handshake, making stateless interaction the default model for the protocol. This -choice is rooted in the "pay as you go" principle and the desire to align MCP -with modern, cloud-native architecture. By making the simplest +The primary design decision of this SEP is to remove the mandatory +initialization handshake, making stateless interaction the default model for the +protocol. This choice is rooted in the "pay as you go" principle and the desire +to align MCP with modern, cloud-native architecture. By making the simplest interaction model the default, we lower the barrier to entry and reduce implementation complexity for the most common use cases. This immediately enables straightforward horizontal scaling and improves resilience, as any @@ -571,12 +538,10 @@ scalable, and more robust foundation. This proposal originally included dedicated `sessions/create` and `sessions/delete` RPCs to manage the lifecycle of a logical session. -Session management is now addressed separately by -[SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567), -which proposes removing sessions entirely and replacing them with explicit -state handles. This aligns with the -[sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) -made by the Core Maintainers. +Session management is now addressed separately by [SEP-2567][SEP-2567], which +proposes removing sessions entirely and replacing them with explicit state +handles. This aligns with the [sessions-vs-sessionless +decision][sessions-decision] made by the Core Maintainers. ### Separation of Concerns @@ -587,8 +552,8 @@ discovery into a single, complex interaction. The new design explicitly separates these: - **Discovery**: Handled exclusively by `server/discover`. -- **Capabilities**: Handled on a per-request basis via the `_meta` field or - the `notifications/listen` RPC. +- **Capabilities**: Handled on a per-request basis via the `_meta` field or the + `notifications/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -693,8 +658,7 @@ different implementations, leading to both confusion and incompatibility. ### How does `server/discover` relate to the MCP Server Card? -The `server/discover` RPC overlaps with the -[MCP Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) +The `server/discover` RPC overlaps with the [MCP Server Card][SEP-2127] proposal, which defines a `.well-known/mcp.json` document for HTTP-based discovery. Both mechanisms are intentionally retained: the Server Card is well-suited to HTTP (no auth required, cacheable, indexable) while @@ -713,17 +677,24 @@ This follows the spec's allowance for "purpose-specific metadata" reserved by definitions in the schema. However, this risks overloading `_meta` over time — at what point do we add -top-level fields again? One possible distinction: required protocol-level -fields (e.g., `protocolVersion`) might better live as top-level fields, while -optional or extension-provided values stay in `_meta`. This question deserves -broader discussion before this SEP is finalized. +top-level fields again? One possible distinction: required protocol-level fields +(e.g., `protocolVersion`) might better live as top-level fields, while optional +or extension-provided values stay in `_meta`. This question deserves broader +discussion before this SEP is finalized. ### Should `clientInfo` be part of `ClientCapabilities`? Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` (`ClientCapabilities` type) are separate fields. In a per-request model, having a single field for all client metadata would reduce overhead. However, -`clientInfo` serves a different purpose (identity/UI) than capabilities -(feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, -remain a separate per-request `_meta` field, or be handled through a different -mechanism entirely (e.g., only sent via `notifications/listen`)? +`clientInfo` serves a different purpose (identity/UI) than capabilities (feature +negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a +separate per-request `_meta` field, or be handled through a different mechanism +entirely (e.g., only sent via `notifications/listen`)? + +[SEP-2127]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 +[SEP-2243]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243 +[SEP-2260]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260 +[SEP-2322]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322 +[SEP-2567]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567 +[sessions-decision]: https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index c7d8522cd..a8d62819d 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -21,7 +21,7 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi | SEP | Title | Status | Type | Created | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------- | ---------------- | ---------- | -| [SEP-2575](/seps/2575-stateless-mcp) | Stateless-by-Default MCP | Draft | Standards Track | 2025-06-18 | +| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Draft | Standards Track | 2025-06-18 | | [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | | [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Approved | Standards Track | 2026-02-03 | | [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Accepted | Standards Track | 2026-02-16 | diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index d0f3a283b..b74b2022c 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -3,7 +3,8 @@ - **Status**: Draft - **Type**: Standards Track - **Created**: 2025-06-18 -- **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) +- **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), + Shaun Smith (@evalstate), Harvey Tuch (@htuch), Kurtis Van Gent (@kurtisvg) - **Sponsor**: Kurtis Van Gent (@kurtisvg) - **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575 @@ -64,10 +65,9 @@ and scalability. - **Server-side:** Developers must implement logic to create, manage, and eventually garbage-collect per-client session state. This is a common source of bugs and memory leaks. - - **Client-side:** Developers must write complex code to manage a - persistent connection and handle the inevitable network failures and - reconnections, including the logic to resynchronize state after a - disconnect. + - **Client-side:** Developers must write complex code to manage a persistent + connection and handle the inevitable network failures and reconnections, + including the logic to resynchronize state after a disconnect. ## Design Principles @@ -113,19 +113,15 @@ initialization phase, the specification creates an implied link between them, particularly between the exchange of capabilities and a mandatory connection lifecycle. -This proposal is to **remove the initialization handshake** and "unbundle" -its functions into discrete, stateless components. We will provide new, more -clearly defined mechanisms for clients and servers to exchange this information -without a mandatory state-creating cycle. +This proposal is to **remove the initialization handshake** and "unbundle" its +functions into discrete, stateless components. We will provide new, more clearly +defined mechanisms for clients and servers to exchange this information without +a mandatory state-creating cycle. > **Note:** Session management (both transport-level and application-level) is -> addressed separately by -> [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) -> and -> [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567). -> This SEP focuses exclusively on removing the initialization handshake and -> providing stateless alternatives for version negotiation, discovery, and -> capabilities. +> addressed separately by [SEP-2322][SEP-2322] and [SEP-2567][SEP-2567]. This +> SEP focuses exclusively on removing the initialization handshake and providing +> stateless alternatives for version negotiation, discovery, and capabilities. ### Protocol Version @@ -137,15 +133,14 @@ handshake must now be included with **every request**. For the HTTP transport, protocol version MUST be passed as an **HTTP header**. The header value MUST match the value provided in the request payload's `_meta` field; otherwise the server MUST return a `400 Bad Request` (see -[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). +[SEP-2243][SEP-2243]). - `MCP-Protocol-Version: 2025-06-18` - - **Purpose**: To inform the server which version of the MCP specification - the client is using for this specific request. + - **Purpose**: To inform the server which version of the MCP specification the + client is using for this specific request. - **Requirement**: This header is **MANDATORY**. Servers should reject requests with a missing or unsupported version. - - This header MUST match the value provided in the Request as specified - below. + - This header MUST match the value provided in the Request as specified below. #### Per-request Version @@ -162,14 +157,8 @@ export interface RequestMetaObject extends MetaObject { + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; -+ /** -+ * Identifies the client software. -+ */ -+ "io.modelcontextprotocol/clientInfo": Implementation; -+ /** -+ * The desired log level for this request. -+ */ -+ "io.modelcontextprotocol/logLevel"?: LoggingLevel; + // Additional per-request fields (clientInfo, clientCapabilities, logLevel) + // are introduced in the Per-Request Client Capabilities section below. } ``` @@ -207,11 +196,12 @@ export interface UnsupportedProtocolVersionError extends Omit< Without an initialization handshake, version negotiation happens inline: 1. The client sends a request with its preferred protocol version in the - `MCP-Protocol-Version` header and - `io.modelcontextprotocol/protocolVersion` `_meta` field. + `MCP-Protocol-Version` header and `io.modelcontextprotocol/protocolVersion` + `_meta` field. 2. If the server supports that version, it processes the request normally. 3. If the server does not support the requested version, it returns an - `UnsupportedProtocolVersionError` containing its list of `supported` versions. + `UnsupportedProtocolVersionError` containing its list of `supported` + versions. 4. The client selects a mutually supported version from the list and retries. Alternatively, a client **MAY** call `server/discover` first to learn the @@ -231,8 +221,8 @@ status code MUST be `404 Not Found`. #### `server/discover` RPC -- **Purpose**: To allow a client to query the server for its supported - protocol versions, capabilities, and other metadata. +- **Purpose**: To allow a client to query the server for its supported protocol + versions, capabilities, and other metadata. **Request Schema:** @@ -279,38 +269,85 @@ To complete the decoupling from the initial handshake, client capabilities are no longer negotiated once at initialization. Instead, a client **MUST** specify its capabilities on every request. This ensures the server is always fully informed about what optional features the client can handle for that specific -transaction, such as streaming responses. An absent or empty capabilities -object means the client supports no optional capabilities — servers **MUST -NOT** infer capabilities from prior requests. - -Two related SEPs together eliminate independent server-to-client requests -entirely: - -- [SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260) - restricts the `notifications/listen` SSE stream to notifications only — no - requests flow on this channel. -- [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) - (Multi Round-Trip Requests, MRTR) introduces `IncompleteResult`, which lets - a server embed input requests (elicitation, sampling, roots) within the - response to a specific client request (e.g., `CallTool`, `GetPrompt`, - `ListResources`). The client satisfies these and retries the original - request. - -With both in place, the server never independently calls the client. Instead, -when processing a request, the server inspects the declared -`clientCapabilities` and decides between two paths: - -- **Required capability missing** — the server returns - `MissingRequiredClientCapabilityError` indicating which capability it needs. -- **Required capability present** — the server returns an `IncompleteResult` - containing the relevant input requests, or completes the request directly - if no client interaction is needed. - -A server **MUST NOT** rely on capabilities the client has not declared. - -If a server requires client capabilities the client has not provided, the -server MUST return a JSON-RPC error, which specifies the missing capabilities. -For HTTP, the response status code MUST be `400 Bad Request`. +transaction. An absent or empty capabilities object means the client supports no +optional capabilities — servers **MUST NOT** infer capabilities from prior +requests. + +#### Per-Request Metadata Schema + +Every request's `_meta` carries a small set of fields that previously lived in +the initialization handshake. The full `RequestMetaObject` shape: + +```ts +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; + /** + * The MCP Protocol Version being used for this request. + */ + "io.modelcontextprotocol/protocolVersion": string; + /** + * Identifies the client software. + */ + "io.modelcontextprotocol/clientInfo": Implementation; + /** + * Capabilities of the client for this specific request. + */ + "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; + /** + * The desired log level for this request. + */ + "io.modelcontextprotocol/logLevel"?: LoggingLevel; +} +``` + +Field semantics: + +- `"io.modelcontextprotocol/protocolVersion"`: `string` — the MCP Protocol + Version. **Required.** See the Protocol Version section above for negotiation + details. +- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the + client software. **Required.** The `Implementation` schema requires `name` and + `version`; other fields are optional. +- `"io.modelcontextprotocol/clientCapabilities"`: `ClientCapabilities` — the + client's capabilities for this request. **Required.** +- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level + for this request. **Optional.** If absent, the server **MUST NOT** send any + log notifications for this request. The client opts in to log messages by + explicitly setting a level. Replaces the `logging/setLevel` RPC. + +Roots are intentionally not included as a per-request `_meta` field. Servers +that need the client's roots **MUST** request them via the MRTR +`ListRootsRequest` mechanism (see [SEP-2322][SEP-2322]), which avoids putting +potentially large root lists on every request and follows the "pay as you go" +principle. + +A request missing any required field is malformed; the server **MUST** reject it +with `INVALID_PARAMS` (and `400 Bad Request` for HTTP). + +#### Response Streaming + +These declared capabilities govern what the server may include in the response +stream. [SEP-2322][SEP-2322] (MRTR) defines how server-to-client interactions +are embedded inline within responses via `IncompleteResult`; this SEP specifies +that those interactions are governed by the per-request `clientCapabilities` +declared in `RequestMetaObject`. + +For HTTP, any request's response **MAY** be delivered as an SSE stream +(`Content-Type: text/event-stream`) instead of a single JSON object. Only +notifications (e.g., `notifications/progress`, `notifications/message`) flow as +independent messages on this stream, followed by the final result. +Server-to-client interactions (sampling, elicitation, listRoots) are **not** +sent as independent requests — they are embedded as input requests inside an +`IncompleteResult` returned from specific request paths (e.g., `CallTool`, +`GetPrompt`, `ListResources`). The client satisfies the input requests and +retries the original request. + +#### Missing Required Capabilities + +A server **MUST NOT** rely on capabilities the client has not declared. If a +server requires client capabilities the client has not provided, the server +**MUST** return a JSON-RPC error, which specifies the missing capabilities. For +HTTP, the response status code MUST be `400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; @@ -332,74 +369,19 @@ export interface MissingRequiredClientCapabilityError extends Omit< } ``` -In addition to `clientCapabilities`, the following fields previously exchanged -during initialization **MAY** be included in per-request `_meta` fields: +### `notifications/listen` RPC -- `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the - client software. **Required.** The `Implementation` schema requires `name` - and `version`; other fields are optional. -- `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log - level for this request. **Optional.** If absent, the server **MUST NOT** - send any log notifications for this request. The client opts in to log - messages by explicitly setting a level. Replaces the `logging/setLevel` - RPC. +This SEP introduces a new `notifications/listen` RPC that replaces the previous +HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. A +client uses it to open a long-lived channel for receiving notifications outside +the context of a specific request. -Roots are intentionally not included as a per-request `_meta` field. Servers -that need the client's roots **MUST** request them via the MRTR -`ListRootsRequest` mechanism (see -[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)), -which avoids putting potentially large root lists on every request and -follows the "pay as you go" principle. - -The required fields (`protocolVersion`, `clientCapabilities`, `clientInfo`) -MUST be present on every request. A request missing any of these is -malformed; the server **MUST** reject it with `INVALID_PARAMS` (and `400 Bad -Request` for HTTP). - -The primary capability defined in this proposal is the ability to handle -streaming responses, which is supported through two distinct models: -server-initiated and client-initiated. - -#### Streaming Models - -##### Server-Initiated Streaming (Response Stream) - -This model applies when a client makes a standard RPC call and the server -responds back with an SSE stream. The client specifies supported capabilities -directly in the request. - -The client adds a `clientCapabilities` field to `RequestMetaObject`. For the -HTTP transport, a server that supports this **MAY** then respond with an SSE -stream for that transaction. - -```ts -export interface RequestMetaObject extends MetaObject { - progressToken?: ProgressToken; - "io.modelcontextprotocol/protocolVersion": string; - "io.modelcontextprotocol/clientInfo": Implementation; - "io.modelcontextprotocol/logLevel"?: LoggingLevel; -+ /** -+ * Capabilities of the client for this specific request. -+ */ -+ "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; -} -``` - -##### Client-Initiated Streaming (Background Streaming) - -This model applies when a client wants to proactively open a persistent SSE -stream to receive multiple or unsolicited events. - -This is achieved using a dedicated `notifications/listen` RPC. For the HTTP -transport, the client sends this request via `POST`, and the server's response -is an open SSE stream, with a `NotificationsListenNotification` sent as the -first event. For the STDIO transport, this RPC is used to declare the client's -capabilities and interests. +The HTTP GET endpoint used by Streamable HTTP for server-to-client messages is +**removed** in this version of the protocol. All communication uses POST. -This RPC replaces the existing HTTP GET endpoint for Streamable HTTP. The GET -endpoint is removed; all communication uses POST. Only notifications (not -requests) may be sent on the listen stream, per -[SEP-2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260). +Per [SEP-2260][SEP-2260], only notifications (not requests) flow on this +channel; server-initiated requests use MRTR (see Response Streaming above) and +are scoped to a specific client request. **Request Schema:** @@ -415,11 +397,11 @@ export interface NotificationsListenRequest extends Request { }; /** - * Optional filter for which notifications the client wants to receive. - * If omitted, the server SHOULD send all notifications the client's - * capabilities support. + * The notifications the client wants to receive on this stream. + * Each notification type is opt-in; the server **MUST NOT** send + * notification types the client has not explicitly requested here. */ - notifications?: { + notifications: { /** * If true, receive notifications/tools/list_changed. */ @@ -445,9 +427,10 @@ export interface NotificationsListenRequest extends Request { } ``` -If `notifications` is omitted entirely, the server **SHOULD** send all -notifications the client's declared capabilities support. If provided, only -the specified notification types are delivered. +The `notifications` field is **required** and the client **MUST** explicitly +opt in to each notification type it wants to receive. If a field within +`notifications` is omitted (or set to `false`), the server **MUST NOT** send +notifications of that type. **Acknowledgment Notification:** @@ -462,33 +445,19 @@ export interface NotificationsListenNotification extends Notification { } ``` -#### STDIO Transport Behavior +#### Transport Behavior -For STDIO, a client **MAY** send a `NotificationsListenRequest` at any time to -declare its capabilities and the messages it is interested in receiving. The -server **MUST** acknowledge it by sending a `NotificationsListenNotification`. +**HTTP.** The client sends `NotificationsListenRequest` via `POST`. The server's +response is an open SSE stream (`Content-Type: text/event-stream`), and the +first JSON-RPC message on this stream **MUST** be a +`NotificationsListenNotification`. -The server **MAY** then send server-to-client messages and notifications for -the duration of the connection. If the connection is terminated (e.g., the -server crashes and restarts), the client **MUST** re-send `NotificationsListenRequest` -to re-establish its declared capabilities. - -#### Streamable HTTP Transport Behavior - -For HTTP, there are two distinct models for handling streaming: - -**1. Server-Initiated Streaming** - -To receive a streaming response for a single RPC call, the client **augments the -standard request** by including the `clientCapabilities` object in the `_meta` -field. The server **MAY** then respond with an SSE stream for that transaction. - -**2. Client-Initiated Streaming** - -To proactively open a persistent SSE stream, the client sends the dedicated -`NotificationsListenRequest` via `POST`. The server's response **is an open SSE -stream** (`Content-Type: text/event-stream`), and the first JSON-RPC message on -this stream **MUST** be a `NotificationsListenNotification`. +**STDIO.** The client sends `NotificationsListenRequest` at any time. The server +**MUST** acknowledge it by sending a `NotificationsListenNotification`. +Subsequent notifications flow on the bidirectional STDIO channel. If the +connection is terminated (e.g., the server crashes and restarts), the client +**MUST** re-send `NotificationsListenRequest` to re-establish its declared +capabilities and interests. ### Deprecated and Removed RPCs @@ -496,35 +465,34 @@ To simplify the protocol and align with the move to per-request capabilities, the following RPC methods and notifications are removed: - `initialize` / `notifications/initialized`: The initialization handshake is - removed. Version negotiation is handled per-request via - `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is - handled by `server/discover`. Servers compliant with this SEP **SHOULD** - accept and ignore `notifications/initialized` without error to maintain - backward compatibility with clients that may send it. -- `logging/setLevel`: Removed. The log level is now specified per-request - via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no + removed. Version negotiation is handled per-request via `MCP-Protocol-Version` + headers and `_meta` fields. Capability discovery is handled by + `server/discover`. Servers compliant with this SEP **SHOULD** accept and + ignore `notifications/initialized` without error to maintain backward + compatibility with clients that may send it. +- `logging/setLevel`: Removed. The log level is now specified per-request via + the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no replacement RPC. -- `roots/list`: Removed as a top-level server-to-client RPC. Servers that - need the client's roots **MUST** request them via the MRTR - `ListRootsRequest` mechanism (see SEP-2322). -- `notifications/roots/list_changed`: Removed. Roots are fetched on demand - via MRTR, so there is no need for a change notification. +- `roots/list`: Removed as a top-level server-to-client RPC. Servers that need + the client's roots **MUST** request them via the MRTR `ListRootsRequest` + mechanism (see SEP-2322). +- `notifications/roots/list_changed`: Removed. Roots are fetched on demand via + MRTR, so there is no need for a change notification. - `resources/subscribe` / `resources/unsubscribe`: These methods are removed. Resource subscriptions are inherently stateful — the server must remember - which resources each client has subscribed to. Instead, clients declare - the resources they want updates for in the `notifications` param of the + which resources each client has subscribed to. Instead, clients declare the + resources they want updates for in the `notifications` param of the `notifications/listen` request. The server sends - `notifications/resources/updated` on the listen stream for matching - resources. + `notifications/resources/updated` on the listen stream for matching resources. ## Rationale ### Stateless-First by Default -The primary design decision of this SEP is to remove the mandatory initialization -handshake, making stateless interaction the default model for the protocol. This -choice is rooted in the "pay as you go" principle and the desire to align MCP -with modern, cloud-native architecture. By making the simplest +The primary design decision of this SEP is to remove the mandatory +initialization handshake, making stateless interaction the default model for the +protocol. This choice is rooted in the "pay as you go" principle and the desire +to align MCP with modern, cloud-native architecture. By making the simplest interaction model the default, we lower the barrier to entry and reduce implementation complexity for the most common use cases. This immediately enables straightforward horizontal scaling and improves resilience, as any @@ -552,12 +520,10 @@ scalable, and more robust foundation. This proposal originally included dedicated `sessions/create` and `sessions/delete` RPCs to manage the lifecycle of a logical session. -Session management is now addressed separately by -[SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567), -which proposes removing sessions entirely and replacing them with explicit -state handles. This aligns with the -[sessions-vs-sessionless decision](https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md) -made by the Core Maintainers. +Session management is now addressed separately by [SEP-2567][SEP-2567], which +proposes removing sessions entirely and replacing them with explicit state +handles. This aligns with the [sessions-vs-sessionless +decision][sessions-decision] made by the Core Maintainers. ### Separation of Concerns @@ -568,8 +534,8 @@ discovery into a single, complex interaction. The new design explicitly separates these: - **Discovery**: Handled exclusively by `server/discover`. -- **Capabilities**: Handled on a per-request basis via the `_meta` field or - the `notifications/listen` RPC. +- **Capabilities**: Handled on a per-request basis via the `_meta` field or the + `notifications/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -674,8 +640,7 @@ different implementations, leading to both confusion and incompatibility. ### How does `server/discover` relate to the MCP Server Card? -The `server/discover` RPC overlaps with the -[MCP Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) +The `server/discover` RPC overlaps with the [MCP Server Card][SEP-2127] proposal, which defines a `.well-known/mcp.json` document for HTTP-based discovery. Both mechanisms are intentionally retained: the Server Card is well-suited to HTTP (no auth required, cacheable, indexable) while @@ -694,17 +659,24 @@ This follows the spec's allowance for "purpose-specific metadata" reserved by definitions in the schema. However, this risks overloading `_meta` over time — at what point do we add -top-level fields again? One possible distinction: required protocol-level -fields (e.g., `protocolVersion`) might better live as top-level fields, while -optional or extension-provided values stay in `_meta`. This question deserves -broader discussion before this SEP is finalized. +top-level fields again? One possible distinction: required protocol-level fields +(e.g., `protocolVersion`) might better live as top-level fields, while optional +or extension-provided values stay in `_meta`. This question deserves broader +discussion before this SEP is finalized. ### Should `clientInfo` be part of `ClientCapabilities`? Currently, `clientInfo` (`Implementation` type) and `clientCapabilities` (`ClientCapabilities` type) are separate fields. In a per-request model, having a single field for all client metadata would reduce overhead. However, -`clientInfo` serves a different purpose (identity/UI) than capabilities -(feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, -remain a separate per-request `_meta` field, or be handled through a different -mechanism entirely (e.g., only sent via `notifications/listen`)? +`clientInfo` serves a different purpose (identity/UI) than capabilities (feature +negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a +separate per-request `_meta` field, or be handled through a different mechanism +entirely (e.g., only sent via `notifications/listen`)? + +[SEP-2127]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 +[SEP-2243]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243 +[SEP-2260]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260 +[SEP-2322]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322 +[SEP-2567]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567 +[sessions-decision]: https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md From 09cbb49e75112923df935a8873327be3b698413f Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:26:15 -0600 Subject: [PATCH 30/69] fix: remove ping RPC --- docs/seps/2575-stateless-mcp.mdx | 4 ++++ seps/2575-stateless-mcp.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 782cf01c2..e036e5478 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -502,6 +502,10 @@ the following RPC methods and notifications are removed: resources they want updates for in the `notifications` param of the `notifications/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. +- `ping`: Removed. With independent server-to-client requests eliminated, + ping no longer serves a useful purpose — any normal RPC call (or the + `notifications/listen` SSE stream's transport-layer keep-alive) demonstrates + liveness equivalently. ## Rationale diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index b74b2022c..67440a9af 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -484,6 +484,10 @@ the following RPC methods and notifications are removed: resources they want updates for in the `notifications` param of the `notifications/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. +- `ping`: Removed. With independent server-to-client requests eliminated, + ping no longer serves a useful purpose — any normal RPC call (or the + `notifications/listen` SSE stream's transport-layer keep-alive) demonstrates + liveness equivalently. ## Rationale From b9f1753c7394c45cbea3d66a1dd6839fff25f414 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:33:12 -0600 Subject: [PATCH 31/69] fix: add logLevel to notifications/listen subscription filter --- docs/seps/2575-stateless-mcp.mdx | 6 ++++++ seps/2575-stateless-mcp.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index e036e5478..43df333c9 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -440,6 +440,12 @@ export interface NotificationsListenRequest extends Request { * resource URIs. Replaces the resources/subscribe RPC. */ resourceSubscriptions?: string[]; + + /** + * If set, receive notifications/message at or above this level. + * If absent, no log messages are sent on this stream. + */ + logLevel?: LoggingLevel; }; }; } diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 67440a9af..f0b01e9ea 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -422,6 +422,12 @@ export interface NotificationsListenRequest extends Request { * resource URIs. Replaces the resources/subscribe RPC. */ resourceSubscriptions?: string[]; + + /** + * If set, receive notifications/message at or above this level. + * If absent, no log messages are sent on this stream. + */ + logLevel?: LoggingLevel; }; }; } From 7a541b8c45f19f45a5e30a093599413eb5ae313b Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:41:04 -0600 Subject: [PATCH 32/69] fix: rename notifications/listen to subscriptions/listen --- docs/seps/2575-stateless-mcp.mdx | 34 ++++++++++++++++---------------- seps/2575-stateless-mcp.md | 34 ++++++++++++++++---------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 43df333c9..075c59ae8 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -387,9 +387,9 @@ export interface MissingRequiredClientCapabilityError extends Omit< } ``` -### `notifications/listen` RPC +### `subscriptions/listen` RPC -This SEP introduces a new `notifications/listen` RPC that replaces the previous +This SEP introduces a new `subscriptions/listen` RPC that replaces the previous HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. A client uses it to open a long-lived channel for receiving notifications outside the context of a specific request. @@ -401,11 +401,11 @@ Per [SEP-2260][SEP-2260], only notifications (not requests) flow on this channel; server-initiated requests use MRTR (see Response Streaming above) and are scoped to a specific client request. -**Request Schema:** +#### Request Schema ```ts -export interface NotificationsListenRequest extends Request { - method: "notifications/listen"; +export interface SubscriptionsListenRequest extends Request { + method: "subscriptions/listen"; params: { _meta?: { "io.modelcontextprotocol/protocolVersion": string; @@ -456,7 +456,7 @@ opt in to each notification type it wants to receive. If a field within `notifications` is omitted (or set to `false`), the server **MUST NOT** send notifications of that type. -**Acknowledgment Notification:** +#### Acknowledgment Notification The server sends this notification as the first event on the stream to acknowledge that the listen stream has been established. For HTTP, this is the @@ -464,23 +464,23 @@ first SSE event. The stream remains open for subsequent server-to-client messages until the server sends a final `Result` to close it. ```ts -export interface NotificationsListenNotification extends Notification { - method: "notifications/listen/acknowledged"; +export interface SubscriptionsAcknowledgedNotification extends Notification { + method: "notifications/subscriptions/acknowledged"; } ``` #### Transport Behavior -**HTTP.** The client sends `NotificationsListenRequest` via `POST`. The server's +**HTTP.** The client sends `SubscriptionsListenRequest` via `POST`. The server's response is an open SSE stream (`Content-Type: text/event-stream`), and the first JSON-RPC message on this stream **MUST** be a -`NotificationsListenNotification`. +`SubscriptionsAcknowledgedNotification`. -**STDIO.** The client sends `NotificationsListenRequest` at any time. The server -**MUST** acknowledge it by sending a `NotificationsListenNotification`. +**STDIO.** The client sends `SubscriptionsListenRequest` at any time. The server +**MUST** acknowledge it by sending a `SubscriptionsAcknowledgedNotification`. Subsequent notifications flow on the bidirectional STDIO channel. If the connection is terminated (e.g., the server crashes and restarts), the client -**MUST** re-send `NotificationsListenRequest` to re-establish its declared +**MUST** re-send `SubscriptionsListenRequest` to re-establish its declared capabilities and interests. ### Deprecated and Removed RPCs @@ -506,11 +506,11 @@ the following RPC methods and notifications are removed: Resource subscriptions are inherently stateful — the server must remember which resources each client has subscribed to. Instead, clients declare the resources they want updates for in the `notifications` param of the - `notifications/listen` request. The server sends + `subscriptions/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. - `ping`: Removed. With independent server-to-client requests eliminated, ping no longer serves a useful purpose — any normal RPC call (or the - `notifications/listen` SSE stream's transport-layer keep-alive) demonstrates + `subscriptions/listen` SSE stream's transport-layer keep-alive) demonstrates liveness equivalently. ## Rationale @@ -563,7 +563,7 @@ separates these: - **Discovery**: Handled exclusively by `server/discover`. - **Capabilities**: Handled on a per-request basis via the `_meta` field or the - `notifications/listen` RPC. + `subscriptions/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -700,7 +700,7 @@ a single field for all client metadata would reduce overhead. However, `clientInfo` serves a different purpose (identity/UI) than capabilities (feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a separate per-request `_meta` field, or be handled through a different mechanism -entirely (e.g., only sent via `notifications/listen`)? +entirely (e.g., only sent via `subscriptions/listen`)? [SEP-2127]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 [SEP-2243]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243 diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index f0b01e9ea..71cbdefac 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -369,9 +369,9 @@ export interface MissingRequiredClientCapabilityError extends Omit< } ``` -### `notifications/listen` RPC +### `subscriptions/listen` RPC -This SEP introduces a new `notifications/listen` RPC that replaces the previous +This SEP introduces a new `subscriptions/listen` RPC that replaces the previous HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. A client uses it to open a long-lived channel for receiving notifications outside the context of a specific request. @@ -383,11 +383,11 @@ Per [SEP-2260][SEP-2260], only notifications (not requests) flow on this channel; server-initiated requests use MRTR (see Response Streaming above) and are scoped to a specific client request. -**Request Schema:** +#### Request Schema ```ts -export interface NotificationsListenRequest extends Request { - method: "notifications/listen"; +export interface SubscriptionsListenRequest extends Request { + method: "subscriptions/listen"; params: { _meta?: { "io.modelcontextprotocol/protocolVersion": string; @@ -438,7 +438,7 @@ opt in to each notification type it wants to receive. If a field within `notifications` is omitted (or set to `false`), the server **MUST NOT** send notifications of that type. -**Acknowledgment Notification:** +#### Acknowledgment Notification The server sends this notification as the first event on the stream to acknowledge that the listen stream has been established. For HTTP, this is the @@ -446,23 +446,23 @@ first SSE event. The stream remains open for subsequent server-to-client messages until the server sends a final `Result` to close it. ```ts -export interface NotificationsListenNotification extends Notification { - method: "notifications/listen/acknowledged"; +export interface SubscriptionsAcknowledgedNotification extends Notification { + method: "notifications/subscriptions/acknowledged"; } ``` #### Transport Behavior -**HTTP.** The client sends `NotificationsListenRequest` via `POST`. The server's +**HTTP.** The client sends `SubscriptionsListenRequest` via `POST`. The server's response is an open SSE stream (`Content-Type: text/event-stream`), and the first JSON-RPC message on this stream **MUST** be a -`NotificationsListenNotification`. +`SubscriptionsAcknowledgedNotification`. -**STDIO.** The client sends `NotificationsListenRequest` at any time. The server -**MUST** acknowledge it by sending a `NotificationsListenNotification`. +**STDIO.** The client sends `SubscriptionsListenRequest` at any time. The server +**MUST** acknowledge it by sending a `SubscriptionsAcknowledgedNotification`. Subsequent notifications flow on the bidirectional STDIO channel. If the connection is terminated (e.g., the server crashes and restarts), the client -**MUST** re-send `NotificationsListenRequest` to re-establish its declared +**MUST** re-send `SubscriptionsListenRequest` to re-establish its declared capabilities and interests. ### Deprecated and Removed RPCs @@ -488,11 +488,11 @@ the following RPC methods and notifications are removed: Resource subscriptions are inherently stateful — the server must remember which resources each client has subscribed to. Instead, clients declare the resources they want updates for in the `notifications` param of the - `notifications/listen` request. The server sends + `subscriptions/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. - `ping`: Removed. With independent server-to-client requests eliminated, ping no longer serves a useful purpose — any normal RPC call (or the - `notifications/listen` SSE stream's transport-layer keep-alive) demonstrates + `subscriptions/listen` SSE stream's transport-layer keep-alive) demonstrates liveness equivalently. ## Rationale @@ -545,7 +545,7 @@ separates these: - **Discovery**: Handled exclusively by `server/discover`. - **Capabilities**: Handled on a per-request basis via the `_meta` field or the - `notifications/listen` RPC. + `subscriptions/listen` RPC. The rationale for this is to create a more modular, flexible, and understandable protocol. Each component now has a single, well-defined responsibility. This @@ -682,7 +682,7 @@ a single field for all client metadata would reduce overhead. However, `clientInfo` serves a different purpose (identity/UI) than capabilities (feature negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a separate per-request `_meta` field, or be handled through a different mechanism -entirely (e.g., only sent via `notifications/listen`)? +entirely (e.g., only sent via `subscriptions/listen`)? [SEP-2127]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 [SEP-2243]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243 From dc4c2aabe6a0136b0462f7271fc1156e97b327bc Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:48:17 -0600 Subject: [PATCH 33/69] fix: have ack notification confirm subscriptions server agreed to --- docs/seps/2575-stateless-mcp.mdx | 20 +++++++++++++++++--- seps/2575-stateless-mcp.md | 20 +++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 075c59ae8..f35770d66 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -466,6 +466,22 @@ messages until the server sends a final `Result` to close it. ```ts export interface SubscriptionsAcknowledgedNotification extends Notification { method: "notifications/subscriptions/acknowledged"; + params: { + /** + * The notification subscriptions the server has agreed to honor. + * Only includes notification types the server actually supports. + * If the client requested an unsupported notification type + * (e.g., promptsListChanged when the server has no prompts), + * it is omitted from this set. + */ + notifications: { + toolsListChanged?: boolean; + promptsListChanged?: boolean; + resourcesListChanged?: boolean; + resourceSubscriptions?: string[]; + logLevel?: LoggingLevel; + }; + }; } ``` @@ -491,9 +507,7 @@ the following RPC methods and notifications are removed: - `initialize` / `notifications/initialized`: The initialization handshake is removed. Version negotiation is handled per-request via `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is handled by - `server/discover`. Servers compliant with this SEP **SHOULD** accept and - ignore `notifications/initialized` without error to maintain backward - compatibility with clients that may send it. + `server/discover`. - `logging/setLevel`: Removed. The log level is now specified per-request via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no replacement RPC. diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 71cbdefac..6a1f2cb6b 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -448,6 +448,22 @@ messages until the server sends a final `Result` to close it. ```ts export interface SubscriptionsAcknowledgedNotification extends Notification { method: "notifications/subscriptions/acknowledged"; + params: { + /** + * The notification subscriptions the server has agreed to honor. + * Only includes notification types the server actually supports. + * If the client requested an unsupported notification type + * (e.g., promptsListChanged when the server has no prompts), + * it is omitted from this set. + */ + notifications: { + toolsListChanged?: boolean; + promptsListChanged?: boolean; + resourcesListChanged?: boolean; + resourceSubscriptions?: string[]; + logLevel?: LoggingLevel; + }; + }; } ``` @@ -473,9 +489,7 @@ the following RPC methods and notifications are removed: - `initialize` / `notifications/initialized`: The initialization handshake is removed. Version negotiation is handled per-request via `MCP-Protocol-Version` headers and `_meta` fields. Capability discovery is handled by - `server/discover`. Servers compliant with this SEP **SHOULD** accept and - ignore `notifications/initialized` without error to maintain backward - compatibility with clients that may send it. + `server/discover`. - `logging/setLevel`: Removed. The log level is now specified per-request via the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no replacement RPC. From b6c82ec8bd8fcf73175f1cb42c46cc9fdce640e0 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:24:43 -0600 Subject: [PATCH 34/69] fix: specify request cancellation behavior for HTTP and STDIO --- docs/seps/2575-stateless-mcp.mdx | 14 ++++++++++++++ seps/2575-stateless-mcp.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index f35770d66..95ab4badb 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -360,6 +360,20 @@ sent as independent requests — they are embedded as input requests inside an `GetPrompt`, `ListResources`). The client satisfies the input requests and retries the original request. +#### Request Cancellation + +How a client cancels an in-flight request depends on the transport: + +- **HTTP.** Closing the SSE response stream **MUST** be treated by the server + as cancellation of that request. Because each request has its own response + stream, the transport-level disconnect is unambiguous. +- **STDIO.** The client **MUST** send a `notifications/cancelled` + notification referencing the request ID. STDIO has a single shared channel, + so there is no per-request stream to close. + +Servers **SHOULD** stop work on a cancelled request as soon as practical and +**MUST NOT** send any further messages for it. + #### Missing Required Capabilities A server **MUST NOT** rely on capabilities the client has not declared. If a diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 6a1f2cb6b..bb7fbfc88 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -342,6 +342,20 @@ sent as independent requests — they are embedded as input requests inside an `GetPrompt`, `ListResources`). The client satisfies the input requests and retries the original request. +#### Request Cancellation + +How a client cancels an in-flight request depends on the transport: + +- **HTTP.** Closing the SSE response stream **MUST** be treated by the server + as cancellation of that request. Because each request has its own response + stream, the transport-level disconnect is unambiguous. +- **STDIO.** The client **MUST** send a `notifications/cancelled` + notification referencing the request ID. STDIO has a single shared channel, + so there is no per-request stream to close. + +Servers **SHOULD** stop work on a cancelled request as soon as practical and +**MUST NOT** send any further messages for it. + #### Missing Required Capabilities A server **MUST NOT** rely on capabilities the client has not declared. If a From a84dbfa200207cab33b49ad67f8b61d5a1ede322 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:26:52 -0600 Subject: [PATCH 35/69] fix: make _meta required in SubscriptionsListenRequest --- docs/seps/2575-stateless-mcp.mdx | 2 +- seps/2575-stateless-mcp.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 95ab4badb..53be34b8c 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -421,7 +421,7 @@ are scoped to a specific client request. export interface SubscriptionsListenRequest extends Request { method: "subscriptions/listen"; params: { - _meta?: { + _meta: { "io.modelcontextprotocol/protocolVersion": string; "io.modelcontextprotocol/clientInfo": Implementation; "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index bb7fbfc88..2f46037dc 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -403,7 +403,7 @@ are scoped to a specific client request. export interface SubscriptionsListenRequest extends Request { method: "subscriptions/listen"; params: { - _meta?: { + _meta: { "io.modelcontextprotocol/protocolVersion": string; "io.modelcontextprotocol/clientInfo": Implementation; "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; From 6c420f4c1eb387712889d45b06ca096515f45caf Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:32:12 -0600 Subject: [PATCH 36/69] feat: support multiple subscriptions with request ID correlation --- docs/seps/2575-stateless-mcp.mdx | 48 +++++++++++++++++++++++++++----- seps/2575-stateless-mcp.md | 48 +++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 53be34b8c..55295c723 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -287,9 +287,8 @@ To complete the decoupling from the initial handshake, client capabilities are no longer negotiated once at initialization. Instead, a client **MUST** specify its capabilities on every request. This ensures the server is always fully informed about what optional features the client can handle for that specific -transaction. An absent or empty capabilities object means the client supports no -optional capabilities — servers **MUST NOT** infer capabilities from prior -requests. +transaction. An empty capabilities object means the client supports no optional +capabilities — servers **MUST NOT** infer capabilities from prior requests. #### Per-Request Metadata Schema @@ -499,6 +498,41 @@ export interface SubscriptionsAcknowledgedNotification extends Notification { } ``` +#### Multiple Concurrent Subscriptions + +A client **MAY** have multiple active subscriptions concurrently (e.g., one +listening for tools-list changes, another for resource updates). Each +subscription is identified by the JSON-RPC request ID of its +`SubscriptionsListenRequest`. + +To allow STDIO clients to demultiplex notifications belonging to different +subscriptions on the single shared channel, every notification delivered as +part of an active subscription **MUST** include the subscription's request ID +in `_meta`: + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/tools/list_changed", + "params": { + "_meta": { + "io.modelcontextprotocol/subscriptionId": "" + } + } +} +``` + +This same correlation pattern applies to other server-to-client notifications +that need to be associated with a specific request, such as +`notifications/progress` (which uses the originating request's ID). + +#### Stopping a Subscription + +- **HTTP.** Closing the SSE response stream stops the subscription. +- **STDIO.** The client sends `notifications/cancelled` referencing the listen + request's ID. The server **MUST** stop sending notifications for that + subscription. + #### Transport Behavior **HTTP.** The client sends `SubscriptionsListenRequest` via `POST`. The server's @@ -508,10 +542,10 @@ first JSON-RPC message on this stream **MUST** be a **STDIO.** The client sends `SubscriptionsListenRequest` at any time. The server **MUST** acknowledge it by sending a `SubscriptionsAcknowledgedNotification`. -Subsequent notifications flow on the bidirectional STDIO channel. If the -connection is terminated (e.g., the server crashes and restarts), the client -**MUST** re-send `SubscriptionsListenRequest` to re-establish its declared -capabilities and interests. +Subsequent notifications flow on the bidirectional STDIO channel, each tagged +with the subscription's request ID as described above. If the connection is +terminated (e.g., the server crashes and restarts), the client **MUST** re-send +`SubscriptionsListenRequest` to re-establish its subscriptions. ### Deprecated and Removed RPCs diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 2f46037dc..453f27004 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -269,9 +269,8 @@ To complete the decoupling from the initial handshake, client capabilities are no longer negotiated once at initialization. Instead, a client **MUST** specify its capabilities on every request. This ensures the server is always fully informed about what optional features the client can handle for that specific -transaction. An absent or empty capabilities object means the client supports no -optional capabilities — servers **MUST NOT** infer capabilities from prior -requests. +transaction. An empty capabilities object means the client supports no optional +capabilities — servers **MUST NOT** infer capabilities from prior requests. #### Per-Request Metadata Schema @@ -481,6 +480,41 @@ export interface SubscriptionsAcknowledgedNotification extends Notification { } ``` +#### Multiple Concurrent Subscriptions + +A client **MAY** have multiple active subscriptions concurrently (e.g., one +listening for tools-list changes, another for resource updates). Each +subscription is identified by the JSON-RPC request ID of its +`SubscriptionsListenRequest`. + +To allow STDIO clients to demultiplex notifications belonging to different +subscriptions on the single shared channel, every notification delivered as +part of an active subscription **MUST** include the subscription's request ID +in `_meta`: + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/tools/list_changed", + "params": { + "_meta": { + "io.modelcontextprotocol/subscriptionId": "" + } + } +} +``` + +This same correlation pattern applies to other server-to-client notifications +that need to be associated with a specific request, such as +`notifications/progress` (which uses the originating request's ID). + +#### Stopping a Subscription + +- **HTTP.** Closing the SSE response stream stops the subscription. +- **STDIO.** The client sends `notifications/cancelled` referencing the listen + request's ID. The server **MUST** stop sending notifications for that + subscription. + #### Transport Behavior **HTTP.** The client sends `SubscriptionsListenRequest` via `POST`. The server's @@ -490,10 +524,10 @@ first JSON-RPC message on this stream **MUST** be a **STDIO.** The client sends `SubscriptionsListenRequest` at any time. The server **MUST** acknowledge it by sending a `SubscriptionsAcknowledgedNotification`. -Subsequent notifications flow on the bidirectional STDIO channel. If the -connection is terminated (e.g., the server crashes and restarts), the client -**MUST** re-send `SubscriptionsListenRequest` to re-establish its declared -capabilities and interests. +Subsequent notifications flow on the bidirectional STDIO channel, each tagged +with the subscription's request ID as described above. If the connection is +terminated (e.g., the server crashes and restarts), the client **MUST** re-send +`SubscriptionsListenRequest` to re-establish its subscriptions. ### Deprecated and Removed RPCs From a1968d552972d76cda18bcbabe1a9c7fe4b5aae7 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:40:03 -0600 Subject: [PATCH 37/69] fix: clarify STDIO backward-compat probe applies to dual-version clients only --- docs/seps/2575-stateless-mcp.mdx | 25 +++++++++++++++++++++---- seps/2575-stateless-mcp.md | 25 +++++++++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 55295c723..83f8fc81e 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -670,10 +670,27 @@ the version prior to the SEP, and vAfter indicates a version after it. #### Client (supporting vPrev, vPost) → Server (vPrev) -1. Client sends a request (e.g. tools/list) with MCP Protocol Version header - 1. HTTP: Server says "400 bad request" - 2. STDIO: returns error indicating initialization was required -2. Client falls back to vPrev (and makes initialization) for future requests +For HTTP, the client may attempt any vPost request (e.g., `tools/list` with the +MCP Protocol Version header). The server returns `400 Bad Request` (or +`Unsupported protocol version`); the client falls back to vPrev (and performs +initialization) for future requests. + +For STDIO, the client cannot rely on a per-request error to detect the server's +version. A client that supports both a vPost (which does not require +initialization) **and** a legacy version that does require `initialize` +**SHOULD** probe with `server/discover` first to determine which to use: + +1. Client sends `server/discover` with the MCP Protocol Version `_meta` field + set to its preferred vPost. +2. If the server supports vPost (or any vPost-style version the client also + supports), the client uses the discovered version for subsequent requests. +3. If the server returns `Unsupported protocol version` or `Method not found`, + the client falls back to its supported legacy version and performs the + `initialize` handshake. + +A client that supports only vPost-style versions has no need to probe — it +simply uses its preferred version and handles `Unsupported protocol version` +errors normally. ## Security Implications diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 453f27004..63d7dc1c8 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -652,10 +652,27 @@ the version prior to the SEP, and vAfter indicates a version after it. #### Client (supporting vPrev, vPost) → Server (vPrev) -1. Client sends a request (e.g. tools/list) with MCP Protocol Version header - 1. HTTP: Server says "400 bad request" - 2. STDIO: returns error indicating initialization was required -2. Client falls back to vPrev (and makes initialization) for future requests +For HTTP, the client may attempt any vPost request (e.g., `tools/list` with the +MCP Protocol Version header). The server returns `400 Bad Request` (or +`Unsupported protocol version`); the client falls back to vPrev (and performs +initialization) for future requests. + +For STDIO, the client cannot rely on a per-request error to detect the server's +version. A client that supports both a vPost (which does not require +initialization) **and** a legacy version that does require `initialize` +**SHOULD** probe with `server/discover` first to determine which to use: + +1. Client sends `server/discover` with the MCP Protocol Version `_meta` field + set to its preferred vPost. +2. If the server supports vPost (or any vPost-style version the client also + supports), the client uses the discovered version for subsequent requests. +3. If the server returns `Unsupported protocol version` or `Method not found`, + the client falls back to its supported legacy version and performs the + `initialize` handshake. + +A client that supports only vPost-style versions has no need to probe — it +simply uses its preferred version and handles `Unsupported protocol version` +errors normally. ## Security Implications From da316b089c393ed78d752c59924728093391db99 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:41:34 -0600 Subject: [PATCH 38/69] fix: clarify missing capability check is for individual capabilities --- docs/seps/2575-stateless-mcp.mdx | 9 +++++---- seps/2575-stateless-mcp.md | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 83f8fc81e..91f49342e 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -375,10 +375,11 @@ Servers **SHOULD** stop work on a cancelled request as soon as practical and #### Missing Required Capabilities -A server **MUST NOT** rely on capabilities the client has not declared. If a -server requires client capabilities the client has not provided, the server -**MUST** return a JSON-RPC error, which specifies the missing capabilities. For -HTTP, the response status code MUST be `400 Bad Request`. +A server **MUST NOT** rely on capabilities the client has not declared. If +processing a request requires a capability the client did not declare in its +`clientCapabilities`, the server **MUST** return a JSON-RPC error specifying +the missing capabilities. For HTTP, the response status code MUST be +`400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 63d7dc1c8..0b720c4ec 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -357,10 +357,11 @@ Servers **SHOULD** stop work on a cancelled request as soon as practical and #### Missing Required Capabilities -A server **MUST NOT** rely on capabilities the client has not declared. If a -server requires client capabilities the client has not provided, the server -**MUST** return a JSON-RPC error, which specifies the missing capabilities. For -HTTP, the response status code MUST be `400 Bad Request`. +A server **MUST NOT** rely on capabilities the client has not declared. If +processing a request requires a capability the client did not declare in its +`clientCapabilities`, the server **MUST** return a JSON-RPC error specifying +the missing capabilities. For HTTP, the response status code MUST be +`400 Bad Request`. ```ts export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32003; From 87b48c6d1367cec34f66986565268106b39c2b6b Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:44:38 -0600 Subject: [PATCH 39/69] fix: clarify subscription lifecycle and remove stale result wording --- docs/seps/2575-stateless-mcp.mdx | 15 +++++++++++---- seps/2575-stateless-mcp.md | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 91f49342e..100404946 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -472,10 +472,17 @@ notifications of that type. #### Acknowledgment Notification -The server sends this notification as the first event on the stream to -acknowledge that the listen stream has been established. For HTTP, this is the -first SSE event. The stream remains open for subsequent server-to-client -messages until the server sends a final `Result` to close it. +The server sends this notification first to acknowledge that the subscription +has been established. The subscription is long-lived and has no natural +"completion result"; it ends when: + +- the client explicitly cancels it (closing the SSE stream on HTTP, or sending + `notifications/cancelled` on STDIO); +- the underlying connection is closed (HTTP timeout, TCP disconnect, STDIO + process exit); or +- the server tears it down (e.g., shutdown), in which case it **MUST** close + the SSE stream (HTTP) or send `notifications/cancelled` referencing the + subscription's request ID (STDIO). ```ts export interface SubscriptionsAcknowledgedNotification extends Notification { diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 0b720c4ec..0feed972d 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -454,10 +454,17 @@ notifications of that type. #### Acknowledgment Notification -The server sends this notification as the first event on the stream to -acknowledge that the listen stream has been established. For HTTP, this is the -first SSE event. The stream remains open for subsequent server-to-client -messages until the server sends a final `Result` to close it. +The server sends this notification first to acknowledge that the subscription +has been established. The subscription is long-lived and has no natural +"completion result"; it ends when: + +- the client explicitly cancels it (closing the SSE stream on HTTP, or sending + `notifications/cancelled` on STDIO); +- the underlying connection is closed (HTTP timeout, TCP disconnect, STDIO + process exit); or +- the server tears it down (e.g., shutdown), in which case it **MUST** close + the SSE stream (HTTP) or send `notifications/cancelled` referencing the + subscription's request ID (STDIO). ```ts export interface SubscriptionsAcknowledgedNotification extends Notification { From 369e7b014895ff5cd0d6f4aa734641adad73eafa Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:46:37 -0600 Subject: [PATCH 40/69] feat: remove resumable streams in favor of tasks primitive --- docs/seps/2575-stateless-mcp.mdx | 11 +++++++++++ seps/2575-stateless-mcp.md | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 100404946..89ae9ca51 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -373,6 +373,17 @@ How a client cancels an in-flight request depends on the transport: Servers **SHOULD** stop work on a cancelled request as soon as practical and **MUST NOT** send any further messages for it. +##### Resumable Streams Are Removed + +Because connection drops now implicitly cancel a request, resumable SSE streams +(via `Last-Event-ID` reconnection) are removed. They contradict the +stateless-by-default paradigm: resuming would require the server to retain +per-request state across connection failures. + +Workloads that need durability or resumability **MUST** use the tasks +primitive instead, which provides explicit mechanisms for fetching results +after a connection drop. + #### Missing Required Capabilities A server **MUST NOT** rely on capabilities the client has not declared. If diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 0feed972d..c53d24ddd 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -355,6 +355,17 @@ How a client cancels an in-flight request depends on the transport: Servers **SHOULD** stop work on a cancelled request as soon as practical and **MUST NOT** send any further messages for it. +##### Resumable Streams Are Removed + +Because connection drops now implicitly cancel a request, resumable SSE streams +(via `Last-Event-ID` reconnection) are removed. They contradict the +stateless-by-default paradigm: resuming would require the server to retain +per-request state across connection failures. + +Workloads that need durability or resumability **MUST** use the tasks +primitive instead, which provides explicit mechanisms for fetching results +after a connection drop. + #### Missing Required Capabilities A server **MUST NOT** rely on capabilities the client has not declared. If From f7d68ef8265f141cd423bd0ec6723c82ac9ce5e1 Mon Sep 17 00:00:00 2001 From: kpvangent <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:49:53 -0600 Subject: [PATCH 41/69] fix: clarify ping is removed in both directions --- docs/seps/2575-stateless-mcp.mdx | 9 +++++---- seps/2575-stateless-mcp.md | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 89ae9ca51..f24902503 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -589,10 +589,11 @@ the following RPC methods and notifications are removed: resources they want updates for in the `notifications` param of the `subscriptions/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. -- `ping`: Removed. With independent server-to-client requests eliminated, - ping no longer serves a useful purpose — any normal RPC call (or the - `subscriptions/listen` SSE stream's transport-layer keep-alive) demonstrates - liveness equivalently. +- `ping`: Removed in **both directions**. Server-to-client ping is removed + because servers can no longer independently send requests. Client-to-server + ping is also removed because any normal RPC call already proves server + liveness, and transport-layer mechanisms (HTTP keep-alives, SSE comments, + STDIO process status) handle connection-health checks more appropriately. ## Rationale diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index c53d24ddd..562ab12ee 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -571,10 +571,11 @@ the following RPC methods and notifications are removed: resources they want updates for in the `notifications` param of the `subscriptions/listen` request. The server sends `notifications/resources/updated` on the listen stream for matching resources. -- `ping`: Removed. With independent server-to-client requests eliminated, - ping no longer serves a useful purpose — any normal RPC call (or the - `subscriptions/listen` SSE stream's transport-layer keep-alive) demonstrates - liveness equivalently. +- `ping`: Removed in **both directions**. Server-to-client ping is removed + because servers can no longer independently send requests. Client-to-server + ping is also removed because any normal RPC call already proves server + liveness, and transport-layer mechanisms (HTTP keep-alives, SSE comments, + STDIO process status) handle connection-health checks more appropriately. ## Rationale From 4a0661f25126c19d783e0054a562cb4a8f53272f Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Thu, 7 May 2026 19:45:20 +0000 Subject: [PATCH 42/69] docs: rewrite lifecycle.mdx (SEP-2575) - Frame MCP as a stateless protocol; every request carries its own context, and connection/process identity is not a proxy for session continuity. - Replace the initialize/operation/shutdown phase framing with per-request protocol version negotiation. - Add a backward-compatibility section for dual-version clients (HTTP via 400 fallback, STDIO via server/discover probe). - Capability reference, shutdown, and timeouts will be relocated to basic/index.mdx and transports.mdx in follow-up commits. --- docs/specification/draft/basic/lifecycle.mdx | 372 +++++-------------- 1 file changed, 93 insertions(+), 279 deletions(-) diff --git a/docs/specification/draft/basic/lifecycle.mdx b/docs/specification/draft/basic/lifecycle.mdx index 69eb319a8..3e7eb3f61 100644 --- a/docs/specification/draft/basic/lifecycle.mdx +++ b/docs/specification/draft/basic/lifecycle.mdx @@ -4,221 +4,118 @@ title: Lifecycle
-The Model Context Protocol (MCP) defines a rigorous lifecycle for client-server -connections that ensures proper capability negotiation and state management. - -1. **Initialization**: Capability negotiation and protocol version agreement -2. **Operation**: Normal protocol communication -3. **Shutdown**: Graceful termination of the connection +The Model Context Protocol (MCP) is a **stateless protocol**: all the +information needed to process a request is contained in the request itself. +A server processes each request independently; no state should be inferred +from previous requests, even those on the same connection or stream. + +In particular, an open connection or STDIO process is not a conversation or +session: clients may interleave unrelated requests on the same transport, +and a server **MUST NOT** treat connection or process identity as a proxy +for conversation or session continuity. + +Specifically: + +- Servers **MUST NOT** rely on prior requests over the same connection to + establish context (e.g., capabilities, protocol version, client identity). + Every request supplies this metadata in its + [`_meta`](/specification/draft/basic/index#meta) field. +- Servers **MUST NOT** require that a client reuse the same connection to + perform related operations. +- State that needs to span multiple requests (e.g., long-running tasks, + application-level handles) **MUST** be referenced by an explicit identifier + the client passes on each request. + +Long-lived requests like +[`subscriptions/listen`](/specification/draft/server/resources#subscriptions-listen) +remain request/response — the response is just an open stream of notifications. +Their state is scoped to the request itself, not to the connection underneath. ```mermaid sequenceDiagram participant Client participant Server - Note over Client,Server: Initialization Phase - activate Client - Client->>+Server: initialize request - Server-->>Client: initialize response - Client--)Server: initialized notification - - Note over Client,Server: Operation Phase - rect rgb(200, 220, 250) - note over Client,Server: Normal protocol operations + Client->>Server: request (with `_meta`) + alt server supports requested version + Server-->>Client: result + else version unsupported + Server-->>Client: UnsupportedProtocolVersionError + Note over Client,Server: Client retries with a mutually supported version end - - Note over Client,Server: Shutdown - Client--)-Server: Disconnect - deactivate Server - Note over Client,Server: Connection closed ``` - For a walkthrough of how these lifecycle phases map to SDK code, see the + For a walkthrough of how the per-request model maps to SDK code, see the [Architecture guide](/docs/learn/architecture#example). -## Lifecycle Phases - -### Initialization - -The initialization phase **MUST** be the first interaction between client and server. -During this phase, the client and server: +## Protocol Version Negotiation -- Establish protocol version compatibility -- Exchange and negotiate capabilities -- Share implementation details +Every request declares the protocol version it is using in its +[`_meta`](/specification/draft/basic/index#meta) field. On HTTP, this is +also carried in the +[`MCP-Protocol-Version` header](/specification/draft/basic/transports#protocol-version-header). -The client **MUST** initiate this phase by sending an `initialize` request containing: - -- Protocol version supported -- Client capabilities -- Client implementation information +If the server does not implement the requested version (whether the version +is unknown to the server, or is a known version the server has chosen not to +support), it **MUST** respond with an +[`UnsupportedProtocolVersionError`](/specification/draft/schema#unsupportedprotocolversionerror) +listing the versions it does support: ```json { "jsonrpc": "2.0", "id": 1, - "method": "initialize", - "params": { - "protocolVersion": "2025-11-25", - "capabilities": { - "roots": { - "listChanged": true - }, - "sampling": {}, - "elicitation": { - "form": {}, - "url": {} - }, - "tasks": { - "requests": { - "elicitation": { - "create": {} - }, - "sampling": { - "createMessage": {} - } - } - } - }, - "clientInfo": { - "name": "ExampleClient", - "title": "Example Client Display Name", - "version": "1.0.0", - "description": "An example MCP client application", - "icons": [ - { - "src": "https://example.com/icon.png", - "mimeType": "image/png", - "sizes": ["48x48"] - } - ], - "websiteUrl": "https://example.com" + "error": { + "code": -32602, + "message": "Unsupported protocol version", + "data": { + "supported": ["DRAFT-2026-v1", "2025-11-25"], + "requested": "1900-01-01" } } } ``` -The server **MUST** respond with its own capabilities and information: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "protocolVersion": "2025-11-25", - "capabilities": { - "logging": {}, - "prompts": { - "listChanged": true - }, - "resources": { - "subscribe": true, - "listChanged": true - }, - "tools": { - "listChanged": true - }, - "tasks": { - "list": {}, - "cancel": {}, - "requests": { - "tools": { - "call": {} - } - } - } - }, - "serverInfo": { - "name": "ExampleServer", - "title": "Example Server Display Name", - "version": "1.0.0", - "description": "An example MCP server providing tools and resources", - "icons": [ - { - "src": "https://example.com/server-icon.svg", - "mimeType": "image/svg+xml", - "sizes": ["any"] - } - ], - "websiteUrl": "https://example.com/server" - }, - "instructions": "Optional instructions for the client" - } -} -``` - -After successful initialization, the client **MUST** send an `initialized` notification -to indicate it is ready to begin normal operations: - -```json -{ - "jsonrpc": "2.0", - "method": "notifications/initialized" -} -``` - -- The client **SHOULD NOT** send requests other than - [pings](/specification/draft/basic/utilities/ping) before the server has responded to the - `initialize` request. -- The server **SHOULD NOT** send requests other than - [pings](/specification/draft/basic/utilities/ping) and - [logging](/specification/draft/server/utilities/logging) before receiving the `initialized` - notification. - -#### Version Negotiation - -In the `initialize` request, the client **MUST** send a protocol version it supports. -This **SHOULD** be the _latest_ version supported by the client. - -If the server supports the requested protocol version, it **MUST** respond with the same -version. Otherwise, the server **MUST** respond with another protocol version it -supports. This **SHOULD** be the _latest_ version supported by the server. - -If the client does not support the version in the server's response, it **SHOULD** -disconnect. - - -If using HTTP, the client **MUST** include the `MCP-Protocol-Version: -` HTTP header on all subsequent requests to the MCP -server. -For details, see [the Protocol Version Header section in Transports](/specification/draft/basic/transports#protocol-version-header). - - -#### Capability Negotiation - -Client and server capabilities establish which optional protocol features will be -available during the connection. - -Key capabilities include: - -| Category | Capability | Description | -| -------- | -------------- | ---------------------------------------------------------------------------------------- | -| Client | `roots` | Ability to provide filesystem [roots](/specification/draft/client/roots) | -| Client | `sampling` | Support for LLM [sampling](/specification/draft/client/sampling) requests | -| Client | `elicitation` | Support for server [elicitation](/specification/draft/client/elicitation) requests | -| Client | `tasks` | Support for [task-augmented](/specification/draft/basic/utilities/tasks) client requests | -| Client | `extensions` | Support for optional [extensions](/docs/extensions/overview) beyond the core protocol | -| Client | `experimental` | Describes support for non-standard experimental features | -| Server | `prompts` | Offers [prompt templates](/specification/draft/server/prompts) | -| Server | `resources` | Provides readable [resources](/specification/draft/server/resources) | -| Server | `tools` | Exposes callable [tools](/specification/draft/server/tools) | -| Server | `logging` | Emits structured [log messages](/specification/draft/server/utilities/logging) | -| Server | `completions` | Supports argument [autocompletion](/specification/draft/server/utilities/completion) | -| Server | `tasks` | Support for [task-augmented](/specification/draft/basic/utilities/tasks) server requests | -| Server | `extensions` | Support for optional [extensions](/docs/extensions/overview) beyond the core protocol | -| Server | `experimental` | Describes support for non-standard experimental features | - -Capability objects can describe sub-capabilities like: - -- `listChanged`: Support for list change notifications (for prompts, resources, and - tools) -- `subscribe`: Support for subscribing to individual items' changes (resources only) - -#### Extension Negotiation - -Clients and servers can also negotiate support for optional [extensions](/docs/extensions/overview) beyond the core protocol. Extensions are advertised in the `extensions` field of capabilities, which is a map of extension identifiers to per-extension settings objects. +The client **SHOULD** select a mutually supported version from the `supported` +list and retry the request, or surface an error to the user if no compatible +version exists. + +A client **MAY** call +[`server/discover`](/specification/draft/schema#discoverrequest) before sending +any other requests to learn the server's supported versions up front. This is +optional — a client is free to invoke any RPC inline and handle +`UnsupportedProtocolVersionError` if its preferred version is not supported. + +### Backward Compatibility with Initialization-Based Versions + +A server that wishes to support both legacy clients (which expect an +`initialize` handshake) and modern clients (which use per-request metadata) +**MAY** implement both behaviors. A client that needs to interoperate with +both kinds of servers can detect which is present: + +- **HTTP.** Try a modern request directly. If the server returns + `400 Bad Request` (or any other version error indicating the server does not + implement the modern protocol), fall back to `initialize` and continue with + the legacy version for subsequent requests. +- **STDIO.** Because there is no per-request status code to drive fallback, + a client that supports both eras **SHOULD** probe with + [`server/discover`](/specification/draft/schema#discoverrequest) first, + setting its preferred modern version in `_meta`. If the server returns + `Method not found` or `UnsupportedProtocolVersionError`, fall back to the + legacy `initialize` handshake. + +A client that only supports modern (per-request-metadata) versions does not +need to probe — it simply sends its preferred version and handles +`UnsupportedProtocolVersionError` normally. + +## Extension Negotiation + +Clients and servers can negotiate support for optional +[extensions](/docs/extensions/overview) beyond the core protocol. Extensions +are advertised in the `extensions` field of capabilities, which is a map of +extension identifiers to per-extension settings objects. Example client capabilities with extensions: @@ -248,93 +145,10 @@ Example server capabilities with extensions: } ``` -Each extension specifies the schema of its settings object; an empty object indicates support with no additional settings. - -If one party supports an extension but the other does not, the supporting party **MUST** either revert to core protocol behavior or reject the request with an appropriate error. Extensions **SHOULD** document their expected fallback behavior. - -### Operation - -During the operation phase, the client and server exchange messages according to the -negotiated capabilities. - -Both parties **MUST**: - -- Respect the negotiated protocol version -- Only use capabilities that were successfully negotiated - -### Shutdown - -During the shutdown phase, one side (usually the client) cleanly terminates the protocol -connection. No specific shutdown messages are defined—instead, the underlying transport -mechanism should be used to signal connection termination: - -#### stdio - -For the stdio [transport](/specification/draft/basic/transports), the client **SHOULD** initiate -shutdown by: - -1. First, closing the input stream to the child process (the server) -2. Waiting for the server to exit -3. If the server does not exit within a reasonable time, forcibly terminating the - process using the mechanism appropriate for the operating system - -On POSIX systems, forced termination typically escalates from -[`SIGTERM`](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) to -`SIGKILL`. On Windows, where POSIX signals are not available, clients can use -[`TerminateProcess`](https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess) -or -[Job Objects](https://learn.microsoft.com/windows/win32/procthread/job-objects). - -Servers **SHOULD** exit promptly when their standard input is closed or reads return -end-of-file. This is the primary graceful-shutdown signal and the only portable one, so -honoring it reduces the need for forced termination. - -The server **MAY** initiate shutdown by closing its output stream to the client and -exiting. - -#### HTTP - -For HTTP [transports](/specification/draft/basic/transports), shutdown is indicated by closing the -associated HTTP connection(s). - -## Timeouts +Each extension specifies the schema of its settings object; an empty object +indicates support with no additional settings. -Implementations **SHOULD** establish timeouts for all sent requests, to prevent hung -connections and resource exhaustion. When the request has not received a success or error -response within the timeout period, the sender **SHOULD** issue a [cancellation -notification](/specification/draft/basic/utilities/cancellation) for that request and stop waiting for -a response. - -SDKs and other middleware **SHOULD** allow these timeouts to be configured on a -per-request basis. - -Implementations **MAY** choose to reset the timeout clock when receiving a [progress -notification](/specification/draft/basic/utilities/progress) corresponding to the request, as this -implies that work is actually happening. However, implementations **SHOULD** always -enforce a maximum timeout, regardless of progress notifications, to limit the impact of a -misbehaving client or server. - -## Error Handling - -Implementations **SHOULD** be prepared to handle these error cases: - -- Protocol version mismatch -- Failure to negotiate required capabilities -- Request [timeouts](#timeouts) - -Example initialization error: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - "code": -32602, - "message": "Unsupported protocol version", - "data": { - "supported": ["2024-11-05"], - "requested": "1.0.0" - } - } -} -``` +If one party supports an extension but the other does not, the supporting +party **MUST** either revert to core protocol behavior or reject the request +with an appropriate error. Extensions **SHOULD** document their expected +fallback behavior. From 2a538c687bc29bd20f630cd0ec36ad8b272e9062 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:12:06 -0600 Subject: [PATCH 43/69] docs: rewrite transports.mdx (SEP-2575) - stdio: add Sending/Receiving/Request Metadata/Cancellation/Shutdown/Backward Compatibility subsections - HTTP: remove GET endpoint, Multiple Connections, Resumability/Redelivery sections - HTTP: add Cancellation subsection (closing SSE stream = cancel) - HTTP: fold X-Accel-Buffering into Receiving Messages; add MRTR rule and tasks durability pointer - HTTP: remove Shutdown subsection (stateless; nothing protocol-level to say) - HTTP: restructure Request Metadata into Protocol Version Header + Standard Request Headers + Custom Headers - HTTP: update Protocol Version Header to SEP-2575 rules; examples include _meta fields - Both: convert cross-page links to reference-style definitions at bottom of each section --- docs/specification/draft/basic/transports.mdx | 780 +++++++++--------- 1 file changed, 405 insertions(+), 375 deletions(-) diff --git a/docs/specification/draft/basic/transports.mdx b/docs/specification/draft/basic/transports.mdx index b58092b75..c364ca363 100644 --- a/docs/specification/draft/basic/transports.mdx +++ b/docs/specification/draft/basic/transports.mdx @@ -19,242 +19,262 @@ It is also possible for clients and servers to implement ## stdio -In the **stdio** transport: +In the **stdio** transport, the client launches the MCP server as a subprocess. +The two ends communicate over the subprocess's standard streams: -- The client launches the MCP server as a subprocess. -- The server reads JSON-RPC messages from its standard input (`stdin`) and sends messages - to its standard output (`stdout`). +- The server reads JSON-RPC messages from `stdin` and writes JSON-RPC messages to + `stdout`. - Messages are individual JSON-RPC requests, notifications, or responses. - Messages are delimited by newlines, and **MUST NOT** contain embedded newlines. -- The server **MAY** write UTF-8 strings to its standard error (`stderr`) for any - logging purposes including informational, debug, and error messages. -- The client **MAY** capture, forward, or ignore the server's `stderr` output - and **SHOULD NOT** assume `stderr` output indicates error conditions. -- The server **MUST NOT** write anything to its `stdout` that is not a valid MCP message. -- The client **MUST NOT** write anything to the server's `stdin` that is not a valid MCP +- The server **MAY** write UTF-8 strings to `stderr` for any logging purposes + including informational, debug, and error messages. +- The client **MAY** capture, forward, or ignore the server's `stderr` output and + **SHOULD NOT** assume `stderr` output indicates error conditions. +- The server **MUST NOT** write anything to its `stdout` that is not a valid MCP message. +- The client **MUST NOT** write anything to the server's `stdin` that is not a + valid MCP message. -```mermaid -sequenceDiagram - participant Client - participant Server Process - - Client->>+Server Process: Launch subprocess - loop Message Exchange - Client->>Server Process: Write to stdin - Server Process->>Client: Write to stdout - Server Process--)Client: Optional logs on stderr - end - Client->>Server Process: Close stdin, terminate subprocess - deactivate Server Process -``` +### Sending Messages -## Streamable HTTP +The client sends messages by writing JSON-RPC requests, notifications, or +responses to the server's `stdin`, one message per line. - +### Receiving Messages -This replaces the [HTTP+SSE -transport](/specification/2024-11-05/basic/transports#http-with-sse) from -protocol version 2024-11-05. See the [backwards compatibility](#backwards-compatibility) -guide below. +All server-to-client messages — responses to client requests, in-flight +notifications (`notifications/progress`, `notifications/message`), and +deliveries on a [`subscriptions/listen`][subscriptions-listen] +stream — arrive on `stdout`, one message per line, multiplexed onto a single +shared channel. - +To distinguish notifications belonging to different concurrent subscriptions, +clients **MUST** correlate notifications using the +`io.modelcontextprotocol/subscriptionId` field carried in `_meta`. See the +schema for [`SubscriptionsListenRequest`][subscriptions-listen-request] +for details. -In the **Streamable HTTP** transport, the server operates as an independent process that -can handle multiple client connections. This transport uses HTTP POST and GET requests. -The server can optionally make use of -[Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE) to stream -multiple server messages. This permits basic MCP servers, as well as more feature-rich -servers supporting streaming and server-to-client notifications and requests. +[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen +[subscriptions-listen-request]: /specification/draft/schema#subscriptionslistenrequest -The server **MUST** provide a single HTTP endpoint path (hereafter referred to as the -**MCP endpoint**) that supports both POST and GET methods. For example, this could be a -URL like `https://example.com/mcp`. +### Request Metadata -#### Security Warning +All request metadata for the stdio transport is carried inline in the +JSON-RPC message body. The protocol version, client identity, and +per-request capabilities live in +[`_meta.io.modelcontextprotocol/*`][meta-fields]; +the method name and arguments live where JSON-RPC puts them. There is no +header layer. -When implementing Streamable HTTP transport: +[meta-fields]: /specification/draft/basic/index#meta -1. Servers **MUST** validate the `Origin` header on all incoming connections to prevent DNS rebinding attacks - - If the `Origin` header is present and invalid, servers **MUST** respond with HTTP 403 Forbidden. The HTTP response - body **MAY** comprise a JSON-RPC _error response_ that has no `id` -2. When running locally, servers **SHOULD** bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0) -3. Servers **SHOULD** implement proper authentication for all connections +### Cancellation -Without these protections, attackers could use DNS rebinding to interact with local MCP servers from remote websites. +To cancel an in-flight request, the client **MUST** send a +`notifications/cancelled` notification referencing the request's ID. Because +stdio is a single shared bidirectional channel, there is no per-request stream +to close. Servers **SHOULD** stop work on a cancelled request as soon as +practical and **MUST NOT** send any further messages for it. See +[Cancellation][cancellation] for the full rules. -### Sending Messages to the Server +[cancellation]: /specification/draft/basic/utilities/cancellation -Every JSON-RPC message sent from the client **MUST** be a new HTTP POST request to the -MCP endpoint. +### Shutdown -1. The client **MUST** use HTTP POST to send JSON-RPC messages to the MCP endpoint. -2. The client **MUST** include an `Accept` header, listing both `application/json` and - `text/event-stream` as supported content types. -3. The client **MUST** include the [standard MCP request headers](#standard-mcp-request-headers) - on each POST request. -4. The body of the HTTP POST request **MUST** be a single JSON-RPC _request_, _notification_, or _response_ to a server-sent request. -5. If the body is a JSON-RPC _notification_ or _response_ to a server-sent request: - - If the server accepts the input, the server **MUST** return HTTP status code 202 - Accepted with no body. - - If the server cannot accept the input, it **MUST** return an HTTP error status code - (e.g., 400 Bad Request). The HTTP response body **MAY** comprise a JSON-RPC _error - response_ that has no `id`. -6. If the body is a JSON-RPC _request_, the server **MUST** either - return `Content-Type: text/event-stream`, to initiate an SSE stream, or - `Content-Type: application/json`, to return one JSON object. The client **MUST** - support both these cases. -7. If the server initiates an SSE stream: - - The server **SHOULD** immediately send an SSE event consisting of an event - ID and an empty `data` field in order to prime the client to reconnect - (using that event ID as `Last-Event-ID`). - - After the server has sent an SSE event with an event ID to the client, the - server **MAY** close the _connection_ (without terminating the _SSE stream_) - at any time in order to avoid holding a long-lived connection. The client - **SHOULD** then "poll" the SSE stream by attempting to reconnect. - - If the server does close the _connection_ prior to terminating the _SSE stream_, - it **SHOULD** send an SSE event with a standard [`retry`](https://html.spec.whatwg.org/multipage/server-sent-events.html#:~:text=field%20name%20is%20%22retry%22) field before - closing the connection. The client **MUST** respect the `retry` field, - waiting the given number of milliseconds before attempting to reconnect. - - The SSE stream **SHOULD** eventually include a JSON-RPC _response_ for the - JSON-RPC _request_ sent in the POST body. - - The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending the - JSON-RPC _response_. These messages **MUST** relate to the originating client - _request_. - - After the JSON-RPC _response_ has been sent, the server **SHOULD** terminate the - SSE stream. - - Disconnection **MAY** occur at any time (e.g., due to network conditions). - Therefore: - - Disconnection **SHOULD NOT** be interpreted as the client cancelling its request. - - To cancel, the client **SHOULD** explicitly send an MCP `CancelledNotification`. - - To avoid message loss due to disconnection, the server **MAY** make the stream - [resumable](#resumability-and-redelivery). - -### Listening for Messages from the Server - -1. The client **MAY** issue an HTTP GET to the MCP endpoint. This can be used to open an - SSE stream, allowing the server to communicate to the client, without the client first - sending data via HTTP POST. -2. The client **MUST** include an `Accept` header, listing `text/event-stream` as a - supported content type. -3. The server **MUST** either return `Content-Type: text/event-stream` in response to - this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server - does not offer an SSE stream at this endpoint. Per [RFC 9110 §15.5.6](https://httpwg.org/specs/rfc9110.html#status.405), if the server returns HTTP 405, it - **MUST** include an `Allow` header listing the methods it does support (e.g., - `Allow: POST`). -4. If the server initiates an SSE stream: - - The server **MAY** send JSON-RPC _notifications_ and _pings_ on the stream. - - These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC - _request_ from the client, **except** that `roots/list`, - `sampling/createMessage`, and `elicitation/create` requests **MUST NOT** be - sent on standalone streams. - - The server **MUST NOT** send a JSON-RPC _response_ on the stream **unless** - [resuming](#resumability-and-redelivery) a stream associated with a previous client - request. - - The server **MAY** close the SSE stream at any time. - - If the server closes the _connection_ without terminating the _stream_, it - **SHOULD** follow the same polling behavior as described for POST requests: - sending a `retry` field and allowing the client to reconnect. - - The client **MAY** close the SSE stream at any time. - -### Multiple Connections - -1. The client **MAY** remain connected to multiple SSE streams simultaneously. -2. The server **MUST** send each of its JSON-RPC messages on only one of the connected - streams; that is, it **MUST NOT** broadcast the same message across multiple streams. - - The risk of message loss **MAY** be mitigated by making the stream - [resumable](#resumability-and-redelivery). - -### Resumability and Redelivery - -To support resuming broken connections, and redelivering messages that might otherwise be -lost: - -1. Servers **MAY** attach an `id` field to their SSE events, as described in the - [SSE standard](https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation). - - If present, the ID **MUST** be globally unique across all streams. - - Event IDs **SHOULD** encode sufficient information to identify the originating - stream, enabling the server to correlate a `Last-Event-ID` to the correct stream. -2. If the client wishes to resume after a disconnection (whether due to network failure - or server-initiated closure), it **SHOULD** issue an HTTP GET to the MCP endpoint, - and include the - [`Last-Event-ID`](https://html.spec.whatwg.org/multipage/server-sent-events.html#the-last-event-id-header) - header to indicate the last event ID it received. - - The server **MAY** use this header to replay messages that would have been sent - after the last event ID, _on the stream that was disconnected_, and to resume the - stream from that point. - - The server **MUST NOT** replay messages that would have been delivered on a - different stream. - - This mechanism applies regardless of how the original stream was initiated (via - POST or GET). Resumption is always via HTTP GET with `Last-Event-ID`. - -In other words, these event IDs should be assigned by servers on a _per-stream_ basis, to -act as a cursor within that particular stream. - -### Sequence Diagram - -```mermaid -sequenceDiagram - participant Client - participant Server - - note over Client, Server: initialization - - Client->>+Server: POST InitializeRequest - Server->>-Client: InitializeResponse - - Client->>+Server: POST InitializedNotification - Server->>-Client: 202 Accepted - - note over Client, Server: client requests - Client->>+Server: POST ... request ... - - alt single HTTP response - Server->>Client: ... response ... - else server opens SSE stream - loop while connection remains open - Server-)Client: ... SSE messages from server ... - end - Server-)Client: SSE event: ... response ... - end - deactivate Server - - note over Client, Server: client notifications/responses - Client->>+Server: POST ... notification/response ... - Server->>-Client: 202 Accepted - - note over Client, Server: server requests - Client->>+Server: GET - loop while connection remains open - Server-)Client: ... SSE messages from server ... - end - deactivate Server +The client **SHOULD** initiate shutdown by: -``` +1. Closing the input stream to the child process (the server). +2. Waiting for the server to exit. +3. If the server does not exit within a reasonable time, forcibly terminating + the process using the mechanism appropriate for the operating system. + +On POSIX systems, forced termination typically escalates from +[`SIGTERM`][sigterm] +to `SIGKILL`. On Windows, where POSIX signals are not available, clients can +use [`TerminateProcess`][terminateprocess] +or [Job Objects][job-objects]. -### Protocol Version Header +Servers **SHOULD** exit promptly when their standard input is closed or reads +return end-of-file. This is the primary graceful-shutdown signal and the only +portable one, so honoring it reduces the need for forced termination. -If using HTTP, the client **MUST** include the `MCP-Protocol-Version: -` HTTP header on all subsequent requests to the MCP -server, allowing the MCP server to respond based on the MCP protocol version. +The server **MAY** initiate shutdown by closing its output stream to the +client and exiting. -For example: `MCP-Protocol-Version: 2025-06-18` +[sigterm]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html +[terminateprocess]: https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess +[job-objects]: https://learn.microsoft.com/windows/win32/procthread/job-objects -The protocol version sent by the client **SHOULD** be the one [negotiated during -initialization](/specification/draft/basic/lifecycle#version-negotiation). +### Backward Compatibility -For backwards compatibility, if the server does _not_ receive an `MCP-Protocol-Version` -header, and has no other way to identify the version - for example, by relying on the -protocol version negotiated during initialization - the server **SHOULD** assume protocol -version `2025-03-26`. +A client that supports both modern (per-request-metadata) MCP versions and a +legacy version that requires an `initialize` handshake **SHOULD** probe with +[`server/discover`][server-discover] before sending +any other request. If the server returns `Method not found` or +`UnsupportedProtocolVersionError`, the client falls back to the legacy +`initialize` handshake. See +[Lifecycle: Backward Compatibility][lifecycle-compat] +for details. -If the server receives a request with an invalid or unsupported -`MCP-Protocol-Version`, it **MUST** respond with `400 Bad Request`. +A client that only supports modern versions does not need to probe. + +[server-discover]: /specification/draft/schema#discoverrequest +[lifecycle-compat]: /specification/draft/basic/lifecycle#backward-compatibility-with-initialization-based-versions + +## Streamable HTTP + + + +This replaces the [HTTP+SSE transport][http-sse] from +protocol version 2024-11-05. See [Backward Compatibility](#backward-compatibility-1) +below. + + -### Standard MCP Request Headers +In the **Streamable HTTP** transport, the server operates as an independent +process that can handle multiple client connections. The transport uses HTTP +POST. The server can optionally use +[Server-Sent Events][sse] (SSE) +to stream multiple server messages in response to a single request. -The Streamable HTTP transport requires clients to include the following headers on POST -requests, mirrored from the JSON-RPC request body: +The server **MUST** provide a single HTTP endpoint path (hereafter referred to +as the **MCP endpoint**) that supports POST. For example, this could be a URL +like `https://example.com/mcp`. + +[http-sse]: /specification/2024-11-05/basic/transports#http-with-sse +[sse]: https://en.wikipedia.org/wiki/Server-sent_events + +### Security & Endpoint + +When implementing Streamable HTTP transport: + +1. Servers **MUST** validate the `Origin` header on all incoming connections + to prevent DNS rebinding attacks. + - If the `Origin` header is present and invalid, servers **MUST** respond + with HTTP 403 Forbidden. The HTTP response body **MAY** comprise a + JSON-RPC _error response_ that has no `id`. +2. When running locally, servers **SHOULD** bind only to localhost + (127.0.0.1) rather than all network interfaces (0.0.0.0). +3. Servers **SHOULD** implement proper authentication for all connections. + +Without these protections, attackers could use DNS rebinding to interact with +local MCP servers from remote websites. + +### Sending Messages + +Every JSON-RPC message sent from the client **MUST** be a new HTTP POST +request to the MCP endpoint. + +1. The client **MUST** use HTTP POST to send JSON-RPC messages. +2. The client **MUST** include an `Accept` header listing both + `application/json` and `text/event-stream` as supported content types. +3. The client **MUST** include the [request metadata headers](#request-metadata-1) + on each POST request. +4. The body of the HTTP POST **MUST** be a single JSON-RPC _request_, + _notification_, or _response_ to a server-initiated input request (see + [Receiving Messages](#receiving-messages-1)). +5. If the body is a JSON-RPC _notification_ or a _response_ to a + server-initiated input request: + - If the server accepts it, the server **MUST** return HTTP status code + `202 Accepted` with no body. + - If the server cannot accept it, it **MUST** return an HTTP error status + code (e.g., `400 Bad Request`). The HTTP response body **MAY** comprise + a JSON-RPC _error response_ that has no `id`. +6. If the body is a JSON-RPC _request_, the server **MUST** return either + `Content-Type: application/json` (a single JSON object) or + `Content-Type: text/event-stream` (an SSE response stream). The client + **MUST** support both. + +### Receiving Messages + +When the server returns an SSE response stream +(`Content-Type: text/event-stream`): + +- The server **MAY** send JSON-RPC _notifications_ — for example, + [`notifications/progress`][notifications-progress] + or [`notifications/message`][notifications-message] — + before the final response. These notifications **MUST** relate to the + originating client request. +- The server **MUST NOT** send independent JSON-RPC _requests_ on this stream. + Server-to-client interactions (sampling, elicitation, list-roots) are + embedded as input requests inside an + [`IncompleteResult`][incomplete-result] per + [SEP-2322 (MRTR)][sep-2322], not delivered as separate requests on + this or any other stream. +- The final JSON-RPC _response_ **SHOULD** terminate the stream. + +Long-lived notification streams are obtained by sending a +[`subscriptions/listen`][subscriptions-listen] +request. The server's response is itself an SSE stream that stays open and +delivers `notifications/tools/list_changed`, +`notifications/resources/updated`, `notifications/message`, etc. for the +notification types the client opted in to. + +When initiating an SSE stream, servers **SHOULD** include the +`X-Accel-Buffering: no` header in the HTTP response. This instructs reverse +proxies (such as nginx) to disable response buffering, ensuring that SSE +events are delivered to clients immediately rather than being held in a +buffer. Without this header, proxies may accumulate messages before sending +them to the client, introducing unwanted latency and potentially breaking the +real-time nature of SSE communication. + +For workloads that need durability across connection drops, use the +[tasks primitive][tasks]; resumable SSE +streams via `Last-Event-ID` are not supported. + +[notifications-progress]: /specification/draft/basic/utilities/progress +[notifications-message]: /specification/draft/server/utilities/logging +[incomplete-result]: /specification/draft/schema#inputrequiredresult +[sep-2322]: /seps/2322-MRTR +[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen +[tasks]: /specification/draft/basic/utilities/tasks + +### Cancellation + +Closing the SSE response stream **MUST** be treated by the server as +cancellation of that request. Because each request has its own response +stream, the transport-level disconnect is unambiguous. The server **SHOULD** +stop work on the cancelled request as soon as practical and **MUST NOT** send +any further messages for it. See +[Cancellation][cancellation] for the full rules. + +[cancellation]: /specification/draft/basic/utilities/cancellation + +### Request Metadata + +The Streamable HTTP transport mirrors selected JSON-RPC body fields into HTTP +headers so that intermediaries (load balancers, gateways, observability +tooling) can route and inspect requests without parsing the body. + +#### Protocol Version Header + +Every POST request to the MCP endpoint **MUST** include an +`MCP-Protocol-Version` header. + +For example: `MCP-Protocol-Version: DRAFT-2026-v1` + +The header value **MUST** match the +`io.modelcontextprotocol/protocolVersion` field carried in the request body's +`_meta`. If the values do not match, the server **MUST** reject the request +with `400 Bad Request` and a `HeaderMismatch` JSON-RPC error +(see [Server Validation](#server-validation)). + +If the server does not implement the requested protocol version (whether the +version is unknown to the server, or is a known version the server has chosen +not to support), it **MUST** respond with `400 Bad Request` and an +[`UnsupportedProtocolVersionError`][unsupported-version] +listing its supported versions. See +[Lifecycle: Protocol Version Negotiation][lifecycle-version] +for the negotiation flow. + +For backward compatibility, if the server does _not_ receive an +`MCP-Protocol-Version` header and has no other way to identify the version, +the server **SHOULD** assume protocol version `2025-03-26`. + +[unsupported-version]: /specification/draft/schema#unsupportedprotocolversionerror +[lifecycle-version]: /specification/draft/basic/lifecycle#protocol-version-negotiation + +#### Standard Request Headers | Header Name | Source Field | Required For | | ------------ | ----------------------------- | ------------------------------------------------------ | @@ -263,13 +283,12 @@ requests, mirrored from the JSON-RPC request body: These headers are **REQUIRED** for compliance. -#### Examples - **`tools/call` request:** ```http POST /mcp HTTP/1.1 Content-Type: application/json +MCP-Protocol-Version: DRAFT-2026-v1 Mcp-Method: tools/call Mcp-Name: get_weather @@ -281,6 +300,14 @@ Mcp-Name: get_weather "name": "get_weather", "arguments": { "location": "Seattle, WA" + }, + "_meta": { + "io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1", + "io.modelcontextprotocol/clientInfo": { + "name": "ExampleClient", + "version": "1.0.0" + }, + "io.modelcontextprotocol/clientCapabilities": {} } } } @@ -291,6 +318,7 @@ Mcp-Name: get_weather ```http POST /mcp HTTP/1.1 Content-Type: application/json +MCP-Protocol-Version: DRAFT-2026-v1 Mcp-Method: resources/read Mcp-Name: file:///projects/myapp/config.json @@ -299,124 +327,53 @@ Mcp-Name: file:///projects/myapp/config.json "id": 2, "method": "resources/read", "params": { - "uri": "file:///projects/myapp/config.json" - } -} -``` - -**`initialize` request (no `Mcp-Name` needed):** - -```http -POST /mcp HTTP/1.1 -Content-Type: application/json -Mcp-Method: initialize - -{ - "jsonrpc": "2.0", - "id": 4, - "method": "initialize", - "params": { - "protocolVersion": "2025-06-18", - "capabilities": {}, - "clientInfo": { - "name": "ExampleClient", - "version": "1.0.0" + "uri": "file:///projects/myapp/config.json", + "_meta": { + "io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1", + "io.modelcontextprotocol/clientInfo": { + "name": "ExampleClient", + "version": "1.0.0" + }, + "io.modelcontextprotocol/clientCapabilities": {} } } } ``` -**Notification:** - -```http -POST /mcp HTTP/1.1 -Content-Type: application/json -Mcp-Method: notifications/initialized - -{ - "jsonrpc": "2.0", - "method": "notifications/initialized" -} -``` - -#### Case Sensitivity - -Header names (called "field names" in [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-field-names)) -are case-insensitive. Clients and servers **MUST** use case-insensitive comparisons for -header names. Header _values_ (such as method names) are case-sensitive. +#### Custom Headers from Tool Parameters -#### Server Validation - -Servers that process the request body **MUST** reject requests where the values specified -in the headers do not match the corresponding values in the request body. This prevents -potential security vulnerabilities when different components in the network rely on -different sources of truth (e.g., a load balancer routing on the header value while the -MCP server executes based on the body value). - -When rejecting a request due to header validation failure, servers **MUST** return HTTP -status `400 Bad Request` and **SHOULD** include a JSON-RPC error response using the following error code: - -| Code | Name | Description | -| -------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `-32001` | `HeaderMismatch` | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. | - -This error code is in the JSON-RPC implementation-defined server error range (`-32000` to -`-32099`). - -**Example error response:** - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - "code": -32001, - "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'" - } -} -``` - -Validation failure conditions include: - -- A required standard header (`Mcp-Method`, `Mcp-Name`) is missing -- A header value does not match the corresponding request body value -- A header value contains invalid characters - - - -Intermediaries **MUST** return an appropriate HTTP error status (e.g., `400 Bad Request`) -for validation failures but are not required to return a JSON-RPC error response. - - - -### Custom Headers from Tool Parameters - -MCP servers **MAY** designate specific tool parameters to be mirrored into HTTP headers -using an `x-mcp-header` extension property in the parameter's schema within the tool's -`inputSchema`. See [Tool Definitions](/specification/draft/server/tools#x-mcp-header) for +MCP servers **MAY** designate specific tool parameters to be mirrored into +HTTP headers using an `x-mcp-header` extension property in the parameter's +schema within the tool's `inputSchema`. See +[Tool Definitions][tool-definitions] for details on how to annotate tool parameters. -While the use of `x-mcp-header` is optional for servers, clients **MUST** support this -feature. When a server's tool definition includes `x-mcp-header` annotations, conforming -clients **MUST** mirror the designated parameter values into HTTP headers. +While the use of `x-mcp-header` is optional for servers, clients **MUST** +support this feature. When a server's tool definition includes +`x-mcp-header` annotations, conforming clients **MUST** mirror the +designated parameter values into HTTP headers. + +[tool-definitions]: /specification/draft/server/tools#x-mcp-header -#### Schema Extension +##### Schema Extension -The `x-mcp-header` property specifies the name portion used to construct the header name -`Mcp-Param-{name}`. +The `x-mcp-header` property specifies the name portion used to construct +the header name `Mcp-Param-{name}`. **Constraints on `x-mcp-header` values**: - **MUST NOT** be empty - **MUST** contain only ASCII characters (excluding space and `:`) -- **MUST** be case-insensitively unique among all `x-mcp-header` values in the - `inputSchema` -- **MUST** only be applied to parameters with primitive types (number, string, boolean) +- **MUST** be case-insensitively unique among all `x-mcp-header` values in + the `inputSchema` +- **MUST** only be applied to parameters with primitive types (number, + string, boolean) -Clients **MUST** reject tool definitions where any `x-mcp-header` value violates these -constraints. Rejection means the client **MUST** exclude the invalid tool from the result -of `tools/list`. Clients **SHOULD** log a warning when rejecting a tool definition, -including the tool name and the reason for rejection. +Clients **MUST** reject tool definitions where any `x-mcp-header` value +violates these constraints. Rejection means the client **MUST** exclude the +invalid tool from the result of `tools/list`. Clients **SHOULD** log a +warning when rejecting a tool definition, including the tool name and the +reason for rejection. **Example tool definition:** @@ -447,6 +404,7 @@ including the tool name and the reason for rejection. ```http POST /mcp HTTP/1.1 Content-Type: application/json +MCP-Protocol-Version: DRAFT-2026-v1 Mcp-Method: tools/call Mcp-Name: execute_sql Mcp-Param-Region: us-west1 @@ -465,10 +423,10 @@ Mcp-Param-Region: us-west1 } ``` -#### Value Encoding +##### Value Encoding -Clients **MUST** encode parameter values before including them in HTTP headers to ensure -safe transmission and prevent injection attacks. +Clients **MUST** encode parameter values before including them in HTTP +headers to ensure safe transmission and prevent injection attacks. **Type conversion**: Convert the parameter value to its string representation: @@ -476,20 +434,21 @@ safe transmission and prevent injection attacks. - `number`: Convert to decimal string representation (e.g., `42`, `3.14`) - `boolean`: Convert to lowercase `"true"` or `"false"` -Per [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-field-values), HTTP -header field values must consist of visible ASCII characters (0x21-0x7E), space (0x20), -and horizontal tab (0x09). When a value cannot be safely represented as a plain ASCII -header value (e.g., it contains non-ASCII characters, control characters, or has -leading/trailing whitespace), clients **MUST** use Base64 encoding of the UTF-8 +Per [RFC 9110][rfc9110-values], +HTTP header field values must consist of visible ASCII characters +(0x21-0x7E), space (0x20), and horizontal tab (0x09). When a value cannot +be safely represented as a plain ASCII header value (e.g., it contains +non-ASCII characters, control characters, or has leading/trailing +whitespace), clients **MUST** use Base64 encoding of the UTF-8 representation with the following format: ```text Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?= ``` -The prefix `=?base64?` and suffix `?=` indicate that the value is Base64-encoded. -Servers and intermediaries that need to inspect these values **MUST** decode them -accordingly. +The prefix `=?base64?` and suffix `?=` indicate that the value is +Base64-encoded. Servers and intermediaries that need to inspect these +values **MUST** decode them accordingly. **Encoding examples:** @@ -500,29 +459,37 @@ accordingly. | `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` | | `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` | -#### Client Behavior +[rfc9110-values]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-values -When constructing a `tools/call` request via HTTP transport, the client **MUST**: +##### Client Behavior -1. Extract the values for any standard headers from the request body (e.g., `method`, - `params.name`, `params.uri`) -2. Append the `Mcp-Method` header and, if applicable, `Mcp-Name` header to the request -3. Inspect the tool's `inputSchema` for properties marked with `x-mcp-header` and extract - the value for each parameter -4. Encode the values according to the [Value Encoding](#value-encoding) rules -5. Append a `Mcp-Param-{Name}: {Value}` header to the request +When constructing a `tools/call` request via HTTP transport, the client +**MUST**: -#### Server Behavior for Custom Headers +1. Extract the values for any standard headers from the request body (e.g., + `method`, `params.name`, `params.uri`). +2. Append the `Mcp-Method` header and, if applicable, `Mcp-Name` header to + the request. +3. Inspect the tool's `inputSchema` for properties marked with + `x-mcp-header` and extract the value for each parameter. +4. Encode the values according to the [Value Encoding](#value-encoding) + rules. +5. Append a `Mcp-Param-{Name}: {Value}` header to the request. -Intermediate servers that do not recognize an `Mcp-Param-{Name}` header **MUST** forward it and otherwise ignore it, as required by the [HTTP Semantics RFC](https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names). +##### Server Behavior for Custom Headers -Servers **MUST** reject requests with a recognized `Mcp-Param-{Name}` header that contains invalid -characters (see [Value Encoding](#value-encoding)). +Intermediate servers that do not recognize an `Mcp-Param-{Name}` header +**MUST** forward it and otherwise ignore it, as required by the +[HTTP Semantics RFC][http-semantics]. -Any server that processes the message body **MUST** validate that encoded header values, -after decoding if Base64-encoded, match the corresponding values in the request body. -Servers **MUST** reject requests with a `400 Bad Request` HTTP status and JSON-RPC error -code `-32001` (`HeaderMismatch`) if any validation fails. +Servers **MUST** reject requests with a recognized `Mcp-Param-{Name}` header +that contains invalid characters (see [Value Encoding](#value-encoding)). + +Any server that processes the message body **MUST** validate that encoded +header values, after decoding if Base64-encoded, match the corresponding +values in the request body. Servers **MUST** reject requests with a +`400 Bad Request` HTTP status and JSON-RPC error code `-32001` +(`HeaderMismatch`) if any validation fails. | Scenario | Client Behavior | Server Behavior | | ---------------------------------------- | ------------------------------ | ---------------------------------------- | @@ -531,52 +498,115 @@ code `-32001` (`HeaderMismatch`) if any validation fails. | Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header | | Client omits header but value is in body | Non-conforming client | Server MUST reject the request | -### SSE Stream Configuration +[http-semantics]: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names + +#### Case Sensitivity + +Header names (called "field names" in +[RFC 9110][rfc9110-names]) +are case-insensitive. Clients and servers **MUST** use case-insensitive +comparisons for header names. Header _values_ (such as method names) are +case-sensitive. + +[rfc9110-names]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-names + +#### Server Validation + +Servers that process the request body **MUST** reject requests where the +values specified in the headers do not match the corresponding values in the +request body. This prevents potential security vulnerabilities when +different components in the network rely on different sources of truth +(e.g., a load balancer routing on the header value while the MCP server +executes based on the body value). + +When rejecting a request due to header validation failure, servers **MUST** +return HTTP status `400 Bad Request` and **SHOULD** include a JSON-RPC error +response using the following error code: + +| Code | Name | Description | +| -------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `-32001` | `HeaderMismatch` | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. | + +This error code is in the JSON-RPC implementation-defined server error range +(`-32000` to `-32099`). + +**Example error response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32001, + "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'" + } +} +``` + +Validation failure conditions include: + +- A required standard header (`MCP-Protocol-Version`, `Mcp-Method`, + `Mcp-Name`) is missing. +- A header value does not match the corresponding request body value. +- A header value contains invalid characters. + + + +Intermediaries **MUST** return an appropriate HTTP error status (e.g., +`400 Bad Request`) for validation failures but are not required to return +a JSON-RPC error response. + + -When initiating SSE streams, servers **SHOULD** include the `X-Accel-Buffering: no` -header in HTTP responses that return `Content-Type: text/event-stream`. This header -instructs reverse proxies (such as nginx) to disable response buffering, ensuring that -SSE events are delivered to clients immediately rather than being held in a buffer. -Without this header, proxies may accumulate messages before sending them to the client, -introducing unwanted latency and potentially breaking the real-time nature of SSE -communication. +### Backward Compatibility -### Backwards Compatibility +A client that supports both modern (per-request-metadata) MCP versions and a +legacy version that requires an `initialize` handshake **MAY** detect which +era the server implements by attempting a modern request first. If the +server returns `400 Bad Request` (or any other version error indicating the +server does not implement the modern protocol), the client falls back to +`initialize` and continues with the legacy version for subsequent requests. +See [Lifecycle: Backward Compatibility][lifecycle-compat] +for details. -Clients and servers can maintain backwards compatibility with the deprecated [HTTP+SSE -transport](/specification/2024-11-05/basic/transports#http-with-sse) (from +Separately, clients and servers can maintain backward compatibility with the +deprecated [HTTP+SSE transport][http-sse] (from protocol version 2024-11-05) as follows: **Servers** wanting to support older clients should: -- Continue to host both the SSE and POST endpoints of the old transport, alongside the - new "MCP endpoint" defined for the Streamable HTTP transport. - - It is also possible to combine the old POST endpoint and the new MCP endpoint, but - this may introduce unneeded complexity. +- Continue to host both the SSE and POST endpoints of the old transport, + alongside the new "MCP endpoint" defined for the Streamable HTTP transport. + - It is also possible to combine the old POST endpoint and the new MCP + endpoint, but this may introduce unneeded complexity. **Clients** wanting to support older servers should: -1. Accept an MCP server URL from the user, which may point to either a server using the - old transport or the new transport. -2. Attempt to POST an `InitializeRequest` to the server URL, with an `Accept` header as +1. Accept an MCP server URL from the user, which may point to either a server + using the old transport or the new transport. +2. Attempt to POST a request to the server URL, with an `Accept` header as defined above: - - If it succeeds, the client can assume this is a server supporting the new Streamable - HTTP transport. - - If it fails with the following HTTP status codes "400 Bad Request", "404 Not - Found", or "405 Method Not Allowed": - - Issue a GET request to the server URL, expecting that this will open an SSE stream - and return an `endpoint` event as the first event. - - When the `endpoint` event arrives, the client can assume this is a server running - the old HTTP+SSE transport, and should use that transport for all subsequent - communication. + - If it succeeds, the client can assume this is a server supporting the + new Streamable HTTP transport. + - If it fails with HTTP status codes "400 Bad Request", "404 Not Found", + or "405 Method Not Allowed": + - Issue a GET request to the server URL, expecting that this will open + an SSE stream and return an `endpoint` event as the first event. + - When the `endpoint` event arrives, the client can assume this is a + server running the old HTTP+SSE transport, and should use that + transport for all subsequent communication. + +[lifecycle-compat]: /specification/draft/basic/lifecycle#backward-compatibility-with-initialization-based-versions +[http-sse]: /specification/2024-11-05/basic/transports#http-with-sse ## Custom Transports -Clients and servers **MAY** implement additional custom transport mechanisms to suit -their specific needs. The protocol is transport-agnostic and can be implemented over any -communication channel that supports bidirectional message exchange. +Clients and servers **MAY** implement additional custom transport mechanisms +to suit their specific needs. The protocol is transport-agnostic and can be +implemented over any communication channel that supports bidirectional +message exchange. -Implementers who choose to support custom transports **MUST** ensure they preserve the -JSON-RPC message format and lifecycle requirements defined by MCP. Custom transports -**SHOULD** document their specific connection establishment and message exchange patterns -to aid interoperability. +Implementers who choose to support custom transports **MUST** ensure they +preserve the JSON-RPC message format. Custom transports **SHOULD** document +their specific connection establishment and message exchange patterns to aid +interoperability. From 6692e2b7ab02e8f8139a8b8667f1346d553760e9 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:15:50 -0600 Subject: [PATCH 44/69] docs: update index.mdx for stateless model (SEP-2575) - Update Lifecycle Management description to reflect per-request model - Add per-request protocol fields table to _meta section --- docs/specification/draft/basic/index.mdx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/specification/draft/basic/index.mdx b/docs/specification/draft/basic/index.mdx index 1d69d197f..37e9cd18d 100644 --- a/docs/specification/draft/basic/index.mdx +++ b/docs/specification/draft/basic/index.mdx @@ -7,7 +7,7 @@ title: Overview The Model Context Protocol consists of several key components that work together: - **Base Protocol**: Core JSON-RPC message types -- **Lifecycle Management**: Connection initialization and capability negotiation +- **Lifecycle Management**: Protocol version negotiation and per-request capability declaration - **Authorization**: Authentication and authorization framework for HTTP-based transports - **Server Features**: Resources, prompts, and tools exposed by servers - **Client Features**: Sampling and root directory lists provided by clients @@ -225,6 +225,27 @@ may reserve particular names for purpose-specific metadata, as declared in those - Unless empty, MUST begin and end with an alphanumeric character (`[a-z0-9A-Z]`). - MAY contain hyphens (`-`), underscores (`_`), dots (`.`), and alphanumerics in between. +**Per-request protocol fields:** + +Every client request **MUST** include the following `io.modelcontextprotocol/*` fields +in `_meta`. Servers use these to identify the client and the protocol version in use +without relying on any prior connection state. See +[Lifecycle][lifecycle] for version negotiation rules. + +| Key | Type | Required | Description | +| -------------------------------------------- | -------------------- | -------- | ----------------------------------------------------------- | +| `io.modelcontextprotocol/protocolVersion` | `string` | Yes | Protocol version for this request (e.g., `"DRAFT-2026-v1"`) | +| `io.modelcontextprotocol/clientInfo` | `Implementation` | Yes | Client name and version | +| `io.modelcontextprotocol/clientCapabilities` | `ClientCapabilities` | Yes | Client capabilities relevant to this request | +| `io.modelcontextprotocol/logLevel` | `LoggingLevel` | No | Minimum log level the server should emit for this request | + +On notifications delivered via a [`subscriptions/listen`][subscriptions-listen] stream, +the server **MUST** include `io.modelcontextprotocol/subscriptionId` in `_meta` so the +client can correlate the notification with the originating subscription request. + +[lifecycle]: /specification/draft/basic/lifecycle +[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen + **OpenTelemetry trace context:** As an exception to the prefix requirement above, the keys `traceparent`, `tracestate`, and From cee8188ce0d9b7ff56d23542a248a6585251754d Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:20:12 -0600 Subject: [PATCH 45/69] docs: update cancellation.mdx for stateless model (SEP-2575) - Remove initialize cancellation restriction (initialize no longer exists) - Add Transport-Specific Cancellation section: HTTP uses stream close, stdio uses notifications/cancelled --- .../draft/basic/utilities/cancellation.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/specification/draft/basic/utilities/cancellation.mdx b/docs/specification/draft/basic/utilities/cancellation.mdx index 1ac449d5f..6c4d8e101 100644 --- a/docs/specification/draft/basic/utilities/cancellation.mdx +++ b/docs/specification/draft/basic/utilities/cancellation.mdx @@ -27,12 +27,21 @@ notification containing: } ``` +## Transport-Specific Cancellation + +How a client signals cancellation depends on the transport: + +- **Streamable HTTP**: Closing the SSE response stream is the cancellation signal. + The server **MUST** treat a client disconnect as cancellation of that request. No + `notifications/cancelled` message is required or expected. +- **stdio**: There is no per-request stream to close. The client **MUST** send a + `notifications/cancelled` notification referencing the request ID. + ## Behavior Requirements 1. Cancellation notifications **MUST** only reference requests that: - Were previously issued in the same direction - Are believed to still be in-progress -1. The `initialize` request **MUST NOT** be cancelled by clients 1. For [task-augmented requests](./tasks), the `tasks/cancel` request **MUST** be used instead of the `notifications/cancelled` notification. Tasks have their own dedicated cancellation mechanism that returns the final task state. 1. Receivers of cancellation notifications **SHOULD**: - Stop processing the cancelled request From 1b04197b8ca7f713393df7784b7fd6a0b7b8cf8b Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:22:31 -0600 Subject: [PATCH 46/69] docs: remove ping from draft spec (SEP-2575) --- docs/docs.json | 1 - .../draft/basic/utilities/ping.mdx | 80 ------------------- 2 files changed, 81 deletions(-) delete mode 100644 docs/specification/draft/basic/utilities/ping.mdx diff --git a/docs/docs.json b/docs/docs.json index 2fa4a8b09..d0d80a2b9 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -322,7 +322,6 @@ "group": "Utilities", "pages": [ "specification/draft/basic/utilities/cancellation", - "specification/draft/basic/utilities/ping", "specification/draft/basic/utilities/progress", "specification/draft/basic/utilities/tasks", "specification/draft/basic/utilities/mrtr" diff --git a/docs/specification/draft/basic/utilities/ping.mdx b/docs/specification/draft/basic/utilities/ping.mdx deleted file mode 100644 index c0a2281fa..000000000 --- a/docs/specification/draft/basic/utilities/ping.mdx +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: Ping ---- - -
- -The Model Context Protocol includes a ping mechanism that either party may use -to verify that their counterpart is still responsive and the connection is alive. - -## Overview - -The ping functionality is implemented through a simple request/response pattern. Either -the client or server can initiate a ping by sending a `ping` request. - - - -`ping` is an MCP-level liveness check and **MAY** be sent by either party at any -time on an established connection. - -In Streamable HTTP, implementations **SHOULD** prefer transport-level SSE -keepalive mechanisms for idle-connection maintenance; `ping` remains available -for protocol-level responsiveness checks. - -Request-association requirements for `roots/list`, -`sampling/createMessage`, and `elicitation/create` do not apply to `ping`. - - - -## Message Format - -A ping request is a standard JSON-RPC request with no parameters: - -```json -{ - "jsonrpc": "2.0", - "id": "123", - "method": "ping" -} -``` - -## Behavior Requirements - -1. The receiver **MUST** respond promptly with an empty response: - -```json -{ - "jsonrpc": "2.0", - "id": "123", - "result": {} -} -``` - -2. If no response is received within a reasonable timeout period, the sender **MAY**: - - Consider the connection stale - - Terminate the connection - - Attempt reconnection procedures - -## Usage Patterns - -```mermaid -sequenceDiagram - participant Sender - participant Receiver - - Sender->>Receiver: ping request - Receiver->>Sender: empty response -``` - -## Implementation Considerations - -- Implementations **SHOULD** periodically issue pings to detect connection health -- The frequency of pings **SHOULD** be configurable -- Timeouts **SHOULD** be appropriate for the network environment -- Excessive pinging **SHOULD** be avoided to reduce network overhead - -## Error Handling - -- Timeouts **SHOULD** be treated as connection failures -- Multiple failed pings **MAY** trigger connection reset -- Implementations **SHOULD** log ping failures for diagnostics From 066fb1d1a83994752f4be7cf42c0c74eed2221f9 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:27:17 -0600 Subject: [PATCH 47/69] docs: replace resource subscribe with subscriptions/listen (SEP-2575) --- docs/specification/draft/server/resources.mdx | 81 ++++++++----------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/docs/specification/draft/server/resources.mdx b/docs/specification/draft/server/resources.mdx index 60be181bb..5e4ff8e73 100644 --- a/docs/specification/draft/server/resources.mdx +++ b/docs/specification/draft/server/resources.mdx @@ -35,47 +35,23 @@ Servers that support resources **MUST** declare the `resources` capability: { "capabilities": { "resources": { - "subscribe": true, "listChanged": true } } } ``` -The capability supports two optional features: +The capability supports one optional feature: -- `subscribe`: whether the client can subscribe to be notified of changes to individual - resources. - `listChanged`: whether the server will emit notifications when the list of available resources changes. -Both `subscribe` and `listChanged` are optional—servers can support neither, -either, or both: +`listChanged` is optional—servers may omit it: ```json { "capabilities": { - "resources": {} // Neither feature supported - } -} -``` - -```json -{ - "capabilities": { - "resources": { - "subscribe": true // Only subscriptions supported - } - } -} -``` - -```json -{ - "capabilities": { - "resources": { - "listChanged": true // Only list change notifications supported - } + "resources": {} } } ``` @@ -242,49 +218,62 @@ capability **SHOULD** send a notification: ### Subscriptions -The protocol supports optional subscriptions to resource changes. Clients can subscribe -to specific resources and receive notifications when they change: +Clients can subscribe to change notifications for specific resources using +[`subscriptions/listen`](/specification/draft/schema#subscriptionslistenrequest). +The request opens a long-lived stream; the server delivers +`notifications/resources/updated` on that stream whenever a watched resource changes. -**Subscribe Request:** +**Listen request:** ```json { "jsonrpc": "2.0", "id": 4, - "method": "resources/subscribe", + "method": "subscriptions/listen", "params": { - "uri": "file:///project/src/main.rs" + "notifications": { + "resourceSubscriptions": ["file:///project/src/main.rs"] + } } } ``` -**Update Notification:** +The server **MUST** send a `notifications/subscriptions/acknowledged` notification +to confirm the stream is active before sending any other notifications on it: ```json { "jsonrpc": "2.0", - "method": "notifications/resources/updated", + "method": "notifications/subscriptions/acknowledged", "params": { - "uri": "file:///project/src/main.rs" + "_meta": { + "io.modelcontextprotocol/subscriptionId": "4" + }, + "notifications": { + "resourceSubscriptions": ["file:///project/src/main.rs"] + } } } ``` -**Unsubscribe Request:** +**Update notification** (delivered on the stream): ```json { "jsonrpc": "2.0", - "id": 5, - "method": "resources/unsubscribe", + "method": "notifications/resources/updated", "params": { + "_meta": { + "io.modelcontextprotocol/subscriptionId": "4" + }, "uri": "file:///project/src/main.rs" } } ``` -Clients **SHOULD** send `resources/unsubscribe` when they no longer need updates -for a resource. +To end the subscription, close the stream (HTTP) or send +`notifications/cancelled` referencing the listen request ID (stdio). See +[Cancellation](/specification/draft/basic/utilities/cancellation) for details. ## Message Flow @@ -305,18 +294,14 @@ sequenceDiagram Client->>Server: resources/read Server-->>Client: Resource contents - Note over Client,Server: Subscriptions - Client->>Server: resources/subscribe - Server-->>Client: Subscription confirmed + Note over Client,Server: Subscribe to changes + Client->>Server: subscriptions/listen + Server--)Client: notifications/subscriptions/acknowledged - Note over Client,Server: Updates + Note over Client,Server: Resource updated Server--)Client: notifications/resources/updated Client->>Server: resources/read Server-->>Client: Updated contents - - Note over Client,Server: Unsubscribe - Client->>Server: resources/unsubscribe - Server-->>Client: Unsubscribe confirmed ``` ## Data Types From a5005052ed18cad94434a643806a5821d25bc481 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:34:37 -0600 Subject: [PATCH 48/69] docs: add subscriptions/listen page and update resources/logging (SEP-2575) - New basic/utilities/subscriptions.mdx: full subscriptions/listen mechanics - resources.mdx: trim subscriptions section to cross-link subscriptions page - logging.mdx: remove logging/setLevel; document per-request logLevel and subscriptions/listen - docs.json: add subscriptions to draft nav --- docs/docs.json | 1 + .../draft/basic/utilities/subscriptions.mdx | 111 ++++++++++++++++++ docs/specification/draft/server/resources.mdx | 58 ++------- .../draft/server/utilities/logging.mdx | 63 ++++------ 4 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 docs/specification/draft/basic/utilities/subscriptions.mdx diff --git a/docs/docs.json b/docs/docs.json index d0d80a2b9..c821ffbc4 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -323,6 +323,7 @@ "pages": [ "specification/draft/basic/utilities/cancellation", "specification/draft/basic/utilities/progress", + "specification/draft/basic/utilities/subscriptions", "specification/draft/basic/utilities/tasks", "specification/draft/basic/utilities/mrtr" ] diff --git a/docs/specification/draft/basic/utilities/subscriptions.mdx b/docs/specification/draft/basic/utilities/subscriptions.mdx new file mode 100644 index 000000000..2f0d9c7e8 --- /dev/null +++ b/docs/specification/draft/basic/utilities/subscriptions.mdx @@ -0,0 +1,111 @@ +--- +title: Subscriptions +--- + +
+ +`subscriptions/listen` opens a long-lived notification stream from the server to the +client. Unlike one-off requests, the stream stays open and delivers notifications until +the client cancels it. It replaces the former `resources/subscribe` RPC and the HTTP GET +endpoint. + +## Opening a Stream + +The client sends a `subscriptions/listen` request with a `notifications` filter +specifying which event types it wants to receive. The server **MUST NOT** send +notification types the client has not explicitly requested. + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "subscriptions/listen", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1", + "io.modelcontextprotocol/clientInfo": { + "name": "ExampleClient", + "version": "1.0.0" + }, + "io.modelcontextprotocol/clientCapabilities": {} + }, + "notifications": { + "toolsListChanged": true, + "resourceSubscriptions": ["file:///project/config.json"], + "logLevel": "info" + } + } +} +``` + +### Notification Filter + +| Field | Type | Description | +| ----------------------- | -------------- | ----------------------------------------------------------------- | +| `toolsListChanged` | `boolean` | Receive `notifications/tools/list_changed` when tools change | +| `promptsListChanged` | `boolean` | Receive `notifications/prompts/list_changed` when prompts change | +| `resourcesListChanged` | `boolean` | Receive `notifications/resources/list_changed` when list changes | +| `resourceSubscriptions` | `string[]` | Receive `notifications/resources/updated` for these resource URIs | +| `logLevel` | `LoggingLevel` | Receive `notifications/message` at or above this severity level | + +All fields are optional. Omitting a field is equivalent to not subscribing to that +notification type. + +## Acknowledgment + +The server **MUST** send `notifications/subscriptions/acknowledged` as the first +message on the stream. The `notifications` field in the acknowledgment reflects the +subset the server agreed to honor — notification types the server does not support are +omitted. + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/subscriptions/acknowledged", + "params": { + "_meta": { + "io.modelcontextprotocol/subscriptionId": "1" + }, + "notifications": { + "toolsListChanged": true, + "resourceSubscriptions": ["file:///project/config.json"], + "logLevel": "info" + } + } +} +``` + +The client **SHOULD** check the acknowledged filter against what it requested and handle +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 +share a single channel, clients **MUST** use this field to correlate notifications +with their originating subscription. + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/resources/updated", + "params": { + "_meta": { + "io.modelcontextprotocol/subscriptionId": "1" + }, + "uri": "file:///project/config.json" + } +} +``` + +## Cancellation + +To end a subscription: + +- **Streamable HTTP**: Close the SSE response stream. +- **stdio**: Send `notifications/cancelled` referencing the listen request ID. + +See [Cancellation][cancellation] for the full rules. + +[cancellation]: /specification/draft/basic/utilities/cancellation diff --git a/docs/specification/draft/server/resources.mdx b/docs/specification/draft/server/resources.mdx index 5e4ff8e73..e6fabe937 100644 --- a/docs/specification/draft/server/resources.mdx +++ b/docs/specification/draft/server/resources.mdx @@ -218,62 +218,28 @@ capability **SHOULD** send a notification: ### Subscriptions -Clients can subscribe to change notifications for specific resources using -[`subscriptions/listen`](/specification/draft/schema#subscriptionslistenrequest). -The request opens a long-lived stream; the server delivers -`notifications/resources/updated` on that stream whenever a watched resource changes. - -**Listen request:** - -```json -{ - "jsonrpc": "2.0", - "id": 4, - "method": "subscriptions/listen", - "params": { - "notifications": { - "resourceSubscriptions": ["file:///project/src/main.rs"] - } - } -} -``` - -The server **MUST** send a `notifications/subscriptions/acknowledged` notification -to confirm the stream is active before sending any other notifications on it: - -```json -{ - "jsonrpc": "2.0", - "method": "notifications/subscriptions/acknowledged", - "params": { - "_meta": { - "io.modelcontextprotocol/subscriptionId": "4" - }, - "notifications": { - "resourceSubscriptions": ["file:///project/src/main.rs"] - } - } -} -``` - -**Update notification** (delivered on the stream): +Clients subscribe to change notifications for specific resources by sending a +[`subscriptions/listen`][subscriptions-listen] request with the resource URIs listed in +`notifications.resourceSubscriptions`. The server delivers +`notifications/resources/updated` on the resulting stream whenever a watched resource +changes. ```json { "jsonrpc": "2.0", "method": "notifications/resources/updated", "params": { - "_meta": { - "io.modelcontextprotocol/subscriptionId": "4" - }, + "_meta": { "io.modelcontextprotocol/subscriptionId": "4" }, "uri": "file:///project/src/main.rs" } } ``` -To end the subscription, close the stream (HTTP) or send -`notifications/cancelled` referencing the listen request ID (stdio). See -[Cancellation](/specification/draft/basic/utilities/cancellation) for details. +See [Subscriptions][subscriptions] for the full protocol mechanics (acknowledgment, +`subscriptionId` correlation, and cancellation). + +[subscriptions-listen]: /specification/draft/schema#subscriptionslistenrequest +[subscriptions]: /specification/draft/basic/utilities/subscriptions ## Message Flow @@ -295,7 +261,7 @@ sequenceDiagram Server-->>Client: Resource contents Note over Client,Server: Subscribe to changes - Client->>Server: subscriptions/listen + Client->>Server: subscriptions/listen (resourceSubscriptions) Server--)Client: notifications/subscriptions/acknowledged Note over Client,Server: Resource updated diff --git a/docs/specification/draft/server/utilities/logging.mdx b/docs/specification/draft/server/utilities/logging.mdx index 2722ec434..88574403a 100644 --- a/docs/specification/draft/server/utilities/logging.mdx +++ b/docs/specification/draft/server/utilities/logging.mdx @@ -5,9 +5,9 @@ title: Logging
The Model Context Protocol (MCP) provides a standardized way for servers to send -structured log messages to clients. Clients can control logging verbosity by setting -minimum log levels, with servers sending notifications containing severity levels, -optional logger names, and arbitrary JSON-serializable data. +structured log messages to clients. Clients control logging verbosity per-request via +`_meta`, with servers sending notifications containing severity levels, optional logger +names, and arbitrary JSON-serializable data. ## User Interaction Model @@ -42,24 +42,29 @@ The protocol follows the standard syslog severity levels specified in | alert | Action must be taken immediately | Data corruption detected | | emergency | System is unusable | Complete system failure | -## Protocol Messages +## Requesting Log Messages -### Setting Log Level +### Per-request log level -To configure the minimum log level, clients **MAY** send a `logging/setLevel` request: +To receive log messages for a specific request, include +`io.modelcontextprotocol/logLevel` in the request's `_meta`. The server **MUST NOT** +emit `notifications/message` for a request that does not include this field. -**Request:** +The server sends `notifications/message` notifications on the response stream at or +above the requested level before the final response. -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "logging/setLevel", - "params": { - "level": "info" - } -} -``` +### Long-lived log stream + +To receive a continuous stream of log messages, send a +[`subscriptions/listen`][subscriptions] request with `logLevel` set in the +`notifications` filter. The server delivers `notifications/message` on that stream +for the duration of the subscription. + +See [Subscriptions][subscriptions] for the full protocol mechanics. + +[subscriptions]: /specification/draft/basic/utilities/subscriptions + +## Protocol Messages ### Log Message Notifications @@ -83,34 +88,12 @@ Servers send log messages using `notifications/message` notifications: } ``` -## Message Flow - -```mermaid -sequenceDiagram - participant Client - participant Server - - Note over Client,Server: Configure Logging - Client->>Server: logging/setLevel (info) - Server-->>Client: Empty Result - - Note over Client,Server: Server Activity - Server--)Client: notifications/message (info) - Server--)Client: notifications/message (warning) - Server--)Client: notifications/message (error) - - Note over Client,Server: Level Change - Client->>Server: logging/setLevel (error) - Server-->>Client: Empty Result - Note over Server: Only sends error level
and above -``` - ## Error Handling Servers **SHOULD** return standard JSON-RPC errors for common failure cases: - Invalid log level: `-32602` (Invalid params) -- Configuration errors: `-32603` (Internal error) +- Internal errors: `-32603` (Internal error) ## Implementation Considerations From d962745be932bdad5ee54068ad1e83a21f874740 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:53:10 -0600 Subject: [PATCH 49/69] docs: update architecture.mdx for stateless model (SEP-2575) --- docs/docs/learn/architecture.mdx | 122 +++++++------------------------ 1 file changed, 25 insertions(+), 97 deletions(-) diff --git a/docs/docs/learn/architecture.mdx b/docs/docs/learn/architecture.mdx index 9a5ea2b3a..c6eba9613 100644 --- a/docs/docs/learn/architecture.mdx +++ b/docs/docs/learn/architecture.mdx @@ -84,7 +84,7 @@ Conceptually the data layer is the inner layer, while the transport layer is the The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics. This layer includes: -- **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers +- **Lifecycle management**: Handles protocol version negotiation and per-request capability declaration - **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client - **Client features**: Enables servers to ask the client to sample from the host LLM, elicit input from the user, and log messages to the client - **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations @@ -108,7 +108,7 @@ MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol #### Lifecycle management -MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/latest/basic/lifecycle), and the [example](#example) showcases the initialization sequence. +MCP is a stateless protocol: every request is self-contained and carries all the metadata the server needs to process it. Protocol version, client identity, and capabilities travel in the `_meta` field of each request rather than being negotiated once at connection start. Detailed information can be found in the [specification](/specification/draft/basic/lifecycle), and the [example](#example) demonstrates the per-request model. #### Primitives @@ -150,110 +150,34 @@ The protocol supports real-time notifications to enable dynamic updates between This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages. - + -MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features. +The client can discover available tools by sending a `tools/list` request. Every request carries a `_meta` field with the protocol version, client identity, and capabilities — there is no prior handshake. The server uses these fields to understand who it is talking to and which protocol version applies. - ```json Initialize Request + ```json Tools List Request { "jsonrpc": "2.0", "id": 1, - "method": "initialize", + "method": "tools/list", "params": { - "protocolVersion": "2025-06-18", - "capabilities": { - "elicitation": {} - }, - "clientInfo": { - "name": "example-client", - "version": "1.0.0" - } - } - } - ``` - ```json Initialize Response - { - "jsonrpc": "2.0", - "id": 1, - "result": { - "protocolVersion": "2025-06-18", - "capabilities": { - "tools": { - "listChanged": true + "_meta": { + "io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1", + "io.modelcontextprotocol/clientInfo": { + "name": "example-client", + "version": "1.0.0" }, - "resources": {} - }, - "serverInfo": { - "name": "example-server", - "version": "1.0.0" + "io.modelcontextprotocol/clientCapabilities": { + "elicitation": {} + } } } } ``` - - -#### Understanding the Initialization Exchange - -The initialization process is a key part of MCP's lifecycle management and serves several critical purposes: - -1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2025-06-18") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated. - -2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations. - -3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes. - -In this example, the capability negotiation demonstrates how MCP primitives are declared: - -**Client Capabilities**: - -- `"elicitation": {}` - The client declares it can work with user interaction requests (can receive `elicitation/create` method calls) - -**Server Capabilities**: - -- `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes -- `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods) - -After successful initialization, the client sends a notification to indicate it's ready: - -```json Notification -{ - "jsonrpc": "2.0", - "method": "notifications/initialized" -} -``` - -#### How This Works in AI Applications - -During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. - -```python Pseudo-code for AI application initialization -# Pseudo Code -async with stdio_client(server_config) as (read, write): - async with ClientSession(read, write) as session: - init_response = await session.initialize() - if init_response.capabilities.tools: - app.register_mcp_server(session, supports_tools=True) - app.set_server_ready(session) -``` - - - - -Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them. - - - ```json Tools List Request - { - "jsonrpc": "2.0", - "id": 2, - "method": "tools/list" - } - ``` ```json Tools List Response { "jsonrpc": "2.0", - "id": 2, + "id": 1, "result": { "tools": [ { @@ -300,7 +224,11 @@ Now that the connection is established, the client can discover available tools #### Understanding the Tool Discovery Request -The `tools/list` request is simple, containing no parameters. +The `_meta` field carries three required fields on every request: + +- **`io.modelcontextprotocol/protocolVersion`**: The protocol version the client is using (e.g., `"DRAFT-2026-v1"`). The server responds with an error if it doesn't support that version, listing the versions it does support. +- **`io.modelcontextprotocol/clientInfo`**: Client name and version for identification and debugging. +- **`io.modelcontextprotocol/clientCapabilities`**: The capabilities the client supports for this request (e.g., `"elicitation": {}` declares the client can handle `elicitation/create` calls). The server uses this to know which primitives it can use when responding. #### Understanding the Tool Discovery Response @@ -315,7 +243,7 @@ Each tool object in the response includes several key fields: #### How This Works in AI Applications -The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations. +The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations. Each request to the server includes the client's identity and capabilities, so the server can tailor its response without needing any prior connection state. ```python Pseudo-code for AI application tool discovery # Pseudo-code using MCP Python SDK patterns @@ -339,7 +267,7 @@ The `tools/call` request follows a structured format that ensures type safety an ```json Tool Call Request { "jsonrpc": "2.0", - "id": 3, + "id": 2, "method": "tools/call", "params": { "name": "weather_current", @@ -353,7 +281,7 @@ The `tools/call` request follows a structured format that ensures type safety an ```json Tool Call Response { "jsonrpc": "2.0", - "id": 3, + "id": 2, "result": { "content": [ { @@ -422,7 +350,7 @@ When the server's available tools change—such as when new functionality become 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent. -2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1). +2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability (as advertised in the `tools/list` response in Step 1). 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive. @@ -433,7 +361,7 @@ Upon receiving this notification, the client typically reacts by requesting the ```json Request { "jsonrpc": "2.0", - "id": 4, + "id": 3, "method": "tools/list" } ``` From db1112086076cab6ed1a89da60bf553e491cd7dc Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:55:01 -0600 Subject: [PATCH 50/69] docs: sweep guides for stateless model (SEP-2575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - versioning.mdx: per-request version negotiation via _meta, not initialization - server-concepts.mdx: resources/subscribe → subscriptions/listen in protocol table - authorization.mdx: remove Mcp-Session-Id hardening bullet; reframe as per-request token-based auth --- docs/docs/learn/server-concepts.mdx | 2 +- docs/docs/learn/versioning.mdx | 18 ++++++++++-------- docs/docs/tutorials/security/authorization.mdx | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/docs/learn/server-concepts.mdx b/docs/docs/learn/server-concepts.mdx index 9942ab550..4d7e668d6 100644 --- a/docs/docs/learn/server-concepts.mdx +++ b/docs/docs/learn/server-concepts.mdx @@ -117,7 +117,7 @@ Resource Templates include metadata such as title, description, and expected MIM | `resources/list` | List available direct resources | Array of resource descriptors | | `resources/templates/list` | Discover resource templates | Array of resource template definitions | | `resources/read` | Retrieve resource contents | Resource data with metadata | -| `resources/subscribe` | Monitor resource changes | Subscription confirmation | +| `subscriptions/listen` | Monitor resource changes | Long-lived notification stream | #### Example: Getting Travel Planning Context diff --git a/docs/docs/learn/versioning.mdx b/docs/docs/learn/versioning.mdx index 949631bbe..a6ae675cf 100644 --- a/docs/docs/learn/versioning.mdx +++ b/docs/docs/learn/versioning.mdx @@ -28,11 +28,13 @@ The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/) ## Negotiation -Version negotiation happens during -[initialization](/specification/latest/basic/lifecycle#initialization). Clients and -servers **MAY** support multiple protocol versions simultaneously, but they **MUST** -agree on a single version to use for the session. - -The protocol provides appropriate error handling if version negotiation fails, allowing -clients to gracefully terminate connections when they cannot find a version compatible -with the server. +Version negotiation is per-request: the client declares the protocol version it is using +in the `io.modelcontextprotocol/protocolVersion` field of each request's `_meta`. If the +server does not support that version, it responds with an +`UnsupportedProtocolVersionError` listing the versions it does support. The client can +then retry with a compatible version, or surface an error if no compatible version +exists. + +Clients and servers **MAY** support multiple protocol versions simultaneously. See the +[Lifecycle specification](/specification/draft/basic/lifecycle#protocol-version-negotiation) +for the full negotiation flow. diff --git a/docs/docs/tutorials/security/authorization.mdx b/docs/docs/tutorials/security/authorization.mdx index 4895907fe..d755e3e2f 100644 --- a/docs/docs/tutorials/security/authorization.mdx +++ b/docs/docs/tutorials/security/authorization.mdx @@ -1080,7 +1080,7 @@ For comprehensive security guidance, including attack vectors, mitigation strate - **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server. - **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server. - **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals. -- **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side. +- **Request identifier hardening**. Never tie authorization decisions to request or subscription identifiers. Authorization **MUST** be based solely on the bearer token presented on each request. ## Related Standards and Documentation From dcb52678d39c9ad73048ef1ced367ab8f22ff9fa Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 07:59:44 -0600 Subject: [PATCH 51/69] docs: fix stale initialization references in draft spec (SEP-2575) - client/roots, sampling, elicitation: capability declared in clientCapabilities per-request, not during initialization - server/prompts: capability declared in DiscoverResult, not during initialization - server/tools: remove broken #initialization link from server name note - architecture/index: rewrite Capability Negotiation section for per-request model - basic/utilities/tasks: update capability declaration and negotiation prose --- docs/specification/draft/architecture/index.mdx | 11 ++++++----- docs/specification/draft/basic/utilities/tasks.mdx | 4 ++-- docs/specification/draft/client/elicitation.mdx | 4 ++-- docs/specification/draft/client/roots.mdx | 4 ++-- docs/specification/draft/client/sampling.mdx | 4 ++-- docs/specification/draft/server/prompts.mdx | 4 ++-- docs/specification/draft/server/tools.mdx | 6 ++---- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/specification/draft/architecture/index.mdx b/docs/specification/draft/architecture/index.mdx index a1f886477..2168633f3 100644 --- a/docs/specification/draft/architecture/index.mdx +++ b/docs/specification/draft/architecture/index.mdx @@ -114,13 +114,14 @@ implementation: ## Capability Negotiation The Model Context Protocol uses a capability-based negotiation system where clients and -servers explicitly declare their supported features during initialization. Capabilities -determine which protocol features and primitives are available during a connection. +servers declare their supported features on each request. Clients include their +capabilities in `_meta.io.modelcontextprotocol/clientCapabilities` on every request; +servers advertise their capabilities via [`server/discover`](/specification/draft/schema#discoverrequest). -- Servers declare capabilities like resource subscriptions, tool support, and prompt +- Servers declare capabilities like tool support, resource subscriptions, and prompt templates -- Clients declare capabilities like sampling support and notification handling -- Both parties must respect declared capabilities throughout the connection +- Clients declare capabilities like sampling support and elicitation handling +- Both parties must respect declared capabilities throughout the interaction - Additional capabilities can be negotiated through extensions to the protocol ```mermaid diff --git a/docs/specification/draft/basic/utilities/tasks.mdx b/docs/specification/draft/basic/utilities/tasks.mdx index 8265b0690..6b13aa890 100644 --- a/docs/specification/draft/basic/utilities/tasks.mdx +++ b/docs/specification/draft/basic/utilities/tasks.mdx @@ -32,7 +32,7 @@ Implementations are free to expose tasks through any interface pattern that suit ## Capabilities -Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability during initialization. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation. +Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability. Servers include it in their [`DiscoverResult`](/specification/draft/schema#discoverresult); clients include it in `_meta.io.modelcontextprotocol/clientCapabilities` on each request. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation. ### Server Capabilities @@ -92,7 +92,7 @@ Clients declare if they support tasks, and if so, which client-side requests can ### Capability Negotiation -During the initialization phase, both parties exchange their `tasks` capabilities to establish which operations support task-based execution. Requestors **SHOULD** only augment requests with a task if the corresponding capability has been declared by the receiver. +Requestors **SHOULD** only augment requests with a task if the corresponding capability has been declared by the receiver. Clients discover server `tasks` capabilities via `server/discover`; servers learn client `tasks` capabilities from `clientCapabilities` in each request's `_meta`. For example, if a server's capabilities include `tasks.requests.tools.call: {}`, then clients may augment `tools/call` requests with a task. If a client's capabilities include `tasks.requests.sampling.createMessage: {}`, then servers may augment `sampling/createMessage` requests with a task. diff --git a/docs/specification/draft/client/elicitation.mdx b/docs/specification/draft/client/elicitation.mdx index 4fda5275b..a76cb31ff 100644 --- a/docs/specification/draft/client/elicitation.mdx +++ b/docs/specification/draft/client/elicitation.mdx @@ -64,8 +64,8 @@ to support this pattern. ## Capabilities -Clients that support elicitation **MUST** declare the `elicitation` capability during -[initialization](../basic/lifecycle#initialization): +Clients that support elicitation **MUST** declare the `elicitation` capability in +`_meta.io.modelcontextprotocol/clientCapabilities` on each request: ```json { diff --git a/docs/specification/draft/client/roots.mdx b/docs/specification/draft/client/roots.mdx index b3a9f66eb..dbe68a121 100644 --- a/docs/specification/draft/client/roots.mdx +++ b/docs/specification/draft/client/roots.mdx @@ -39,8 +39,8 @@ to support this pattern. ## Capabilities -Clients that support roots **MUST** declare the `roots` capability during -[initialization](/specification/draft/basic/lifecycle#initialization): +Clients that support roots **MUST** declare the `roots` capability in +`_meta.io.modelcontextprotocol/clientCapabilities` on each request: ```json { diff --git a/docs/specification/draft/client/sampling.mdx b/docs/specification/draft/client/sampling.mdx index c9dd98514..5ea980b24 100644 --- a/docs/specification/draft/client/sampling.mdx +++ b/docs/specification/draft/client/sampling.mdx @@ -55,8 +55,8 @@ Clients **MUST** declare support for tool use via the `sampling.tools` capabilit ## Capabilities -Clients that support sampling **MUST** declare the `sampling` capability during -[initialization](/specification/draft/basic/lifecycle#initialization): +Clients that support sampling **MUST** declare the `sampling` capability in +`_meta.io.modelcontextprotocol/clientCapabilities` on each request: **Basic sampling:** diff --git a/docs/specification/draft/server/prompts.mdx b/docs/specification/draft/server/prompts.mdx index d29f35f93..d966196ce 100644 --- a/docs/specification/draft/server/prompts.mdx +++ b/docs/specification/draft/server/prompts.mdx @@ -29,8 +29,8 @@ model. ## Capabilities -Servers that support prompts **MUST** declare the `prompts` capability during -[initialization](/specification/draft/basic/lifecycle#initialization): +Servers that support prompts **MUST** declare the `prompts` capability in their +[`DiscoverResult`](/specification/draft/schema#discoverresult): ```json { diff --git a/docs/specification/draft/server/tools.mdx b/docs/specification/draft/server/tools.mdx index 4c52fe50c..717e57076 100644 --- a/docs/specification/draft/server/tools.mdx +++ b/docs/specification/draft/server/tools.mdx @@ -313,10 +313,8 @@ aggregate tools from multiple servers **MAY** encounter naming collisions (for example, two servers each exposing a `search` tool) and **SHOULD** implement a disambiguation strategy such as prefixing tool names with a server identifier. -The server `name` returned during -[initialization](/specification/draft/basic/lifecycle#initialization) is not -guaranteed to be unique across servers and **SHOULD NOT** be relied upon for -disambiguation. +The server `name` (from `serverInfo`) is not guaranteed to be unique across +servers and **SHOULD NOT** be relied upon for disambiguation. From b460206db7ad70df47954c1c5df8cafae5d119b5 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 08:37:26 -0600 Subject: [PATCH 52/69] chore: mark SEP-2575 as Accepted --- docs/docs.json | 8 +------- docs/seps/2575-stateless-mcp.mdx | 6 +++--- docs/seps/index.mdx | 5 ++--- seps/2575-stateless-mcp.md | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index c821ffbc4..2b96b6dc7 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -428,13 +428,7 @@ "group": "Accepted", "pages": [ "seps/2207-oidc-refresh-token-guidance", - "seps/2260-Require-Server-requests-to-be-associated-with-Client-requests" - ] - }, - { - "group": "Draft", - "pages": [ - "seps/2243-http-standardization", + "seps/2260-Require-Server-requests-to-be-associated-with-Client-requests", "seps/2575-stateless-mcp" ] }, diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index f24902503..71053ac35 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -5,8 +5,8 @@ description: "Make MCP Stateless" ---
- - Draft + + Accepted Standards Track @@ -17,7 +17,7 @@ description: "Make MCP Stateless" | ------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **SEP** | 2575 | | **Title** | Make MCP Stateless | -| **Status** | Draft | +| **Status** | Accepted | | **Type** | Standards Track | | **Created** | 2025-06-18 | | **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Mark Roth ([@markdroth](https://github.com/markdroth)), | diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index a8d62819d..a80929ad8 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -12,16 +12,15 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary +- **Accepted**: 3 - **Final**: 29 -- **Draft**: 1 - **Approved**: 1 -- **Accepted**: 2 ## All SEPs | SEP | Title | Status | Type | Created | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------- | ---------------- | ---------- | -| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Draft | Standards Track | 2025-06-18 | +| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Accepted | Standards Track | 2025-06-18 | | [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | | [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Approved | Standards Track | 2026-02-03 | | [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Accepted | Standards Track | 2026-02-16 | diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 562ab12ee..61c31f3e3 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -1,6 +1,6 @@ # SEP-2575: Make MCP Stateless -- **Status**: Draft +- **Status**: Accepted - **Type**: Standards Track - **Created**: 2025-06-18 - **Author(s)**: Jonathan Hefner (@jonathanhefner), Mark Roth (@markdroth), From dd1cdcfb29e9cf1a60a6598367dd4e418c320e27 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 16:03:38 -0600 Subject: [PATCH 53/69] revert: restore guide files to pre-SEP-2575 state --- docs/docs/learn/architecture.mdx | 122 ++++++++++++++---- docs/docs/learn/server-concepts.mdx | 2 +- docs/docs/learn/versioning.mdx | 18 ++- .../docs/tutorials/security/authorization.mdx | 2 +- 4 files changed, 107 insertions(+), 37 deletions(-) diff --git a/docs/docs/learn/architecture.mdx b/docs/docs/learn/architecture.mdx index c6eba9613..9a5ea2b3a 100644 --- a/docs/docs/learn/architecture.mdx +++ b/docs/docs/learn/architecture.mdx @@ -84,7 +84,7 @@ Conceptually the data layer is the inner layer, while the transport layer is the The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics. This layer includes: -- **Lifecycle management**: Handles protocol version negotiation and per-request capability declaration +- **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers - **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client - **Client features**: Enables servers to ask the client to sample from the host LLM, elicit input from the user, and log messages to the client - **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations @@ -108,7 +108,7 @@ MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol #### Lifecycle management -MCP is a stateless protocol: every request is self-contained and carries all the metadata the server needs to process it. Protocol version, client identity, and capabilities travel in the `_meta` field of each request rather than being negotiated once at connection start. Detailed information can be found in the [specification](/specification/draft/basic/lifecycle), and the [example](#example) demonstrates the per-request model. +MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](/specification/latest/basic/lifecycle), and the [example](#example) showcases the initialization sequence. #### Primitives @@ -150,34 +150,110 @@ The protocol supports real-time notifications to enable dynamic updates between This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages. - + -The client can discover available tools by sending a `tools/list` request. Every request carries a `_meta` field with the protocol version, client identity, and capabilities — there is no prior handshake. The server uses these fields to understand who it is talking to and which protocol version applies. +MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features. - ```json Tools List Request + ```json Initialize Request { "jsonrpc": "2.0", "id": 1, - "method": "tools/list", + "method": "initialize", "params": { - "_meta": { - "io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1", - "io.modelcontextprotocol/clientInfo": { - "name": "example-client", - "version": "1.0.0" + "protocolVersion": "2025-06-18", + "capabilities": { + "elicitation": {} + }, + "clientInfo": { + "name": "example-client", + "version": "1.0.0" + } + } + } + ``` + ```json Initialize Response + { + "jsonrpc": "2.0", + "id": 1, + "result": { + "protocolVersion": "2025-06-18", + "capabilities": { + "tools": { + "listChanged": true }, - "io.modelcontextprotocol/clientCapabilities": { - "elicitation": {} - } + "resources": {} + }, + "serverInfo": { + "name": "example-server", + "version": "1.0.0" } } } ``` + + +#### Understanding the Initialization Exchange + +The initialization process is a key part of MCP's lifecycle management and serves several critical purposes: + +1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., "2025-06-18") ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated. + +2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](#notifications). This enables efficient communication by avoiding unsupported operations. + +3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes. + +In this example, the capability negotiation demonstrates how MCP primitives are declared: + +**Client Capabilities**: + +- `"elicitation": {}` - The client declares it can work with user interaction requests (can receive `elicitation/create` method calls) + +**Server Capabilities**: + +- `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes +- `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods) + +After successful initialization, the client sends a notification to indicate it's ready: + +```json Notification +{ + "jsonrpc": "2.0", + "method": "notifications/initialized" +} +``` + +#### How This Works in AI Applications + +During initialization, the AI application's MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. + +```python Pseudo-code for AI application initialization +# Pseudo Code +async with stdio_client(server_config) as (read, write): + async with ClientSession(read, write) as session: + init_response = await session.initialize() + if init_response.capabilities.tools: + app.register_mcp_server(session, supports_tools=True) + app.set_server_ready(session) +``` + + + + +Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them. + + + ```json Tools List Request + { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list" + } + ``` ```json Tools List Response { "jsonrpc": "2.0", - "id": 1, + "id": 2, "result": { "tools": [ { @@ -224,11 +300,7 @@ The client can discover available tools by sending a `tools/list` request. Every #### Understanding the Tool Discovery Request -The `_meta` field carries three required fields on every request: - -- **`io.modelcontextprotocol/protocolVersion`**: The protocol version the client is using (e.g., `"DRAFT-2026-v1"`). The server responds with an error if it doesn't support that version, listing the versions it does support. -- **`io.modelcontextprotocol/clientInfo`**: Client name and version for identification and debugging. -- **`io.modelcontextprotocol/clientCapabilities`**: The capabilities the client supports for this request (e.g., `"elicitation": {}` declares the client can handle `elicitation/create` calls). The server uses this to know which primitives it can use when responding. +The `tools/list` request is simple, containing no parameters. #### Understanding the Tool Discovery Response @@ -243,7 +315,7 @@ Each tool object in the response includes several key fields: #### How This Works in AI Applications -The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations. Each request to the server includes the client's identity and capabilities, so the server can tailor its response without needing any prior connection state. +The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations. ```python Pseudo-code for AI application tool discovery # Pseudo-code using MCP Python SDK patterns @@ -267,7 +339,7 @@ The `tools/call` request follows a structured format that ensures type safety an ```json Tool Call Request { "jsonrpc": "2.0", - "id": 2, + "id": 3, "method": "tools/call", "params": { "name": "weather_current", @@ -281,7 +353,7 @@ The `tools/call` request follows a structured format that ensures type safety an ```json Tool Call Response { "jsonrpc": "2.0", - "id": 2, + "id": 3, "result": { "content": [ { @@ -350,7 +422,7 @@ When the server's available tools change—such as when new functionality become 1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent. -2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability (as advertised in the `tools/list` response in Step 1). +2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1). 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive. @@ -361,7 +433,7 @@ Upon receiving this notification, the client typically reacts by requesting the ```json Request { "jsonrpc": "2.0", - "id": 3, + "id": 4, "method": "tools/list" } ``` diff --git a/docs/docs/learn/server-concepts.mdx b/docs/docs/learn/server-concepts.mdx index 4d7e668d6..9942ab550 100644 --- a/docs/docs/learn/server-concepts.mdx +++ b/docs/docs/learn/server-concepts.mdx @@ -117,7 +117,7 @@ Resource Templates include metadata such as title, description, and expected MIM | `resources/list` | List available direct resources | Array of resource descriptors | | `resources/templates/list` | Discover resource templates | Array of resource template definitions | | `resources/read` | Retrieve resource contents | Resource data with metadata | -| `subscriptions/listen` | Monitor resource changes | Long-lived notification stream | +| `resources/subscribe` | Monitor resource changes | Subscription confirmation | #### Example: Getting Travel Planning Context diff --git a/docs/docs/learn/versioning.mdx b/docs/docs/learn/versioning.mdx index a6ae675cf..949631bbe 100644 --- a/docs/docs/learn/versioning.mdx +++ b/docs/docs/learn/versioning.mdx @@ -28,13 +28,11 @@ The **current** protocol version is [**2025-11-25**](/specification/2025-11-25/) ## Negotiation -Version negotiation is per-request: the client declares the protocol version it is using -in the `io.modelcontextprotocol/protocolVersion` field of each request's `_meta`. If the -server does not support that version, it responds with an -`UnsupportedProtocolVersionError` listing the versions it does support. The client can -then retry with a compatible version, or surface an error if no compatible version -exists. - -Clients and servers **MAY** support multiple protocol versions simultaneously. See the -[Lifecycle specification](/specification/draft/basic/lifecycle#protocol-version-negotiation) -for the full negotiation flow. +Version negotiation happens during +[initialization](/specification/latest/basic/lifecycle#initialization). Clients and +servers **MAY** support multiple protocol versions simultaneously, but they **MUST** +agree on a single version to use for the session. + +The protocol provides appropriate error handling if version negotiation fails, allowing +clients to gracefully terminate connections when they cannot find a version compatible +with the server. diff --git a/docs/docs/tutorials/security/authorization.mdx b/docs/docs/tutorials/security/authorization.mdx index d755e3e2f..4895907fe 100644 --- a/docs/docs/tutorials/security/authorization.mdx +++ b/docs/docs/tutorials/security/authorization.mdx @@ -1080,7 +1080,7 @@ For comprehensive security guidance, including attack vectors, mitigation strate - **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server. - **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server. - **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals. -- **Request identifier hardening**. Never tie authorization decisions to request or subscription identifiers. Authorization **MUST** be based solely on the bearer token presented on each request. +- **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side. ## Related Standards and Documentation From 4f0f786649a79cfecf6dde081742c44ef0ea04c1 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 16:16:41 -0600 Subject: [PATCH 54/69] fix: remove logLevel from subscriptions/listen filter --- docs/seps/2575-stateless-mcp.mdx | 7 ------- .../draft/basic/utilities/subscriptions.mdx | 19 ++++++++----------- .../draft/server/utilities/logging.mdx | 11 ----------- seps/2575-stateless-mcp.md | 7 ------- 4 files changed, 8 insertions(+), 36 deletions(-) diff --git a/docs/seps/2575-stateless-mcp.mdx b/docs/seps/2575-stateless-mcp.mdx index 71053ac35..9fd65b8d4 100644 --- a/docs/seps/2575-stateless-mcp.mdx +++ b/docs/seps/2575-stateless-mcp.mdx @@ -465,12 +465,6 @@ export interface SubscriptionsListenRequest extends Request { * resource URIs. Replaces the resources/subscribe RPC. */ resourceSubscriptions?: string[]; - - /** - * If set, receive notifications/message at or above this level. - * If absent, no log messages are sent on this stream. - */ - logLevel?: LoggingLevel; }; }; } @@ -511,7 +505,6 @@ export interface SubscriptionsAcknowledgedNotification extends Notification { promptsListChanged?: boolean; resourcesListChanged?: boolean; resourceSubscriptions?: string[]; - logLevel?: LoggingLevel; }; }; } diff --git a/docs/specification/draft/basic/utilities/subscriptions.mdx b/docs/specification/draft/basic/utilities/subscriptions.mdx index 2f0d9c7e8..12730a9e8 100644 --- a/docs/specification/draft/basic/utilities/subscriptions.mdx +++ b/docs/specification/draft/basic/utilities/subscriptions.mdx @@ -31,8 +31,7 @@ notification types the client has not explicitly requested. }, "notifications": { "toolsListChanged": true, - "resourceSubscriptions": ["file:///project/config.json"], - "logLevel": "info" + "resourceSubscriptions": ["file:///project/config.json"] } } } @@ -40,13 +39,12 @@ notification types the client has not explicitly requested. ### Notification Filter -| Field | Type | Description | -| ----------------------- | -------------- | ----------------------------------------------------------------- | -| `toolsListChanged` | `boolean` | Receive `notifications/tools/list_changed` when tools change | -| `promptsListChanged` | `boolean` | Receive `notifications/prompts/list_changed` when prompts change | -| `resourcesListChanged` | `boolean` | Receive `notifications/resources/list_changed` when list changes | -| `resourceSubscriptions` | `string[]` | Receive `notifications/resources/updated` for these resource URIs | -| `logLevel` | `LoggingLevel` | Receive `notifications/message` at or above this severity level | +| Field | Type | Description | +| ----------------------- | ---------- | ----------------------------------------------------------------- | +| `toolsListChanged` | `boolean` | Receive `notifications/tools/list_changed` when tools change | +| `promptsListChanged` | `boolean` | Receive `notifications/prompts/list_changed` when prompts change | +| `resourcesListChanged` | `boolean` | Receive `notifications/resources/list_changed` when list changes | +| `resourceSubscriptions` | `string[]` | Receive `notifications/resources/updated` for these resource URIs | All fields are optional. Omitting a field is equivalent to not subscribing to that notification type. @@ -68,8 +66,7 @@ omitted. }, "notifications": { "toolsListChanged": true, - "resourceSubscriptions": ["file:///project/config.json"], - "logLevel": "info" + "resourceSubscriptions": ["file:///project/config.json"] } } } diff --git a/docs/specification/draft/server/utilities/logging.mdx b/docs/specification/draft/server/utilities/logging.mdx index 88574403a..5cbe4b5dc 100644 --- a/docs/specification/draft/server/utilities/logging.mdx +++ b/docs/specification/draft/server/utilities/logging.mdx @@ -53,17 +53,6 @@ emit `notifications/message` for a request that does not include this field. The server sends `notifications/message` notifications on the response stream at or above the requested level before the final response. -### Long-lived log stream - -To receive a continuous stream of log messages, send a -[`subscriptions/listen`][subscriptions] request with `logLevel` set in the -`notifications` filter. The server delivers `notifications/message` on that stream -for the duration of the subscription. - -See [Subscriptions][subscriptions] for the full protocol mechanics. - -[subscriptions]: /specification/draft/basic/utilities/subscriptions - ## Protocol Messages ### Log Message Notifications diff --git a/seps/2575-stateless-mcp.md b/seps/2575-stateless-mcp.md index 61c31f3e3..dd34d2dee 100644 --- a/seps/2575-stateless-mcp.md +++ b/seps/2575-stateless-mcp.md @@ -447,12 +447,6 @@ export interface SubscriptionsListenRequest extends Request { * resource URIs. Replaces the resources/subscribe RPC. */ resourceSubscriptions?: string[]; - - /** - * If set, receive notifications/message at or above this level. - * If absent, no log messages are sent on this stream. - */ - logLevel?: LoggingLevel; }; }; } @@ -493,7 +487,6 @@ export interface SubscriptionsAcknowledgedNotification extends Notification { promptsListChanged?: boolean; resourcesListChanged?: boolean; resourceSubscriptions?: string[]; - logLevel?: LoggingLevel; }; }; } From 3c66ee32fbe93e817243f4370035c491389c6fd4 Mon Sep 17 00:00:00 2001 From: kurtisvg <31518063+kurtisvg@users.noreply.github.com> Date: Fri, 8 May 2026 16:42:30 -0600 Subject: [PATCH 55/69] feat: update schema for stateless MCP (SEP-2575) --- docs/specification/draft/basic/index.mdx | 2 +- docs/specification/draft/basic/lifecycle.mdx | 2 +- docs/specification/draft/basic/transports.mdx | 4 +- docs/specification/draft/schema.mdx | 351 +++++----- .../tool-call-params-with-progress-token.json | 6 + .../server-discover-request.json | 5 + .../server-capabilities-discovery.json | 13 + .../discover-result-response.json | 16 + .../InitializeRequest/initialize-request.json | 42 -- .../full-client-capabilities.json | 37 -- .../full-server-capabilities.json | 40 -- .../InitializeResult/with-instructions.json | 12 - .../initialize-result-response.json | 44 -- .../initialized-notification.json | 4 - .../missing-elicitation-capability.json | 13 + .../examples/PingRequest/ping-request.json | 5 - .../ping-result-response.json | 5 - .../roots-list-changed.json | 4 - .../set-logging-level-request.json | 8 - .../set-log-level-to-info.json | 3 - .../set-logging-level-result-response.json | 5 - .../SubscribeRequest/subscribe-request.json | 8 - .../subscribe-to-file-resource.json | 3 - .../subscribe-result-response.json | 5 - .../listen-acknowledged.json | 13 + .../listen-for-list-changes.json | 19 + .../task-input-response-request.json | 6 + .../task-input-response-params.json | 6 + .../unsubscribe-request.json | 8 - .../unsubscribe-result-response.json | 5 - .../unsupported-version.json | 12 + schema/draft/schema.json | 627 ++++++++---------- schema/draft/schema.mdx | 36 +- schema/draft/schema.ts | 418 ++++++------ 34 files changed, 764 insertions(+), 1023 deletions(-) create mode 100644 schema/draft/examples/DiscoverRequest/server-discover-request.json create mode 100644 schema/draft/examples/DiscoverResult/server-capabilities-discovery.json create mode 100644 schema/draft/examples/DiscoverResultResponse/discover-result-response.json delete mode 100644 schema/draft/examples/InitializeRequest/initialize-request.json delete mode 100644 schema/draft/examples/InitializeRequestParams/full-client-capabilities.json delete mode 100644 schema/draft/examples/InitializeResult/full-server-capabilities.json delete mode 100644 schema/draft/examples/InitializeResult/with-instructions.json delete mode 100644 schema/draft/examples/InitializeResultResponse/initialize-result-response.json delete mode 100644 schema/draft/examples/InitializedNotification/initialized-notification.json create mode 100644 schema/draft/examples/MissingRequiredClientCapabilityError/missing-elicitation-capability.json delete mode 100644 schema/draft/examples/PingRequest/ping-request.json delete mode 100644 schema/draft/examples/PingResultResponse/ping-result-response.json delete mode 100644 schema/draft/examples/RootsListChangedNotification/roots-list-changed.json delete mode 100644 schema/draft/examples/SetLevelRequest/set-logging-level-request.json delete mode 100644 schema/draft/examples/SetLevelRequestParams/set-log-level-to-info.json delete mode 100644 schema/draft/examples/SetLevelResultResponse/set-logging-level-result-response.json delete mode 100644 schema/draft/examples/SubscribeRequest/subscribe-request.json delete mode 100644 schema/draft/examples/SubscribeRequestParams/subscribe-to-file-resource.json delete mode 100644 schema/draft/examples/SubscribeResultResponse/subscribe-result-response.json create mode 100644 schema/draft/examples/SubscriptionsAcknowledgedNotification/listen-acknowledged.json create mode 100644 schema/draft/examples/SubscriptionsListenRequest/listen-for-list-changes.json delete mode 100644 schema/draft/examples/UnsubscribeRequest/unsubscribe-request.json delete mode 100644 schema/draft/examples/UnsubscribeResultResponse/unsubscribe-result-response.json create mode 100644 schema/draft/examples/UnsupportedProtocolVersionError/unsupported-version.json diff --git a/docs/specification/draft/basic/index.mdx b/docs/specification/draft/basic/index.mdx index 37e9cd18d..e49387452 100644 --- a/docs/specification/draft/basic/index.mdx +++ b/docs/specification/draft/basic/index.mdx @@ -244,7 +244,7 @@ the server **MUST** include `io.modelcontextprotocol/subscriptionId` in `_meta` client can correlate the notification with the originating subscription request. [lifecycle]: /specification/draft/basic/lifecycle -[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen +[subscriptions-listen]: /specification/draft/basic/utilities/subscriptions **OpenTelemetry trace context:** diff --git a/docs/specification/draft/basic/lifecycle.mdx b/docs/specification/draft/basic/lifecycle.mdx index 3e7eb3f61..e801741ae 100644 --- a/docs/specification/draft/basic/lifecycle.mdx +++ b/docs/specification/draft/basic/lifecycle.mdx @@ -27,7 +27,7 @@ Specifically: the client passes on each request. Long-lived requests like -[`subscriptions/listen`](/specification/draft/server/resources#subscriptions-listen) +[`subscriptions/listen`](/specification/draft/basic/utilities/subscriptions) remain request/response — the response is just an open stream of notifications. Their state is scoped to the request itself, not to the connection underneath. diff --git a/docs/specification/draft/basic/transports.mdx b/docs/specification/draft/basic/transports.mdx index c364ca363..bbb865df4 100644 --- a/docs/specification/draft/basic/transports.mdx +++ b/docs/specification/draft/basic/transports.mdx @@ -54,7 +54,7 @@ clients **MUST** correlate notifications using the schema for [`SubscriptionsListenRequest`][subscriptions-listen-request] for details. -[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen +[subscriptions-listen]: /specification/draft/basic/utilities/subscriptions [subscriptions-listen-request]: /specification/draft/schema#subscriptionslistenrequest ### Request Metadata @@ -226,7 +226,7 @@ streams via `Last-Event-ID` are not supported. [notifications-message]: /specification/draft/server/utilities/logging [incomplete-result]: /specification/draft/schema#inputrequiredresult [sep-2322]: /seps/2322-MRTR -[subscriptions-listen]: /specification/draft/server/resources#subscriptions-listen +[subscriptions-listen]: /specification/draft/basic/utilities/subscriptions [tasks]: /specification/draft/basic/utilities/tasks ### Cancellation diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx index 0a54ab3ae..ab83064f0 100644 --- a/docs/specification/draft/schema.mdx +++ b/docs/specification/draft/schema.mdx @@ -183,7 +183,14 @@ If provided, the server should return results starting after this cursor.

interface RequestMetaObject {
  progressToken?: ProgressToken;
  [key: string]: unknown;
}

Extends MetaObject with additional request-specific fields. All key naming rules from MetaObject apply.

If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.

+

Extends MetaObject with additional request-specific fields. All key naming rules from MetaObject apply.

If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.

The MCP Protocol Version being used for this request. Required.

For the HTTP transport, this value MUST match the MCP-Protocol-Version +header; otherwise the server MUST return a 400 Bad Request. If the +server does not support the requested version, it MUST return an UnsupportedProtocolVersionError.

Identifies the client software making the request. Required.

The Implementation schema requires name and version; other +fields are optional.

The client's capabilities for this specific request. Required.

Capabilities are declared per-request rather than once at initialization; +an empty object means the client supports no optional capabilities. +Servers MUST NOT infer capabilities from prior requests.

The desired log level for this request. Optional.

If absent, the server MUST NOT send any notifications/message +notifications for this request. The client opts in to log messages by +explicitly setting a level. Replaces the former logging/setLevel RPC.

@@ -265,6 +272,24 @@ input_required - the request requires additional input and the result conta
+
+ +### `MISSING_REQUIRED_CLIENT_CAPABILITY` + +
MISSING_REQUIRED_CLIENT_CAPABILITY: -32003

Error code returned when a server requires a client capability that was +not declared in the request's clientCapabilities.

+
+ + +
+ +### `MissingRequiredClientCapabilityError` + +
interface MissingRequiredClientCapabilityError {
  jsonrpc: "2.0";
  id?: RequestId;
  error: Error & {
    code: -32003;
    data: { requiredCapabilities: ClientCapabilities };
  };
}

Returned when processing a request requires a capability the client did not +declare in clientCapabilities. For HTTP, the response status code MUST be 400 Bad Request.

Example: Missing elicitation capability
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32003,
"message": "Server requires the elicitation capability for this request",
"data": {
"requiredCapabilities": {
"elicitation": {}
}
}
}
}
+
+ +
### `ParseError` @@ -273,6 +298,16 @@ input_required - the request requires additional input and the result conta
+
+ +### `UnsupportedProtocolVersionError` + +
interface UnsupportedProtocolVersionError {
  jsonrpc: "2.0";
  id?: RequestId;
  error: Error & {
    code: -32602;
    data: { supported: string[]; requested: string };
  };
}

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 +chosen not to implement). For HTTP, the response status code MUST be 400 Bad Request.

Example: Unsupported protocol version
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Unsupported protocol version",
"data": {
"supported": ["DRAFT-2026-v1", "2025-11-25"],
"requested": "1900-01-01"
}
}
}
+
+ + ## Content @@ -546,110 +581,13 @@ without nested objects or arrays.

-## `initialize` - -
- -### `InitializeRequest` - -
interface InitializeRequest {
  jsonrpc: "2.0";
  id: RequestId;
  method: "initialize";
  params: InitializeRequestParams;
}

This request is sent from the client to the server when it first connects, asking it to begin initialization.

Example: Initialize request
{
"jsonrpc": "2.0",
"id": "initialize-example",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {
"form": {},
"url": {}
},
"tasks": {
"requests": {
"elicitation": {
"create": {}
},
"sampling": {
"createMessage": {}
}
}
}
},
"clientInfo": {
"name": "ExampleClient",
"title": "Example Client Display Name",
"version": "1.0.0",
"description": "An example MCP client application",
"icons": [
{
"src": "https://example.com/icon.png",
"mimeType": "image/png",
"sizes": ["48x48"]
}
],
"websiteUrl": "https://example.com"
}
}
}
-
- - -
- -### `InitializeRequestParams` - -
interface InitializeRequestParams {
  _meta?: RequestMetaObject;
  protocolVersion: string;
  capabilities: ClientCapabilities;
  clientInfo: Implementation;
}

Parameters for an initialize request.

Example: Full client capabilities
{
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {
"form": {},
"url": {}
},
"tasks": {
"requests": {
"elicitation": {
"create": {}
},
"sampling": {
"createMessage": {}
}
}
}
},
"clientInfo": {
"name": "ExampleClient",
"title": "Example Client Display Name",
"version": "1.0.0",
"description": "An example MCP client application",
"icons": [
{
"src": "https://example.com/icon.png",
"mimeType": "image/png",
"sizes": ["48x48"]
}
],
"websiteUrl": "https://example.com"
}
}

The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.

-
- - -
- -### `InitializeResultResponse` - -
interface InitializeResultResponse {
  jsonrpc: "2.0";
  id: RequestId;
  result: InitializeResult;
}

A successful response from the server for a initialize request.

Example: Initialize result response
{
"jsonrpc": "2.0",
"id": "initialize-example",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
},
"tasks": {
"list": {},
"cancel": {},
"requests": {
"tools": {
"call": {}
}
}
}
},
"serverInfo": {
"name": "ExampleServer",
"title": "Example Server Display Name",
"version": "1.0.0",
"description": "An example MCP server providing tools and resources",
"icons": [
{
"src": "https://example.com/server-icon.svg",
"mimeType": "image/svg+xml",
"sizes": ["any"]
}
],
"websiteUrl": "https://example.com/server"
},
"instructions": "Optional instructions for the client"
}
}
-
- - -
- -### `InitializeResult` - -
interface InitializeResult {
  _meta?: MetaObject;
  resultType: ResultType;
  protocolVersion: string;
  capabilities: ServerCapabilities;
  serverInfo: Implementation;
  instructions?: string;
  [key: string]: unknown;
}

The result returned by the server for an initialize request.

Example: Full server capabilities
{
"protocolVersion": "2024-11-05",
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
},
"tasks": {
"list": {},
"cancel": {},
"requests": {
"tools": {
"call": {}
}
}
}
},
"serverInfo": {
"name": "ExampleServer",
"title": "Example Server Display Name",
"version": "1.0.0",
"description": "An example MCP server providing tools and resources",
"icons": [
{
"src": "https://example.com/server-icon.svg",
"mimeType": "image/svg+xml",
"sizes": ["any"]
}
],
"websiteUrl": "https://example.com/server"
},
"instructions": "Optional instructions for the client"
}

Indicates the type of the result, which allows the client to determine -how to parse the result object.

"complete" 

The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.

Instructions describing how to use the server and its features.

Instructions should focus on information that helps the model use the server effectively (e.g., cross-tool relationships, workflow patterns, constraints), but should not duplicate information already in tool descriptions.

Clients MAY add this information to the system prompt.

Example: Server with workflow instructions
{
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "DatabaseServer",
"version": "1.0.0",
"description": "PostgreSQL database management server"
},
"instructions": "Use 'validate_schema' before 'migrate_schema' for safe migrations. Rate limited to 10 req/min."
}
-
- - -
- -### `ClientCapabilities` - -
interface ClientCapabilities {
  experimental?: { [key: string]: JSONObject };
  roots?: { listChanged?: boolean };
  sampling?: { context?: JSONObject; tools?: JSONObject };
  elicitation?: { form?: JSONObject; url?: JSONObject };
  tasks?: {
    list?: JSONObject;
    cancel?: JSONObject;
    requests?: {
      sampling?: { createMessage?: JSONObject };
      elicitation?: { create?: JSONObject };
    };
  };
  extensions?: { [key: string]: JSONObject };
}

Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.

Experimental, non-standard capabilities that the client supports.

Present if the client supports listing roots.

Type Declaration
  • OptionallistChanged?: boolean

    Whether the client supports notifications for changes to the roots list.

Example: Roots — minimum baseline support
{
"roots": {}
}
Example: Roots — list changed notifications
{
"roots": {
"listChanged": true
}
}

Present if the client supports sampling from an LLM.

Type Declaration
  • Optionalcontext?: JSONObject

    Whether the client supports context inclusion via includeContext parameter. -If not declared, servers SHOULD only use includeContext: "none" (or omit it).

  • Optionaltools?: JSONObject

    Whether the client supports tool use via tools and toolChoice parameters.

Example: Sampling — minimum baseline support
{
"sampling": {}
}
Example: Sampling — tool use support
{
"sampling": {
"tools": {}
}
}
Example: Sampling — context inclusion support (soft-deprecated)
{
"sampling": {
"context": {}
}
}

Present if the client supports elicitation from the server.

Example: Elicitation — form and URL mode support
{
"elicitation": {
"form": {},
"url": {}
}
}
Example: Elicitation — form mode only (implicit)
{
"elicitation": {}
}

Present if the client supports task-augmented requests.

Type Declaration
  • Optionallist?: JSONObject

    Whether this client supports tasks/list.

  • Optionalcancel?: JSONObject

    Whether this client supports tasks/cancel.

  • Optionalrequests?: {
      sampling?: { createMessage?: JSONObject };
      elicitation?: { create?: JSONObject };
    }

    Specifies which request types can be augmented with tasks.

    • Optionalsampling?: { createMessage?: JSONObject }

      Task support for sampling-related requests.

      • OptionalcreateMessage?: JSONObject

        Whether the client supports task-augmented sampling/createMessage requests.

    • Optionalelicitation?: { create?: JSONObject }

      Task support for elicitation-related requests.

Optional MCP extensions that the client supports. Keys are extension identifiers -(e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are -per-extension settings objects. An empty object indicates support with no settings.

Example: Extensions — UI extension with MIME type support
{
"extensions": {
"io.modelcontextprotocol/apps": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
-
- - -
- -### `Implementation` - -
interface Implementation {
  icons?: Icon[];
  name: string;
  title?: string;
  version: string;
  description?: string;
  websiteUrl?: string;
}

Describes the MCP implementation.

Optional set of sized icons that the client can display in a user interface.

Clients that support rendering icons MUST support at least the following MIME types:

  • image/png - PNG images (safe, universal compatibility)
  • image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)

Clients that support rendering icons SHOULD also support:

  • image/svg+xml - SVG images (scalable but requires security precautions)
  • image/webp - WebP images (modern, efficient format)

Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).

Intended for UI and end-user contexts — optimized to be human-readable and easily understood, -even by those unfamiliar with domain-specific terminology.

If not provided, the name should be used for display (except for Tool, -where annotations.title should be given precedence over using name, -if present).

The version of this implementation.

An optional human-readable description of what this implementation does.

This can be used by clients or servers to provide context about their purpose -and capabilities. For example, a server might describe the types of resources -or tools it provides, while a client might describe its intended use case.

An optional URL of the website for this implementation.

-
- - -
- -### `ServerCapabilities` - -
interface ServerCapabilities {
  experimental?: { [key: string]: JSONObject };
  logging?: JSONObject;
  completions?: JSONObject;
  prompts?: { listChanged?: boolean };
  resources?: { subscribe?: boolean; listChanged?: boolean };
  tools?: { listChanged?: boolean };
  tasks?: {
    list?: JSONObject;
    cancel?: JSONObject;
    requests?: { tools?: { call?: JSONObject } };
  };
  extensions?: { [key: string]: JSONObject };
}

Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.

Experimental, non-standard capabilities that the server supports.

Present if the server supports sending log messages to the client.

Example: Logging — minimum baseline support
{
"logging": {}
}

Present if the server supports argument autocompletion suggestions.

Example: Completions — minimum baseline support
{
"completions": {}
}

Present if the server offers any prompt templates.

Type Declaration
  • OptionallistChanged?: boolean

    Whether this server supports notifications for changes to the prompt list.

Example: Prompts — minimum baseline support
{
"prompts": {}
}
Example: Prompts — list changed notifications
{
"prompts": {
"listChanged": true
}
}

Present if the server offers any resources to read.

Type Declaration
  • Optionalsubscribe?: boolean

    Whether this server supports subscribing to resource updates.

  • OptionallistChanged?: boolean

    Whether this server supports notifications for changes to the resource list.

Example: Resources — minimum baseline support
{
"resources": {}
}
Example: Resources — subscription to individual resource updates (only)
{
"resources": {
"subscribe": true
}
}
Example: Resources — list changed notifications (only)
{
"resources": {
"listChanged": true
}
}
Example: Resources — all notifications
{
"resources": {
"subscribe": true,
"listChanged": true
}
}

Present if the server offers any tools to call.

Type Declaration
  • OptionallistChanged?: boolean

    Whether this server supports notifications for changes to the tool list.

Example: Tools — minimum baseline support
{
"tools": {}
}
Example: Tools — list changed notifications
{
"tools": {
"listChanged": true
}
}

Present if the server supports task-augmented requests.

Type Declaration
  • Optionallist?: JSONObject

    Whether this server supports tasks/list.

  • Optionalcancel?: JSONObject

    Whether this server supports tasks/cancel.

  • Optionalrequests?: { tools?: { call?: JSONObject } }

    Specifies which request types can be augmented with tasks.

    • Optionaltools?: { call?: JSONObject }

      Task support for tool-related requests.

Optional MCP extensions that the server supports. Keys are extension identifiers -(e.g., "io.modelcontextprotocol/apps"), and values are per-extension settings -objects. An empty object indicates support with no settings.

Example: Extensions — UI extension support
{
"extensions": {
"io.modelcontextprotocol/apps": {}
}
}
-
- - - -## `logging/setLevel` - -
- -### `SetLevelRequest` - -
interface SetLevelRequest {
  jsonrpc: "2.0";
  id: RequestId;
  method: "logging/setLevel";
  params: SetLevelRequestParams;
}

A request from the client to the server, to enable or adjust logging.

Example: Set logging level request
{
"jsonrpc": "2.0",
"id": "set-logging-level-example",
"method": "logging/setLevel",
"params": {
"level": "info"
}
}
-
- - -
- -### `SetLevelRequestParams` - -
interface SetLevelRequestParams {
  _meta?: RequestMetaObject;
  level: LoggingLevel;
}

Parameters for a logging/setLevel request.

Example: Set log level to "info"
{
"level": "info"
}

The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message.

-
- - -
- -### `SetLevelResultResponse` - -
interface SetLevelResultResponse {
  jsonrpc: "2.0";
  id: RequestId;
  result: Result;
}

A successful response from the server for a logging/setLevel request.

Example: Set logging level result response
{
"jsonrpc": "2.0",
"id": "set-logging-level-example",
"result": {}
}
-
- - - ## `notifications/cancelled`
### `CancelledNotification` -
interface CancelledNotification {
  jsonrpc: "2.0";
  method: "notifications/cancelled";
  params: CancelledNotificationParams;
}

This notification can be sent by either side to indicate that it is cancelling a previously-issued 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.

This notification indicates that the result will be unused, so any associated processing SHOULD cease.

A client MUST NOT attempt to cancel its initialize request.

For task cancellation, use the tasks/cancel request instead of this notification.

Example: User-requested cancellation
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "123",
"reason": "User requested cancellation"
}
}
+
interface CancelledNotification {
  jsonrpc: "2.0";
  method: "notifications/cancelled";
  params: CancelledNotificationParams;
}

This notification can be sent by either side to indicate that it is cancelling a previously-issued 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.

This notification indicates that the result will be unused, so any associated processing SHOULD cease.

For task cancellation, use the tasks/cancel request instead of this notification.

Example: User-requested cancellation
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "123",
"reason": "User requested cancellation"
}
}
@@ -664,17 +602,6 @@ This MUST NOT be used for cancelling tasks (use the - -### `InitializedNotification` - -
interface InitializedNotification {
  
jsonrpc: "2.0";
  method: "notifications/initialized";
  params?: NotificationParams;
}

This notification is sent from the client to the server after initialization has finished.

Example: Initialized notification
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
-
- - - ## `notifications/tasks/status`
@@ -700,7 +627,7 @@ This MUST NOT be used for cancelling tasks (use the interface LoggingMessageNotification {
  
jsonrpc: "2.0";
  method: "notifications/message";
  params: LoggingMessageNotificationParams;
}

JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.

Example: Log database connection failed
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "error",
"logger": "database",
"data": {
"error": "Connection failed",
"details": {
"host": "localhost",
"port": 5432
}
}
}
}
+
interface LoggingMessageNotification {
  jsonrpc: "2.0";
  method: "notifications/message";
  params: LoggingMessageNotificationParams;
}

JSONRPCNotification of a log message passed from server to client. The client opts in by setting "io.modelcontextprotocol/logLevel" in a request's _meta.

Example: Log database connection failed
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "error",
"logger": "database",
"data": {
"error": "Connection failed",
"details": {
"host": "localhost",
"port": 5432
}
}
}
}
@@ -760,7 +687,7 @@ This MUST NOT be used for cancelling tasks (use the interface ResourceUpdatedNotification {
  
jsonrpc: "2.0";
  method: "notifications/resources/updated";
  params: ResourceUpdatedNotificationParams;
}

A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.

Example: File resource updated notification
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "file:///project/src/main.rs"
}
}
+
interface ResourceUpdatedNotification {
  jsonrpc: "2.0";
  method: "notifications/resources/updated";
  params: ResourceUpdatedNotificationParams;
}

A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the resourceSubscriptions field of a subscriptions/listen request.

Example: File resource updated notification
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "file:///project/src/main.rs"
}
}
@@ -773,56 +700,48 @@ This MUST NOT be used for cancelling tasks (use the -### `RootsListChangedNotification` +### `SubscriptionsAcknowledgedNotification` -
interface RootsListChangedNotification {
  
jsonrpc: "2.0";
  method: "notifications/roots/list_changed";
  params?: NotificationParams;
}

A notification from the client to the server, informing it that the list of roots has changed. -This notification should be sent whenever the client adds, removes, or modifies any root. -The server should then request an updated list of roots using the ListRootsRequest.

Example: Roots list changed
{
"jsonrpc": "2.0",
"method": "notifications/roots/list_changed"
}
+
interface SubscriptionsAcknowledgedNotification {
  jsonrpc: "2.0";
  method: "notifications/subscriptions/acknowledged";
  params: SubscriptionsAcknowledgedNotificationParams;
}

Sent by the server as the first message on a subscriptions/listen stream to acknowledge +that the subscription has been established and to report which notification +types it agreed to honor.

Example: Listen acknowledged
{
"jsonrpc": "2.0",
"method": "notifications/subscriptions/acknowledged",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": "listen-1"
},
"notifications": {
"toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}
- -## `notifications/tools/list_changed` -
-### `ToolListChangedNotification` +### `SubscriptionsAcknowledgedNotificationParams` -
interface ToolListChangedNotification {
  jsonrpc: "2.0";
  method: "notifications/tools/list_changed";
  params?: NotificationParams;
}

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.

Example: Tools list changed
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
+
interface SubscriptionsAcknowledgedNotificationParams {
  _meta?: MetaObject;
  notifications: SubscriptionFilter;
}

Parameters for a notifications/subscriptions/acknowledged notification.

The subset of requested notification types the server agreed to honor. +Only includes notification types the server actually supports; if the +client requested an unsupported type (e.g., promptsListChanged when +the server has no prompts), it is omitted from this set.

-## `notifications/elicitation/complete` +## `notifications/tools/list_changed`
-### `ElicitationCompleteNotification` +### `ToolListChangedNotification` -
interface ElicitationCompleteNotification {
  jsonrpc: "2.0";
  method: "notifications/elicitation/complete";
  params: { 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
{
"jsonrpc": "2.0",
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Type Declaration
  • elicitationId: string

    The ID of the elicitation that completed.

+
interface ToolListChangedNotification {
  jsonrpc: "2.0";
  method: "notifications/tools/list_changed";
  params?: NotificationParams;
}

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.

Example: Tools list changed
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
-## `ping` - -
- -### `PingRequest` - -
interface PingRequest {
  jsonrpc: "2.0";
  id: RequestId;
  method: "ping";
  params?: RequestParams;
}

A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.

Example: Ping request
{
"jsonrpc": "2.0",
"id": "ping-example",
"method": "ping"
}
-
- +## `notifications/elicitation/complete`
-### `PingResultResponse` +### `ElicitationCompleteNotification` -
interface PingResultResponse {
  jsonrpc: "2.0";
  id: RequestId;
  result: Result;
}

A successful response for a ping request.

Example: Ping result response
{
"jsonrpc": "2.0",
"id": "ping-example",
"result": {}
}
+
interface ElicitationCompleteNotification {
  jsonrpc: "2.0";
  method: "notifications/elicitation/complete";
  params: { 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
{
"jsonrpc": "2.0",
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Type Declaration
  • elicitationId: string

    The ID of the elicitation that completed.

@@ -1006,7 +925,7 @@ If present, there may be more results available.