Skip to content

[Server] Add MCP stateless mode support - #416

Open
andreybolonin wants to merge 4 commits into
modelcontextprotocol:mainfrom
andreybolonin:main
Open

[Server] Add MCP stateless mode support#416
andreybolonin wants to merge 4 commits into
modelcontextprotocol:mainfrom
andreybolonin:main

Conversation

@andreybolonin

Copy link
Copy Markdown

Motivation and Context

This PR implements MCP 2026-07-28 stateless mode support for the official PHP SDK.

The stateless protocol variant removes the requirement for a persistent session handshake (initialize / initialized) and the Mcp-Session-Id header. Each HTTP POST request is self-contained: the server creates an ephemeral in-memory session, processes the request, and destroys it at the end of the lifecycle.

This enables MCP servers to run in environments where sticky sessions are not available — such as serverless platforms, plain round-robin load balancers, and stateless API gateways.

Key changes:

  • Protocol: Added stateless flag. When enabled, skips resolveSession() and creates a fresh ephemeral session per request.
  • Builder: Added setStateless(bool) to toggle the mode. In stateless mode, it registers ServerDiscoverHandler instead of InitializeHandler, and omits InitializedHandler.
  • ServerDiscoverHandler: New handler for server/discover — the stateless replacement for the initialize handshake. Returns InitializeResult with capabilities, server info, and instructions.
  • StatelessStreamableHttpTransport: New HTTP transport that accepts POST and OPTIONS only. No Mcp-Session-Id validation, no DELETE endpoint.

How Has This Been Tested?

  • Unit tests added for StatelessStreamableHttpTransport covering:
    • POST without session ID is accepted
    • DELETE returns 405
    • GET returns 405
    • Default middleware (CORS, DNS rebinding, protocol version)
    • Body size limits
    • Empty middleware warning
    • Custom middleware composition
  • Unit tests added for ServerDiscoverHandler covering:
    • Method support (server/discover vs others)
    • Response structure with full configuration
    • Fallback to defaults when configuration is minimal
  • Verified that existing StreamableHttpTransport tests continue to pass (no regression in stateful mode)

Breaking Changes

None. This is an opt-in feature. Existing servers continue to work unchanged. To enable stateless mode:

$server = Server::builder()
    ->setServerInfo('my-app', '1.0.0')
    ->setStateless(true)  // opt-in
    ->addTool(MyTool::class)
    ->build();

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Design decisions

  1. Reused existing Protocol class instead of creating a separate StatelessProtocol. The stateless boolean flag keeps the diff minimal and avoids code duplication for request handling, fiber management, and outgoing message queues.

  2. Ephemeral sessions are still created via SessionManager::create() so that existing handlers (tools, resources, prompts) can use $session->get() / $session->set() without knowing whether the server is stateful or stateless. The session lives only for the duration of one HTTP request.

  3. ServerDiscoverHandler returns InitializeResult — the same result type as InitializeHandler. This keeps client compatibility simple: clients receive the same shape of capabilities and server info, just via server/discover instead of initialize.

  4. StatelessStreamableHttpTransport extends BaseTransport and reuses all fiber/SSE logic from the original transport. Only the request routing layer differs (no session ID parsing, no DELETE).

@chr-hertel

Copy link
Copy Markdown
Member

Hi @andreybolonin, thanks for this - before diving into it, i read that you mostly verified by unit tests? did you also try it with one of the examples and inspector - basically going end to end with the transport?

@chr-hertel chr-hertel changed the title Add MCP stateless mode support [Server] Add MCP stateless mode support Aug 13, 2026
@chr-hertel chr-hertel added Server Issues & PRs related to the Server component 2026-07-28 All issues and PRs related to the spec release 2026-07-28 improves spec compliance Improves consistency with other SDKs such as TyepScript labels Aug 13, 2026
@chr-hertel chr-hertel added this to the 0.8.0 milestone Aug 13, 2026
@andreybolonin

Copy link
Copy Markdown
Author

Hi @chr-hertel, good catch — so far I've only covered it with unit tests.

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

Labels

2026-07-28 All issues and PRs related to the spec release 2026-07-28 improves spec compliance Improves consistency with other SDKs such as TyepScript Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants