diff --git a/docs/docs.json b/docs/docs.json index 0201f80a3..dd5d27018 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -322,8 +322,8 @@ "group": "Utilities", "pages": [ "specification/draft/basic/utilities/cancellation", - "specification/draft/basic/utilities/ping", "specification/draft/basic/utilities/progress", + "specification/draft/basic/utilities/subscriptions", "specification/draft/basic/utilities/tasks", "specification/draft/basic/utilities/mrtr" ] @@ -342,6 +342,7 @@ "group": "Server Features", "pages": [ "specification/draft/server/index", + "specification/draft/server/discover", "specification/draft/server/prompts", "specification/draft/server/resources", "specification/draft/server/tools", @@ -428,7 +429,8 @@ "group": "Accepted", "pages": [ "seps/2207-oidc-refresh-token-guidance", - "seps/2260-Require-Server-requests-to-be-associated-with-Client-requests" + "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 new file mode 100644 index 000000000..9fd65b8d4 --- /dev/null +++ b/docs/seps/2575-stateless-mcp.mdx @@ -0,0 +1,802 @@ +--- +title: "SEP-2575: Make MCP Stateless" +sidebarTitle: "SEP-2575: Make MCP Stateless" +description: "Make MCP Stateless" +--- + +
+ + Accepted + + + Standards Track + +
+ +| Field | Value | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| **SEP** | 2575 | +| **Title** | Make MCP Stateless | +| **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)), | +| **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][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 + +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 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][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. + - **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 `RequestMetaObject`: + +```ts +export interface RequestMetaObject extends MetaObject { + progressToken?: ProgressToken; ++ /** ++ * The MCP Protocol Version being used for this request. ++ */ ++ "io.modelcontextprotocol/protocolVersion": string; + // Additional per-request fields (clientInfo, clientCapabilities, logLevel) + // are introduced in the Per-Request Client Capabilities section below. +} +``` + +#### Unsupported Protocol Versions + +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: + +```ts +export interface UnsupportedProtocolVersionError extends Omit< + JSONRPCErrorResponse, + "error" +> { + error: Error & { + code: typeof INVALID_PARAMS; + data: { + /** + * An array of protocol version strings that the server supports. + */ + supported: string[]; + /** + * The protocol version that was requested by the client. + */ + requested: string; + }; + }; +} +``` + +#### 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 `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. +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. + +### 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. + +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 + +- **Purpose**: To allow a client to query the server for its supported protocol + versions, capabilities, and other metadata. + +**Request Schema:** + +```ts +export interface DiscoverRequest extends Request { + method: "server/discover"; + params?: {}; +} +``` + +**Response Schema:** + +```ts +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 + * 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 **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 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. + +#### 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. + +##### 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 +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; + +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; + }; + }; +} +``` + +### `subscriptions/listen` RPC + +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. + +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. + +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 + +```ts +export interface SubscriptionsListenRequest extends Request { + method: "subscriptions/listen"; + params: { + _meta: { + "io.modelcontextprotocol/protocolVersion": string; + "io.modelcontextprotocol/clientInfo": Implementation; + "io.modelcontextprotocol/clientCapabilities": ClientCapabilities; + // ... other meta fields + }; + + /** + * 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: { + /** + * 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[]; + }; + }; +} +``` + +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 + +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 { + 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[]; + }; + }; +} +``` + +#### 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 +response is an open SSE stream (`Content-Type: text/event-stream`), and the +first JSON-RPC message on this stream **MUST** be a +`SubscriptionsAcknowledgedNotification`. + +**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, 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 + +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`. +- `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 the + 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 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 + +### 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][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 + +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 + `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 +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) + +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 + +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 + +// 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. + +### How does `server/discover` relate to the MCP Server Card? + +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 +`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? + +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` +(`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 `subscriptions/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 76d5f9665..a80929ad8 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -12,14 +12,15 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary +- **Accepted**: 3 - **Final**: 29 - **Approved**: 1 -- **Accepted**: 2 ## All SEPs | SEP | Title | Status | Type | Created | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------- | ---------------- | ---------- | +| [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/docs/specification/draft/architecture/index.mdx b/docs/specification/draft/architecture/index.mdx index a1f886477..2c6375ab5 100644 --- a/docs/specification/draft/architecture/index.mdx +++ b/docs/specification/draft/architecture/index.mdx @@ -5,10 +5,12 @@ title: Architecture
The Model Context Protocol (MCP) follows a client-host-server architecture where each -host can run multiple client instances. This architecture enables users to integrate AI -capabilities across applications while maintaining clear security boundaries and -isolating concerns. Built on JSON-RPC, MCP provides a protocol focused on context -exchange and sampling coordination between clients and servers. +host can run multiple client instances. MCP is a stateless protocol: every request is +self-contained and carries its own protocol version, client identity, and capabilities. +This architecture enables users to integrate AI capabilities across applications while +maintaining clear security boundaries and isolating concerns. Built on JSON-RPC, MCP +provides a protocol focused on context exchange and sampling coordination between +clients and servers. ## Core Components @@ -58,10 +60,10 @@ The host process acts as the container and coordinator: ### Clients -Each client is created by the host and maintains an isolated server connection: +Each client is created by the host and communicates with exactly one server: -- Establishes one connection per server -- Handles protocol negotiation and capability exchange +- Communicates with exactly one server +- Attaches protocol version and capabilities to every request - Routes protocol messages bidirectionally - Manages subscriptions and notifications - Maintains security boundaries between servers @@ -75,7 +77,7 @@ Servers provide specialized context and capabilities: - Expose resources, tools and prompts via MCP primitives - Operate independently with focused responsibilities -- Request sampling through client interfaces +- Request client input (sampling, elicitation, roots) via `IncompleteResponse` within a reply - Must respect security constraints - Can be local processes or remote services @@ -100,7 +102,7 @@ implementation: servers** - Servers receive only necessary contextual information - Full conversation history stays with the host - - Each server connection maintains isolation + - Each server maintains isolation - Cross-server interactions are controlled by the host - Host process enforces security boundaries @@ -114,13 +116,16 @@ 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 in response to +[`server/discover`](/specification/draft/server/discover), which clients may call before +any other request for up-front capability discovery. -- 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 @@ -129,43 +134,40 @@ sequenceDiagram participant Client participant Server - Host->>+Client: Initialize client - Client->>+Server: Initialize with capabilities - Server-->>Client: Respond with supported capabilities - - Note over Host,Server: Active Connection with Negotiated Features + opt Discovery + Client->>Server: server/discover + Server-->>Client: supported versions + capabilities + end loop Client Requests Host->>Client: User- or model-initiated action - Client->>Server: Request (tools/resources) + Client->>Server: Request (with _meta: version, clientInfo, clientCapabilities) + alt Server requires client input + Server-->>Client: IncompleteResponse (e.g. sampling/createMessage) + Client->>Host: Forward to AI + Host-->>Client: AI response + Client->>Server: Original request (with input) + end Server-->>Client: Response Client-->>Host: Update UI or respond to model end - loop Server Requests - Server->>Client: Request (sampling) - Client->>Host: Forward to AI - Host-->>Client: AI response - Client-->>Server: Response + opt Subscriptions + Client->>Server: subscriptions/listen (toolsListChanged, resourceSubscriptions, …) + Server--)Client: notifications/subscriptions/acknowledged + loop Stream + Server--)Client: notifications/* (tagged with subscriptionId) + end end - - loop Notifications - Server--)Client: Resource updates - Client--)Server: Status changes - end - - Host->>Client: Terminate - Client->>-Server: End connection - deactivate Server ``` -Each capability unlocks specific protocol features for use during the connection. For -example: +Each capability unlocks specific protocol features on a per-request basis. For example: - Implemented [server features](/specification/draft/server) must be advertised in the server's capabilities -- Emitting resource subscription notifications requires the server to declare - subscription support +- Receiving resource update notifications requires opening a + [`subscriptions/listen`](/specification/draft/basic/utilities/subscriptions) stream + with the desired resource URIs - Tool invocation requires the server to declare tool capabilities - [Sampling](/specification/draft/client) requires the client to declare support in its capabilities diff --git a/docs/specification/draft/basic/index.mdx b/docs/specification/draft/basic/index.mdx index 1d69d197f..989864cb6 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,34 @@ 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 | + +A server **MUST NOT** rely on capabilities the client has not declared. If +processing a request requires a capability the client did not include in +`io.modelcontextprotocol/clientCapabilities`, the server **MUST** return a +[`MissingRequiredClientCapabilityError`](/specification/draft/schema#missingrequiredclientcapabilityerror) +(`-32003`) whose `data.requiredCapabilities` lists the missing capabilities. On +HTTP, the response status **MUST** be `400 Bad 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/basic/utilities/subscriptions + **OpenTelemetry trace context:** As an exception to the prefix requirement above, the keys `traceparent`, `tracestate`, and diff --git a/docs/specification/draft/basic/lifecycle.mdx b/docs/specification/draft/basic/lifecycle.mdx index 69eb319a8..dc8c4282f 100644 --- a/docs/specification/draft/basic/lifecycle.mdx +++ b/docs/specification/draft/basic/lifecycle.mdx @@ -4,221 +4,121 @@ 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/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. ```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. + +Servers **MUST** implement +[`server/discover`](/specification/draft/server/discover). Clients +**MAY** call it before sending any other requests to learn the server's +supported versions up front, but are not required to — 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/server/discover) first, + setting its preferred modern version in `_meta`. If the server returns + `Method not found` (`-32601`), fall back to the legacy `initialize` + handshake. If the server returns `UnsupportedProtocolVersionError`, the + server speaks a version of MCP without `initialize` — use one of its + advertised `supportedVersions` instead of falling back to `initialize`. + +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 +148,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. diff --git a/docs/specification/draft/basic/transports.mdx b/docs/specification/draft/basic/transports.mdx index b58092b75..1a938a8e2 100644 --- a/docs/specification/draft/basic/transports.mdx +++ b/docs/specification/draft/basic/transports.mdx @@ -19,242 +19,276 @@ 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/basic/utilities/subscriptions +[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` +### Unexpected Termination -The protocol version sent by the client **SHOULD** be the one [negotiated during -initialization](/specification/draft/basic/lifecycle#version-negotiation). +If the server process exits unexpectedly, the client **SHOULD** restart it. +Because the protocol is stateless, any in-flight requests are simply lost and +the client can retry them against the fresh process. Active +[`subscriptions/listen`][subscriptions-listen] streams must also be +re-established after restart. -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`. +[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 -If the server receives a request with an invalid or unsupported -`MCP-Protocol-Version`, it **MUST** respond with `400 Bad Request`. +### Backward Compatibility -### Standard MCP Request Headers +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` (`-32601`), the +client falls back to the legacy `initialize` handshake. If the server returns +`UnsupportedProtocolVersionError`, it speaks a version of MCP without +`initialize` — the client **SHOULD** retry using one of the advertised +`supportedVersions` rather than falling back to `initialize`. See +[Lifecycle: Backward Compatibility][lifecycle-compat] +for details. -The Streamable HTTP transport requires clients to include the following headers on POST -requests, mirrored from the JSON-RPC request body: +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. + + + +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 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/basic/utilities/subscriptions +[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. + +If the server does not implement the requested RPC method, it **MUST** respond +with `404 Not Found` and a JSON-RPC error with code `-32601` +(`Method not found`). + +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 +297,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 +314,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 +332,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 +341,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:** +#### Custom Headers from Tool Parameters -```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. - -#### 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 +418,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 +437,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 +448,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 +473,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 +512,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. 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 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 diff --git a/docs/specification/draft/basic/utilities/subscriptions.mdx b/docs/specification/draft/basic/utilities/subscriptions.mdx new file mode 100644 index 000000000..2f9c92b79 --- /dev/null +++ b/docs/specification/draft/basic/utilities/subscriptions.mdx @@ -0,0 +1,125 @@ +--- +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"] + } + } +} +``` + +### 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 | + +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"] + } + } +} +``` + +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" + } +} +``` + +## Multiple Concurrent Subscriptions + +A client **MAY** have multiple active subscriptions concurrently — for example, +one listening for tools-list changes and another for resource updates. Each +subscription is identified by the JSON-RPC request ID of its +`subscriptions/listen` request, and every notification on the stream carries +that ID in `io.modelcontextprotocol/subscriptionId` so clients can demultiplex +them. + +## Cancellation + +A subscription ends when: + +- The **client** cancels it — close the SSE stream (HTTP) or send + `notifications/cancelled` referencing the listen request ID (stdio). +- The **server** tears it down (e.g., during shutdown) — it closes the + underlying transport connection. +- The underlying transport closes (HTTP timeout, TCP disconnect, stdio process + exit). + +On **stdio**, if the connection is terminated and then re-established, the +client **MUST** re-send `subscriptions/listen` to re-establish its +subscriptions — the server holds no subscription state across reconnections. + +See [Cancellation][cancellation] for the full rules. + +[cancellation]: /specification/draft/basic/utilities/cancellation diff --git a/docs/specification/draft/basic/utilities/tasks.mdx b/docs/specification/draft/basic/utilities/tasks.mdx index 8265b0690..372194bd0 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. @@ -492,7 +492,7 @@ With the Streamable HTTP (SSE) transport, servers often close SSE streams after Servers can handle this by enqueueing messages to the client to side-channel task-related messages alongside other responses. -Servers have flexibility in how they manage SSE streams during task polling and result retrieval, and clients **SHOULD** expect messages to be delivered on any SSE stream, including the HTTP GET stream. +Servers have flexibility in how they manage SSE streams during task polling and result retrieval, and clients **SHOULD** expect messages to be delivered on any SSE stream opened via a POST response. One possible approach is maintaining an SSE stream on `tasks/result` (see notes on the `input_required` status). Where possible, servers **SHOULD NOT** upgrade to an SSE stream in response to a `tasks/get` request, as the client has indicated it wishes to poll for a result. diff --git a/docs/specification/draft/changelog.mdx b/docs/specification/draft/changelog.mdx index 08f6f6d63..37d8bc3ce 100644 --- a/docs/specification/draft/changelog.mdx +++ b/docs/specification/draft/changelog.mdx @@ -11,6 +11,14 @@ the previous revision, [2025-11-25](/specification/2025-11-25). 1. Remove protocol-level sessions and the `Mcp-Session-Id` header from the Streamable HTTP transport. List endpoints (`tools/list`, `resources/list`, `prompts/list`) no longer vary per-connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments ([SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567)). +2. Make MCP stateless: remove the `initialize`/`notifications/initialized` handshake. Every request now carries its protocol version, client identity, and client capabilities in `_meta` (`io.modelcontextprotocol/protocolVersion`, `io.modelcontextprotocol/clientInfo`, `io.modelcontextprotocol/clientCapabilities`). Version mismatches return `UnsupportedProtocolVersionError` ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). + +3. Add `server/discover`: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity. Clients MAY call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). + +4. Replace the HTTP GET endpoint and `resources/subscribe`/`resources/unsubscribe` with `subscriptions/listen`: a single long-lived POST-response stream for all server-to-client notifications. Clients opt in to specific types (`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions`); the server acknowledges and tags notifications with `io.modelcontextprotocol/subscriptionId` ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). + +5. Remove `ping`, `logging/setLevel`, and `notifications/roots/list_changed`. Log level is now set per-request via `io.modelcontextprotocol/logLevel` in `_meta`; servers MUST NOT emit `notifications/message` for requests that did not include this field ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). + ## Minor changes 1. Add `extensions` field to `ClientCapabilities` and `ServerCapabilities` to support optional [extensions](/docs/extensions/overview) beyond the core protocol. 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..74c093b53 100644 --- a/docs/specification/draft/client/roots.mdx +++ b/docs/specification/draft/client/roots.mdx @@ -9,7 +9,7 @@ filesystem "roots" to servers. Roots inform servers about the directories and fi client considers relevant, so that servers can focus their operations accordingly. They are informational guidance rather than an access-control mechanism. The protocol does not enforce that servers stay within roots. Servers can request the list of roots from -supporting clients and receive notifications when that list changes. +supporting clients. ## User Interaction Model @@ -39,22 +39,17 @@ 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 { "capabilities": { - "roots": { - "listChanged": true - } + "roots": {} } } ``` -`listChanged` indicates whether the client will emit notifications when the list of roots -changes. - ## Protocol Messages ### Listing Roots @@ -162,6 +157,5 @@ as the server is not waiting for a response with the `IncompleteResponse` patter 2. Servers **SHOULD**: - Check for roots capability before usage - - Handle root list changes gracefully - Respect root boundaries in operations - Cache root information appropriately 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/index.mdx b/docs/specification/draft/index.mdx index ea5fbf986..38b926a07 100644 --- a/docs/specification/draft/index.mdx +++ b/docs/specification/draft/index.mdx @@ -50,8 +50,8 @@ and tools into the ecosystem of AI applications. ### Base Protocol - [JSON-RPC](https://www.jsonrpc.org/) message format -- Stateful connections -- Server and client capability negotiation +- Stateless, self-contained requests +- Per-request capability negotiation ### Features diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx index 0a54ab3ae..b98dce9b2 100644 --- a/docs/specification/draft/schema.mdx +++ b/docs/specification/draft/schema.mdx @@ -102,7 +102,7 @@ the icon is designed to be used with a dark background.

If not provided, ### `InputResponseRequestParams` -

interface InputResponseRequestParams {
  _meta?: RequestMetaObject;
  inputResponses?: InputResponses;
  requestState?: string;
}

Common params for any request.

+
interface InputResponseRequestParams {
  _meta: RequestMetaObject;
  inputResponses?: InputResponses;
  requestState?: string;
}

Common params for any request.

@@ -158,7 +158,7 @@ the icon is designed to be used with a dark background.

If not provided, ### `PaginatedRequestParams` -

interface PaginatedRequestParams {
  _meta?: RequestMetaObject;
  cursor?: string;
}

Common params for paginated requests.

Example: List request with cursor
{
"cursor": "eyJwYWdlIjogMn0="
}

An opaque token representing the current pagination position. +

interface PaginatedRequestParams {
  _meta: RequestMetaObject;
  cursor?: string;
}

Common params for paginated requests.

Example: List request with cursor
{
"_meta": {
"io.modelcontextprotocol/protocolVersion": "DRAFT-2026-v1",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
},
"cursor": "eyJwYWdlIjogMn0="
}

An opaque token representing the current pagination position. If provided, the server should return results starting after this cursor.

@@ -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.

@@ -191,7 +198,7 @@ If provided, the server should return results starting after this cursor.

interface RequestParams {
  _meta?: RequestMetaObject;
}

Common params for any request.

+
interface RequestParams {
  _meta: RequestMetaObject;
}

Common params for any request.

@@ -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 @@ -351,7 +386,7 @@ if present).