// Copyright 2025 The Go MCP SDK Authors. All rights reserved. // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. // TODO: move server-side streamable HTTP logic from streamable.go to this file. package mcp /* Streamable HTTP Server Design This document describes the server-side implementation of the MCP streamable HTTP transport, as defined by the MCP spec: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http # Overview The streamable HTTP transport enables MCP communication over HTTP, with server-sent events (SSE) for server-to-client messages. The implementation consists of several layered components: ┌─────────────────────────────────────────────────────────────────┐ │ [StreamableHTTPHandler] │ │ http.Handler that manages sessions and routes HTTP requests │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ [StreamableServerTransport] │ │ transport implementation, one per session; exposes ServeHTTP │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ [streamableServerConn] │ │ Connection implementation, handles message routing │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ [stream] │ │ Logical message channel within a session, may be resumed │ └─────────────────────────────────────────────────────────────────┘ # Sessions As with other transports, a session represents a logical MCP connection between a client and server. In the streamable transport, sessions are identified by a unique session ID (Mcp-Session-Id header) and persist across multiple HTTP requests. [StreamableHTTPHandler] maintains a map of active sessions ([sessionInfo]), each containing: - The [ServerSession] (MCP-level session state) - The [StreamableServerTransport] (for message I/O) - Optional timeout management for idle session cleanup Sessions are created on the first POST request (typically containing the initialize request) and destroyed either by: - Client sending a DELETE request - Session timeout due to inactivity - Server explicitly closing the session # Streams Within a session, there can be multiple concurrent "streams" - logical channels for message delivery. This is distinct from HTTP streams; a single [stream] may span multiple HTTP request/response cycles (via resumption). There are two types of streams: 1. Optional standalone SSE stream (id = ""): - Created when client sends a GET request to the endpoint - Used for server-initiated messages (requests/notifications to client) - Persists for the lifetime of the session - Only one standalone stream per session 2. Request streams (id = random string): - Created for each POST request containing JSON-RPC calls - Used to route responses back to the originating HTTP request - Completed when all responses have been sent - Can be resumed via GET with Last-Event-ID if interrupted # Message Routing When the server writes a message, it must be routed to the correct [stream]: - Responses: Routed to the stream that originated the request - Requests/Notifications made during request handling: Routed to the same stream as the triggering request (via context) - Requests/Notifications made outside request handling: Routed to the standalone SSE stream This routing is implemented using: - [streamableServerConn.requestStreams] maps request IDs to stream IDs - [idContextKey] is used to store the originating request ID in Context - [streamableServerConn.streams] maps stream IDs to [stream] objects # Stream Resumption If an HTTP connection is interrupted (network issues, etc.), clients can resume a stream by sending a GET request with the Last-Event-ID header. This requires an [EventStore] to be configured on the server. - [EventStore.Open] is called when a new stream is created - [EventStore.Append] is called for each message written to the stream - [EventStore.After] is called to replay messages after a given index - [EventStore.SessionClosed] is called when the session ends Event IDs are formatted as "_" to identify both the stream and position within that stream (see [formatEventID] and [parseEventID]). # Stateless Mode For simpler deployments, the handler supports "stateless" mode ([StreamableHTTPOptions.Stateless]) where: - No session ID validation is performed - Each request creates a temporary session that's closed after the request - Server-to-client requests are not supported (no way to receive response) This mode is useful for simple tool servers that don't need bidirectional communication. # Response Formats The server can respond to POST requests in two formats: 1. text/event-stream (default): Messages sent as SSE events, supports streaming multiple messages and server-initiated communication during request handling. 2. application/json ([StreamableHTTPOptions.JSONResponse]): Single JSON response, simpler but doesn't support streaming. Server-initiated messages during request handling go to the standalone SSE stream instead. # HTTP Methods - POST: Send JSON-RPC messages (requests, responses, notifications) - GET: Open standalone SSE stream or resume an interrupted stream - DELETE: Terminate the session # Key Implementation Details The [stream] struct manages delivery of messages to HTTP responses. Fields: - [stream.w] is the ResponseWriter for the current HTTP response (non-nil indicates claimed) - [stream.done] is closed to release the hanging HTTP request - [stream.requests] tracks pending request IDs (stream completes when empty) Methods: - [stream.deliverLocked] delivers a message to the stream - [stream.close] sends a close event and releases the stream - [stream.release] releases the stream from the HTTP request, allowing resumption [streamableServerConn] handles the [Connection] interface: - [streamableServerConn.Read] receives messages from the incoming channel (fed by POST handlers) - [streamableServerConn.Write] routes messages to appropriate streams - [streamableServerConn.Close] terminates the session and notifies the [EventStore] */