Skip to content

SEP-2575: Make MCP Stateless#2575

Merged
kurtisvg merged 69 commits into
modelcontextprotocol:mainfrom
kurtisvg:sep-1442-reborn
May 11, 2026
Merged

SEP-2575: Make MCP Stateless#2575
kurtisvg merged 69 commits into
modelcontextprotocol:mainfrom
kurtisvg:sep-1442-reborn

Conversation

@kurtisvg
Copy link
Copy Markdown
Contributor

Summary

This PR introduces a SEP for making MCP stateless-by-default, migrated from the original issue discussion at #1442 into the PR-based SEP format per SEP-1850.

Motivation

The current MCP specification requires a mandatory initialization handshake that establishes persistent session state. This creates significant challenges for scalability (load balancing requires sticky sessions), resilience (server failure loses session state), and implementation complexity (both client and server must manage session lifecycles).

What This SEP Proposes

This SEP removes the initialization handshake and replaces it with discrete, stateless alternatives following a "pay as you go" model:

  • Per-request protocol version — via MCP-Protocol-Version HTTP header and _meta field, with version negotiation through UnsupportedVersionError responses
  • server/discover RPC — optional discovery endpoint for server capabilities, supported versions, and metadata, replacing the capability exchange from initialization
  • Per-request client capabilities — clients specify capabilities in _meta per-request instead of negotiating once at connection time
  • messages/listen RPC — dedicated endpoint for client-initiated streaming, replacing the existing GET endpoint behavior for Streamable HTTP

Relationship to Other SEPs

This SEP focuses on removing the initialization handshake and providing stateless alternatives. Session-related concerns are handled separately:

The content of this SEP will be updated in subsequent commits to reflect these decisions and remove session-related material that is now covered by those SEPs.

Fixes #1442

@kurtisvg kurtisvg self-assigned this Apr 14, 2026
@kurtisvg kurtisvg changed the title SEP-XXXX: Make MCP Stateless SEP-2575: Make MCP Stateless Apr 14, 2026
@kurtisvg kurtisvg requested review from a team as code owners April 14, 2026 21:48
@kurtisvg kurtisvg added SEP transport Related to MCP transports labels Apr 14, 2026
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we add the ttl to this that @CaitieM20 is proposing in the other SEP?

One thought on my mind is that for older server/newer client compatibility, realistically the client will actually need to remember the server's older protocol version if it wants to avoid an error on every request trying to figure out the compatible version, so we need a way to cache that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree it should be added somewhere. I wasn't sure which would get merged first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FWIW, I was going to suggest the exact same thing. I'd prefer to err on the side of putting it in both places, to make sure it doesn't get missed. :)

Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
@dsp-ant dsp-ant added the roadmap/transport Roadmap: Transport Evolution & Scalability (incl. Server Cards) label Apr 15, 2026
Comment thread docs/seps/2575-stateless-mcp.mdx Outdated
@localden localden added the draft SEP proposal with a sponsor. label Apr 15, 2026
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment on lines +158 to +164
export interface RequestMetaObject extends MetaObject {
progressToken?: ProgressToken;
+ /**
+ * The MCP Protocol Version being used for this request.
+ */
+ "io.modelcontextprotocol/protocolVersion": string;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if we are overloading _meta here over time. Would we ever add any top level field anymore or shove everything into meta? For me the distinction might be more along the lines of what is a required field by the protocol vs what is an optional field or something provided by an extension. I can't fully point my finger at it, but it feels more right to me, to have a top protocolVersion field instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we started with a top-level field, and there was some feedback that we should use _meta instead because of this line in the spec:

Additionally, definitions in the schema may reserve particular names for purpose-specific metadata, as declared in those definitions.

Is protocolVersion the only field you think makes sense at the top level? Or should the others be as well?

Comment thread seps/2575-stateless-mcp.md
Comment thread seps/2575-stateless-mcp.md
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
Comment thread seps/2575-stateless-mcp.md Outdated
guglielmo-san added a commit to modelcontextprotocol/go-sdk that referenced this pull request May 26, 2026
## Description

This PR lays the foundational server-side groundwork for the `>=
2026-06-30` sessionless and stateless feature introduced by
[SEP-2575](modelcontextprotocol/modelcontextprotocol#2575)
and SEP-2567, tracked in
[design/stateless.md](https://github.com/modelcontextprotocol/go-sdk/blob/614460a2253e7772eff6c78f142bfa0428530dc5/design/stateless.md).

### SEP-2575: Stateless MCP

*   **Per-request protocol detection in `ServerSession.handle()`**
Unmarshal `_meta` from the raw JSON-RPC params determines whether each
request follows the new protocol.

*   **Per-request typed accessors on `ServerRequest[P]`**
    ```go
    func (r *ServerRequest[P]) ProtocolVersion() string
    func (r *ServerRequest[P]) ClientInfo() *Implementation
    func (r *ServerRequest[P]) ClientCapabilities() *ClientCapabilities
    ```

* **Reject client->server `initialize`, `initialized` and `ping` for
new-protocol requests**

*   **Per-request `_meta` field name constants**
Three constants for the wire-protocol field names
(`MetaKeyProtocolVersion`, `MetaKeyClientInfo`,
`MetaKeyClientCapabilities`)

* **Stop synthesizing fake `InitializeParams` for new-protocol
requests**

Fixes: #966
@localden localden added final SEP finalized. and removed accepted SEP accepted by core maintainers, but still requires final wording and reference implementation. labels Jun 4, 2026
@Patdolitse
Copy link
Copy Markdown

Patdolitse commented Jun 6, 2026

+1 from a multi-client MCP server perspective. We built piia-engram (open-source personal memory layer) and it gets connected by Claude Code, Codex, and Cursor at the same time on the same machine.

The init handshake never did anything useful for us — each tool call is already self-contained (user identity comes from config, knowledge IDs are explicit). Having to do a handshake per-client just added startup latency for zero benefit.

server/discover as an optional endpoint makes way more sense. New clients can check what we support without committing to a session, and existing clients that already know our tool surface can skip it entirely.

One small thing: per-request _meta capabilities is great for our case. Different clients have different trust levels in our system (e.g. some are read-only, some can write). Right now we handle that server-side via caller identification, but having the client declare its capabilities per-request would make the contract more explicit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation final SEP finalized. roadmap/transport Roadmap: Transport Evolution & Scalability (incl. Server Cards) SEP transport Related to MCP transports

Projects

Status: Review Batch

Development

Successfully merging this pull request may close these issues.

SEP-1442: Make MCP Stateless (by default)