MCP Streamable HTTP Session Resumption: Sessions, SSE Cursors, and Replay Boundaries
An SSE disconnect, HTTP 404, and DELETE do not signify the same kind of termination. This article binds the MCP session, protocol version, original stream, and Last-Event-ID to prevent cross-stream replay, session hijacking, and duplicate side effects.
Key Points
- Topic Focus: AI Agent、MCP、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
A tool request opens an SSE stream through POST. The Server has already performed a write, but the connection drops before it returns the JSON-RPC response. The Client treats the network disconnect as cancellation and retries with a new request ID; at the same time, it uses the Last-Event-ID from another GET stream for resumption. The result may be both a duplicate write and messages that do not belong to that request entering the state machine.
Streamable HTTP has a single MCP endpoint that supports both POST and GET, but this does not mean that every connection shares a single message stream without boundaries. MCP Timeouts, Retries, and Idempotency addresses retries for business requests, while State Machines for Long-Running Tool Operations handles asynchronous terminal states; this article defines the relationship between transport sessions, SSE streams, and replay cursors.
Track Sessions, Connections, Streams, and Requests Separately
The Server can return a cryptographically secure MCP-Session-Id in the HTTP response containing InitializeResult. Once returned, the Client must include it in every subsequent HTTP request; the Server should return 400 when the ID is missing, and a terminated ID must receive 404. When the Client receives a 404 for a request carrying a session ID, it must reinitialize without an ID rather than moving the old request directly into a new session. When intentionally leaving, the Client should send DELETE; a 405 means only that the Server does not support client-initiated termination and must not be turned into a fabricated deletion receipt.
The mcpHttpSession.v1 ledger records an HMAC of the session ID, the principal, handshake receipt, protocol version, Origin policy, authorization subject, creation and expiration times, and generation. It must never write the raw session ID to ordinary logs. mcpSseStream.v1 separately stores the stream ID, initiation method POST/GET, associated request ID and payload digest, last sent and last acknowledged event IDs, retry recommendation, and reason for closure. Within a session, event IDs must be globally unique and contain enough information to locate the original stream; the Server may replay missed messages only on that stream and must not broadcast them across streams.
SESSION_ACTIVE -> STREAM_OPEN -> DISCONNECTED -> RESUME_PENDING
RESUME_PENDING -> STREAM_OPEN | CURSOR_REJECTED | SESSION_EXPIRED
SESSION_ACTIVE -> DELETE_REQUESTED -> TERMINATED
SESSION_EXPIRED | TERMINATED -> REINITIALIZE_REQUIRED
Resumption always uses the GET endpoint and includes Last-Event-ID. The Client first verifies that the cursor belongs to the current session and original stream, then requests replay from the last accepted watermark in its local durable inbox; when it receives a duplicate event, it deduplicates it by (sessionGeneration, streamId, eventId, messageDigest). A disconnected connection does not mean that the request was canceled; cancellation requires sending an MCP CancelledNotification. The SSE retry supplied by the Server is the reconnect delay in milliseconds. The Client must honor it and also apply the global reconnect budget to prevent traffic amplification during network outages.
The single message acceptance point acceptStreamEvent validates the session, protocol header, authorization subject, stream, monotonic event cursor, JSON-RPC correlation, and content digest, then atomically writes the inbox receipt and business dispatch outbox. A JSON-RPC response may close only its associated POST stream; a standalone GET stream must not carry a response unless it is resuming an earlier POST stream. This allows a late response to complete the original request without creating a second side effect when the browser reconnects. Agent Runtime State Machine reads only accepted inbox entries.
Include Transport Security in Resumption Tests
Every connection must validate Origin, returning 403 for an invalid Origin; a local Server should bind only to loopback, while remote connections require authentication. A session ID cannot replace user identity or authorization, and MCP Authentication Boundaries must still validate the token subject. Ingress DNS rebinding defenses can reuse the resolution principles from Agent Egress SSRF and DNS Rebinding Defenses.
Fault injection should cover loss of the initial empty SSE event, crossed event IDs from two streams, replay after an ACK, an incorrect Last-Event-ID, simultaneous session expiration and response arrival, loss of the DELETE response, an old token paired with a new session, a missing version header, a proxy-modified Origin, and 429/503 reconnect storms. Release gates require zero cross-stream acceptances in 100,000 events, zero duplicate business dispatches, a new handshake for every request carrying a session ID that receives 404, a 100% rejection rate for invalid Origins, recovery p99 below 5 seconds, and reconnect QPS within budget.
For rollout, first have 1% of read-only sessions generate a shadow stream ledger, then gradually enable resumable events; ramp up traffic for write tools only when they also satisfy the idempotency receipt requirement. During rollback, disable resumption for new streams, but retain cursor indexes for old generations until they expire and drain old requests. Deleting the ledger or changing every disconnect into another POST destroys the evidence of uniqueness and is not an acceptable rollback.
Related
Related Content
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Why Does MCP Matter? Understanding the AI Tool Protocol
Technical trends, engineering practices, and an independent developer perspective on MCP and AI Agents.
Article
Core Architecture of AI Agents: Planning, Memory, Tools, and Execution
Technical trends, engineering practices, and independent developer perspectives around AI Agents and large language models.
Article
How Should RAG and Agents Work Together?
An independent developer's perspective on technical trends and engineering practices involving RAG and AI agents.