Streamable HTTP
Purpose and Scope
Streamable HTTP is the SDK serving path for hosting one MCP endpoint that many clients can reach over ordinary web infrastructure. The server-side entry point is a handler built from a factory, not a long-lived server object, so the same endpoint can be mounted in web-standard runtimes, Node HTTP, Express, Fastify, or Hono. Use this path when a host should connect to a network endpoint such as an MCP route, and use stdio instead when the host launches the server as a local child process. Sources: docs/serving/http.md
The central idea is that application code describes tools, resources, and prompts inside a factory that constructs a fresh server for each request. That keeps the HTTP endpoint naturally stateless by default and makes horizontal scaling straightforward, because the handler does not depend on in-memory session state between requests. If a deployment needs resumability, multi-node fan-out, or durable per-user state, treat those as explicit architecture concerns layered around the handler rather than accidental properties of a shared server instance. Sources: docs/serving/http.md
Relevant Source Files
- docs/serving/http.md — Defines the Streamable HTTP serving model, the handler factory, per-request context, mounting options, shutdown and notification hooks, and the security boundary around the handler.
- docs/serving/express.md — Shows how Express mounts the same handler through the Node adapter, including parsed body forwarding, DNS rebinding protection, bearer-auth forwarding, and a curl verification request.
- docs/serving/fastify.md — Shows the Fastify route shape, raw Node request and response forwarding, parsed JSON body handling, DNS rebinding protection, and auth propagation through the raw request.
- docs/serving/hono.md — Shows the web-standard Hono path, where the handler fetch method receives the raw Request directly with parsed body and auth data passed through route context.
- docs/serving/authorization.md — Explains bearer-token enforcement in front of the MCP route, token verification, required scopes, OAuth challenges, and protected resource metadata publication.
- docs/serving/legacy-clients.md — Explains the legacy posture for 2025-era clients, including stateless fallback, rejection, legacy request classification, and routing in front of a strict modern handler.
Handler and Factory Model
Create the HTTP serving surface with createMcpHandler from the server package. It accepts a function that returns a freshly configured server, commonly an McpServer with registered tools. The returned object exposes a web-standard fetch-style handler, so at that point nothing is listening on a port yet. A simple notes service registers an add-note tool inside the factory and returns the server; test code or a real client can then drive the handler through normal HTTP requests. The same handler also exposes shutdown and notification facilities for code that needs lifecycle cleanup or change publication. Sources: docs/serving/http.md
The per-request factory is the most important design constraint to internalize. The factory runs once for each HTTP request, and the handler itself keeps no application instance between requests. Registering tools on a shared server object outside the factory defeats that model and can leak per-caller assumptions across requests. Expensive resources should instead live at module scope as pools, caches, or clients that the factory closes over, while the MCP server object remains cheap, request-specific, and built around the current caller and protocol context. Sources: docs/serving/http.md
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const handler = createMcpHandler(({ authInfo }) => {
const server = new McpServer({ name: 'notes', version: '1.0.0' });
server.registerTool(
'whoami',
{ description: 'Name the authenticated caller', inputSchema: z.object({}) },
async () => ({ content: [{ type: 'text', text: authInfo?.clientId ?? 'anonymous' }] })
);
return server;
});Mounting on Runtimes and Frameworks
On web-standard runtimes, the handler can be exported directly because it already speaks in terms of Request and Response. On Node, wrap it once with toNodeHandler and pass the adapted function to the HTTP server or to a framework route. Express mounts it by creating an app with createMcpExpressApp, adapting with toNodeHandler, and routing all methods for the MCP endpoint to the adapter while passing the parsed body as the third argument. That body pass-through matters because Express has already consumed the request stream. Sources: docs/serving/http.md, docs/serving/express.md
Fastify and Hono follow the same conceptual shape while reflecting their runtime models. Fastify hands the adapter request.raw, reply.raw, and request.body, so the SDK receives the Node primitives plus the already parsed body. Hono does not need the Node adapter at all; its route calls handler.fetch with the raw web Request and an options object containing parsedBody. In every framework guide, the route is just the mount point. The actual MCP behavior still comes from the same per-request server factory. Sources: docs/serving/fastify.md, docs/serving/hono.md
// Node HTTP shape
createServer(toNodeHandler(handler)).listen(3000);
// Express shape
app.all('/mcp', (req, res) => void node(req, res, req.body));
// Hono shape
app.all('/mcp', c => handler.fetch(c.req.raw, { parsedBody: c.get('parsedBody') }));Request Handling, Responses, and Client Interaction
A Streamable HTTP endpoint receives JSON-RPC requests over HTTP and can return an SSE message event carrying the JSON-RPC response. The framework guides verify the route by posting a tools/list request with Content-Type set to application/json and Accept set to both application/json and text/event-stream. The example response is a single event named message whose data contains the tools/list result. From the client side, an HTTP transport connects to the endpoint URL and connect performs the initialize handshake before higher-level operations such as listing tools or calling a tool. Sources: docs/serving/express.md, docs/serving/fastify.md, docs/serving/hono.md, docs/serving/http.md
The request context gives the factory the information it needs to tailor the server without keeping global per-user state. The context includes the protocol era, authentication information, and the inbound request. Era identifies which protocol revision the request is speaking, while authInfo is the authenticated caller data supplied by middleware or the hosting framework. A whoami tool can therefore be registered per request and answer with the current client identifier, but the registration still happens in a fresh server instance that is discarded after the request completes. Sources: docs/serving/http.md
Security, Authorization, and Host Validation
The raw handler trusts the code that calls it: Host checks, Origin checks, and token verification belong in front of the handler. The framework helper apps provide DNS rebinding protection by validating Host and Origin, with safe localhost defaults and explicit allowedHosts or allowedOrigins when binding to public interfaces. Requests from non-browser MCP clients commonly have no Origin header, and the docs describe those as unaffected by the browser-oriented protection. This separation keeps protocol handling focused while making deployment security visible at the framework boundary. Sources: docs/serving/http.md, docs/serving/express.md, docs/serving/fastify.md, docs/serving/hono.md
For bearer-token deployments, the MCP server acts as an OAuth resource server. It verifies access tokens issued elsewhere and does not issue tokens itself. The Express authorization helper builds middleware from an OAuth token verifier, optional required scopes, and a protected resource metadata URL, then mounts before the MCP route. Missing, malformed, expired, or scope-deficient tokens become standard OAuth-style HTTP errors and challenges. When verification succeeds, auth information is forwarded so request handlers can read it through the HTTP context and make per-caller decisions. Sources: docs/serving/authorization.md, docs/serving/express.md
Sessions, Legacy Clients, and Scaling Choices
The default Streamable HTTP serving model is stateless for modern requests and also provides a legacy posture for 2025-era clients. The legacy guide describes createMcpHandler with a default stateless legacy fallback, where each legacy request also receives a fresh instance and standalone legacy SSE or session termination methods are not served. Setting legacy to reject makes the endpoint modern-only and returns an unsupported protocol version error for a legacy initialize request, while still letting modern error handling own malformed modern traffic. Sources: docs/serving/legacy-clients.md
If you already operate a sessionful legacy deployment, route in front of a strict modern handler instead of trying to make the new entry point hold legacy sessions. The exported legacy request predicate classifies a Request the same way the handler does, so the routing branch can send legacy traffic to existing infrastructure and send everything else to the strict handler. This is the safest transition pattern because the modern path remains stateless and explicit, while the legacy path keeps its own event store, sessions, and client compatibility contract. Sources: docs/serving/legacy-clients.md
Compact Reference
| Concern | Primary API or setting | Behavior |
|---|---|---|
| Build HTTP handler | createMcpHandler(factory) | Returns a web-standard handler whose factory creates a fresh server per request. |
| Per-request context | era, authInfo, requestInfo | Lets the factory tailor registration to protocol revision and authenticated caller. |
| Node mount | toNodeHandler(handler) | Adapts the web-standard handler to Node request and response objects. |
| Express helper | createMcpExpressApp | Provides Express defaults, JSON parsing, and Host/Origin validation. |
| Fastify helper | createMcpFastifyApp | Provides Fastify defaults and Host/Origin validation while forwarding raw Node objects. |
| Hono helper | createMcpHonoApp | Provides Hono defaults, parsed body storage, and web-standard fetch mounting. |
| Bearer auth | requireBearerAuth | Verifies tokens before the MCP route and forwards auth information. |
| Legacy posture | legacy: 'stateless' or 'reject' | Either serves 2025-era requests without sessions or rejects them as unsupported. |
Next Steps
Start with the framework page that matches your runtime, then return to the HTTP model whenever a behavior depends on the per-request factory or request context. For public endpoints, add Host and Origin validation before exposing the route and add bearer authorization when tools or resources are caller-specific. If an existing deployment still needs sessionful 2025-era clients, design an explicit routing split rather than hiding legacy state inside the modern handler. After the route works, read the sessions and scaling guidance before adding resumability, fan-out, or shared notification delivery. Sources: docs/serving/http.md, docs/serving/authorization.md, docs/serving/legacy-clients.md