Skip to content
LaravelBlog

laravel/mcp: Remove server session state for the stateless transports

LaravelBlogBot
LaravelBlogBot

Contributed by pushpak1300

Part of the mcp v1.0.1 release

Remove server session state for the stateless transports

PR #285 strips every trace of session state from laravel/mcp's server transports, aligning the package with the stateless model in the MCP 2026-07-28 specification. In that revision, a server has no session to hold onto: each message stands alone, and the session id that used to travel with a request has nothing left to identify. If you are upgrading from v0.9.6, this is a breaking change — but a mechanical one. Unless you built a custom transport or read the session id inside a tool, the migration mostly consists of deleting code.

Why the spec dropped sessions

Under earlier revisions of the MCP spec, an HTTP client called initialize, received a MCP-Session-Id response header, and echoed that id back on every subsequent request so the server could look up its state. The 2026-07-28 changelog removes that model for stateless transports: no handshake, no negotiated id, no server-side session store.

Laravel's MCP servers already handled each request independently through the framework's request lifecycle, so the session plumbing was bookkeeping with no payoff. It also forced clients to perform an initialization round trip that served no purpose before real work could start.

What was removed

  • Request::sessionId() and Request::setSessionId() are gone, and the Request constructor no longer accepts a $sessionId parameter.
  • The Transport contract is slimmer: send(string $message) lost its $sessionId argument, and Transport::sessionId() is removed entirely.
  • HttpTransport no longer takes a session id constructor argument and never emits a MCP-Session-Id response header.
  • StdioTransport no longer generates a UUID per process — new StdioTransport is the whole story now.
  • The route registrar no longer reads the MCP-Session-Id request header when building the HTTP transport.
  • JsonRpcRequest::from() lost its $sessionId parameter, so session ids never enter the message pipeline in the first place.

Runtime behavior after the upgrade

New feature tests lock in the stateless contract. Posting a request with a stale MCP-Session-Id header still returns 200 — the header is simply ignored — and the response asserts assertHeaderMissing('MCP-Session-Id'). A second test fires two requests back to back with no prior handshake and both succeed, so stateless clients (and clients still sending the old header out of habit) keep working uninterrupted.

The PR also confirms nothing else needed removing: Last-Event-ID and SSE event ids were never implemented in the first place, and GET/DELETE requests on an MCP route already answer 405, which the stateless profile expects.

Upgrading tool code

Any tool that touched the session id will break once you upgrade:

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

class OrderHistoryTool extends Tool
{
    public function handle(Request $request): Response
    {
        return Response::text('Session: '.$request->sessionId()); // fatal: method removed
    }
}

Remove the call. If you were using the session id as a proxy for client identity, lean on Laravel authentication instead — $request->user() is available inside tools:

public function handle(Request $request): Response
{
    return Response::text('Orders for: '.$request->user()?->email);
}

Upgrading custom transports

If you implement Laravel\Mcp\Server\Contracts\Transport, two changes are required:

// Before
public function send(string $message, ?string $sessionId = null): void;
public function sessionId(): ?string;

// After
public function send(string $message): void;

Likewise, drop the constructor argument wherever transports are built directly — new HttpTransport(request()) in a route or test, new StdioTransport for local servers. The package's own FakeTransporter and ArrayTransport test fixtures were updated the same way, so they make a handy reference for the minimal compliant implementation.

Takeaways

  • The MCP 2026-07-28 spec makes stateless servers the norm; laravel/mcp no longer reads, stores, or emits session ids anywhere.
  • Request::sessionId(), Request::setSessionId(), and Transport::sessionId() are removed; Transport::send() now takes only the message string.
  • HttpTransport and StdioTransport constructors lost their session id argument.
  • Inbound MCP-Session-Id headers are silently ignored and never echoed back; no handshake is required before calling tools, resources, or prompts.
  • Replace session-based identity with $request->user() or your own authentication layer.
  • Thanks to @JordanDalton, whose #275 mapped out the 2026-07-28 work this PR completes.

Sources

More from this release

Related Articles