Introduction

The Streamable HTTP transport is the recommended way to connect to remote MCP servers. It replaces the legacy Server-Sent Events (SSE) transport with a simpler design that uses a single HTTP endpoint. This lesson covers how it works, session management, and when to choose it over stdio.

Key Concepts

  • Streamable HTTP: The standard remote transport for MCP. The client sends JSON-RPC requests as HTTP POST to a single endpoint, and the server responds with JSON or an SSE stream.
  • Single endpoint: All communication goes through one URL (e.g., /mcp), unlike the legacy SSE transport which required separate endpoints.
  • MCP-Session-Id: A server-issued header that identifies the session. The client must include it in all subsequent requests.
  • MCP-Protocol-Version: A required header specifying the protocol version the client is using.
  • SSE streaming: The server can respond with text/event-stream for long-running operations, streaming partial results as SSE events.

Real World Context

Streamable HTTP is the transport you use when the MCP server runs on a different machine from the client. A cloud-deployed database server, a shared team tool server, or a multi-tenant SaaS service would all use Streamable HTTP. Unlike stdio, which requires the server to be a local subprocess, Streamable HTTP works across networks and supports multiple concurrent clients through session management.

Deep Dive

How Streamable HTTP Works

The client sends all JSON-RPC messages as HTTP POST requests to a single server endpoint. The server can respond in two ways:

  1. Immediate JSON response — For simple requests, the server returns application/json with the JSON-RPC response.
  2. SSE stream — For long-running operations or server-initiated messages, the server returns text/event-stream and sends one or more SSE events containing JSON-RPC messages.

The client signals what it accepts using the Accept header.

Initialization

The first request is always an initialize call. Here is a complete example using curl:

bash
# Initialize
curl -X POST https://example.com/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"MyCLI","version":"1.0.0"}}}'

The server responds with its capabilities and issues an MCP-Session-Id header in the response. This curl command shows the raw HTTP interaction that the SDK handles automatically.

Session Management

After initialization, the client must include the MCP-Session-Id and MCP-Protocol-Version headers in every subsequent request:

bash
# Subsequent request with session
curl -X POST https://example.com/mcp -H "MCP-Session-Id: abc123" -H "MCP-Protocol-Version: 2025-11-25" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

The session ID ties all requests from a single client together, allowing the server to maintain state across requests. This is how the server knows which client is making each request.

Server-Initiated Messages via GET

The client can open a long-lived SSE connection using an HTTP GET request to the same endpoint. This allows the server to push notifications (like resource updates or progress events) without the client polling:

text
GET /mcp
MCP-Session-Id: abc123
Accept: text/event-stream

The server responds with an SSE stream that remains open for the duration of the session. This is optional and only needed when the server needs to send unsolicited messages.

Terminating a Session

To explicitly end a session, the client sends an HTTP DELETE request:

text
DELETE /mcp
MCP-Session-Id: abc123

The server cleans up any resources associated with that session. If the client disconnects without sending DELETE, the server should eventually time out the session.

When to Use Streamable HTTP

Choose Streamable HTTP when:

  • The server runs on a remote machine or in the cloud
  • Multiple clients need to connect to the same server
  • You need authentication (OAuth 2.1, API keys)
  • The server is shared across a team or organization

Common Pitfalls

  1. Forgetting the session header — After initialization, every request must include MCP-Session-Id. Omitting it causes the server to reject the request or create a new session, losing context.
  2. Not handling both response types — The server may respond with either application/json or text/event-stream. The client must check the Content-Type header and parse accordingly.
  3. Using Streamable HTTP for local servers — If the server runs on the same machine, stdio is simpler and avoids the overhead of HTTP. Use Streamable HTTP only when network communication is actually needed.
  4. Not validating the Origin header — Per the MCP spec, servers using Streamable HTTP transport MUST validate the Origin header on all incoming requests to prevent DNS rebinding and cross-site attacks. Servers running locally SHOULD also bind only to localhost to limit exposure.

Best Practices

  1. Always send the Accept header — Include Accept: application/json, text/event-stream so the server can choose the appropriate response format.
  2. Implement session recovery — If the session ID becomes invalid (server restart), catch the error and re-initialize. Store enough state to resume without losing context.
  3. Use HTTPS in production — Streamable HTTP transmits JSON-RPC messages including potentially sensitive tool arguments. Always use TLS for remote connections.

Summary

  • Streamable HTTP is the recommended remote transport, replacing the legacy SSE transport.
  • All communication goes through a single endpoint via HTTP POST, with responses as JSON or SSE streams.
  • Sessions are managed with the MCP-Session-Id header, issued by the server during initialization.
  • GET opens an SSE stream for server-initiated messages; DELETE terminates the session.
  • Use Streamable HTTP for remote servers, cloud deployments, and multi-user scenarios.

Code Examples

typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://example.com/mcp")
);

const client = new Client(
  { name: "my-app", version: "1.0.0" },
  { capabilities: {} }
);

await client.connect(transport);

// The SDK handles session headers automatically
const tools = await client.listTools();
console.log("Remote tools:", tools);
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true }
    },
    "serverInfo": {
      "name": "my-remote-server",
      "version": "2.0.0"
    }
  }
}
✓ Completed