You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Server] SEP-2567: Remove session dependency for list endpoints; deprecate Mcp-Session-Id #341
Removes protocol-level sessions: no session/create, no session/destroy, no Mcp-Session-Id header. tools/list / resources/list / prompts/list MUST NOT depend on per-connection or prior-call state — list endpoints become cacheable at (deployment, auth) granularity. Stateful workflows use server-minted opaque handles passed as parameters.
PHP SDK changes
src/Server/Session/* — entire directory becomes optional/internal-only. SessionManager, InMemorySessionStore, FileSessionStore, Psr16SessionStore, SessionInterface — keep for back-compat with old clients but cannot drive list responses.
src/Server/Transport/StreamableHttpTransport.php — stop minting/requiring Mcp-Session-Id; downgrade to optional legacy header.
src/Capability/Registry/Registry.php and all List*Handler.php — must produce identical results regardless of session. Audit handlers that read from $session->get(...).
RequestContext — sessions become optional, not guaranteed.
Re: honoring session optionality under the current (2025-11-25) spec, ahead of the breaking 2026-07-28 work
This SEP scopes the sessionless work to the 2026-07-28 release as a coordinated breaking change. I'd like to flag that the current2025-11-25 spec already makes session management optional (MAY), yet the transport hard-requires it today — so there's a smaller, non-breaking slice worth considering independently.
Current behaviour (mcp/sdk 0.6.0, Streamable HTTP):Protocol::resolveSession() always mints a session on initialize (calling $transport->setSessionId(...), so Mcp-Session-Id is always emitted), and hard-rejects every non-initialize request that lacks a stored session:
if (!$sessionId) → 400 "A valid session id is REQUIRED for non-initialize requests."
if (!$this->sessionManager->exists($id)) → 404 "Session not found or has expired."
Per the 2025-11-25 spec, session management is entirely MAY — a server is free not to issue a session id and to treat each POST independently. There's currently no supported way to run a spec-compliant stateless server with the SDK.
Why it matters: many deployments are stateless by nature — serverless/FaaS, autoscaled multi-instance apps, and MCP servers that just wrap an existing REST API (auth + params on every request, no subscriptions, no server-initiated messages). Because each HTTP request is a separate PHP process, the current design forces a persistent cross-request (and cross-node) session store purely to satisfy a handshake the spec says is optional.
Proposed non-breaking slice: an opt-in Server::builder()->stateless() (or ->withoutSessions()) that, when enabled:
skips setSessionId() on initialize, so no Mcp-Session-Id header is emitted, and
in resolveSession(), skips the !$sessionId / !exists() rejections and uses an ephemeral per-request session.
Stateful stays the default; statelessness becomes a one-liner. This is aligned with #341's direction (making Mcp-Session-Id optional and decoupling handlers from session state) but could land ahead of, and independently from, the breaking 2026-07-28 overhaul in #334.
Current workaround (for reference): supplying a non-persistent SessionStoreInterface (exists() → true, read() → fresh state, write()/save() → no-op) makes each request self-contained without a real store — but the server still emits an unused Mcp-Session-Id and it relies on resolveSession()'s exact internals rather than a supported mode.
Would a non-breaking opt-in flag under the current spec be in scope here, or would you prefer it tracked as its own issue?
Implemented via #444, #455. StatelessProtocol::handle() builds a fresh, throwaway session per request, so list-endpoint output can't depend on prior calls.
Activity
DevkumarPatel commentedon Jul 7, 2026
Re: honoring session optionality under the current (2025-11-25) spec, ahead of the breaking 2026-07-28 work
This SEP scopes the sessionless work to the 2026-07-28 release as a coordinated breaking change. I'd like to flag that the current 2025-11-25 spec already makes session management optional (
MAY), yet the transport hard-requires it today — so there's a smaller, non-breaking slice worth considering independently.Current behaviour (mcp/sdk 0.6.0, Streamable HTTP):
Protocol::resolveSession()always mints a session oninitialize(calling$transport->setSessionId(...), soMcp-Session-Idis always emitted), and hard-rejects every non-initialize request that lacks a stored session:if (!$sessionId)→ 400 "A valid session id is REQUIRED for non-initialize requests."if (!$this->sessionManager->exists($id))→ 404 "Session not found or has expired."Per the 2025-11-25 spec, session management is entirely
MAY— a server is free not to issue a session id and to treat each POST independently. There's currently no supported way to run a spec-compliant stateless server with the SDK.Why it matters: many deployments are stateless by nature — serverless/FaaS, autoscaled multi-instance apps, and MCP servers that just wrap an existing REST API (auth + params on every request, no subscriptions, no server-initiated messages). Because each HTTP request is a separate PHP process, the current design forces a persistent cross-request (and cross-node) session store purely to satisfy a handshake the spec says is optional.
Proposed non-breaking slice: an opt-in
Server::builder()->stateless()(or->withoutSessions()) that, when enabled:setSessionId()oninitialize, so noMcp-Session-Idheader is emitted, andresolveSession(), skips the!$sessionId/!exists()rejections and uses an ephemeral per-request session.Stateful stays the default; statelessness becomes a one-liner. This is aligned with #341's direction (making
Mcp-Session-Idoptional and decoupling handlers from session state) but could land ahead of, and independently from, the breaking 2026-07-28 overhaul in #334.Current workaround (for reference): supplying a non-persistent
SessionStoreInterface(exists()→true,read()→ fresh state,write()/save()→ no-op) makes each request self-contained without a real store — but the server still emits an unusedMcp-Session-Idand it relies onresolveSession()'s exact internals rather than a supported mode.Would a non-breaking opt-in flag under the current spec be in scope here, or would you prefer it tracked as its own issue?
chr-hertel commentedon Aug 19, 2026
Implemented via #444, #455.
StatelessProtocol::handle()builds a fresh, throwaway session per request, so list-endpoint output can't depend on prior calls.