# How to use the streamable HTTP transport for MCP

- Tool: MCP
- Difficulty: Advanced
- Time required: 25-35 min
- Compatibility: MCP SDK 1.0+, Express 4+, Hono 4+, Node.js 18+
- Last updated: March 2026

## TL;DR

Streamable HTTP is MCP's newest transport protocol, replacing SSE for remote server communication. It uses a single /mcp endpoint, supports stateless and stateful sessions via the Mcp-Session-Id header, and integrates with Express or Hono middleware. Set up involves using createMcpExpressApp or createMcpHonoApp from the SDK, configuring Origin validation for security, and handling session lifecycle.

## Streamable HTTP Transport for MCP Servers

Streamable HTTP is the recommended transport for remote MCP servers, replacing the older SSE approach. It communicates over a single HTTP endpoint, supports both request-response and server-sent event streaming, and works behind load balancers and CDNs. This tutorial covers the full setup using Express and Hono, including session management, Origin validation, and production hardening.

## Before you start

- A working MCP server built with @modelcontextprotocol/sdk
- Node.js 18+ installed
- Familiarity with Express.js or Hono web frameworks
- Understanding of HTTP headers and CORS concepts
- Basic knowledge of MCP architecture (hosts, clients, servers)

## Step-by-step guide

### 1. Install the required dependencies

The MCP SDK includes built-in Express and Hono adapters for Streamable HTTP transport. Install the SDK along with your preferred web framework. Express is the most widely used and has the largest ecosystem, while Hono is lighter and faster — choose based on your preference. Both produce identical MCP behavior.

```
# For Express
npm install @modelcontextprotocol/sdk express zod
npm install -D @types/express typescript

# For Hono
npm install @modelcontextprotocol/sdk hono zod
npm install -D typescript
```

**Expected result:** All dependencies installed and ready for development.

### 2. Create a Streamable HTTP server with Express

The SDK provides createMcpExpressApp which takes your McpServer instance and returns a fully configured Express app. This app exposes a POST /mcp endpoint for client requests and supports response streaming via Server-Sent Events when the client sends an Accept: text/event-stream header. The helper handles all protocol details including JSON-RPC framing, session creation, and connection management.

```
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { z } from "zod";

const server = new McpServer({
  name: "streamable-http-demo",
  version: "1.0.0",
});

server.tool(
  "get-weather",
  "Get current weather for a city",
  { city: z.string() },
  async ({ city }) => ({
    content: [{ type: "text", text: `Weather in ${city}: 22°C, sunny` }],
  })
);

const app = createMcpExpressApp(server);

app.listen(3000, () => {
  console.error("Streamable HTTP MCP server on http://localhost:3000/mcp");
});
```

**Expected result:** An Express server running at http://localhost:3000 with the MCP endpoint at /mcp.

### 3. Create a Streamable HTTP server with Hono

If you prefer Hono for its lightweight footprint and edge-runtime compatibility, the SDK also provides createMcpHonoApp. The API is nearly identical to the Express version. Hono is especially useful when deploying to edge platforms like Cloudflare Workers or Deno Deploy where Express is not available.

```
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpHonoApp } from "@modelcontextprotocol/sdk/server/hono.js";
import { serve } from "@hono/node-server";
import { z } from "zod";

const server = new McpServer({
  name: "hono-mcp-server",
  version: "1.0.0",
});

server.tool(
  "ping",
  "Check if the server is alive",
  {},
  async () => ({
    content: [{ type: "text", text: "pong" }],
  })
);

const app = createMcpHonoApp(server);

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.error("Hono MCP server running on port 3000");
});
```

**Expected result:** A Hono server running with the same MCP capabilities as the Express version.

### 4. Understand and configure Mcp-Session-Id

Streamable HTTP uses the Mcp-Session-Id header to maintain session state between requests. When a client sends its first request (the initialize method), the server generates a unique session ID and returns it in the response header. The client includes this ID in all subsequent requests. This enables stateful conversations while keeping individual requests stateless — perfect for load-balanced deployments. The SDK manages sessions automatically, but you should understand the flow for debugging.

```
// Session lifecycle:
// 1. Client sends POST /mcp with {"method": "initialize"}
// 2. Server responds with Mcp-Session-Id: abc123 header
// 3. Client includes Mcp-Session-Id: abc123 in all future requests
// 4. Client sends DELETE /mcp with Mcp-Session-Id to end session

// The SDK handles all of this automatically.
// To inspect sessions for debugging, you can add middleware:

import express from "express";
const debugApp = express();

debugApp.use((req, _res, next) => {
  console.error(`[${req.method}] ${req.path}`);
  console.error(`  Session: ${req.headers["mcp-session-id"] || "new"}`);
  next();
});
```

**Expected result:** You understand the Mcp-Session-Id lifecycle and can inspect session headers for debugging.

### 5. Add Origin validation for security

When exposing an MCP server over HTTP, you should validate the Origin header to prevent unauthorized browser-based access. This is especially important for servers deployed on the public internet. Add middleware that checks the Origin header against an allowlist before processing requests. The SDK does not enforce Origin validation by default, so this is your responsibility.

```
import express from "express";

const ALLOWED_ORIGINS = [
  "https://claude.ai",
  "https://your-app.com",
];

function originValidation(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const origin = req.headers.origin;
  if (origin && !ALLOWED_ORIGINS.includes(origin)) {
    res.status(403).json({ error: "Origin not allowed" });
    return;
  }
  next();
}

// Apply before MCP routes
app.use("/mcp", originValidation);
```

**Expected result:** Requests from unauthorized origins are rejected with a 403 status before reaching your MCP server logic.

### 6. Test with MCP Inspector and connect your host

Before connecting a production host, test your Streamable HTTP server with the MCP Inspector tool. Run your server, then launch the Inspector pointing to your HTTP endpoint. Verify that tools are listed, execute a test call, and check the response. Once confirmed working, update your Claude Desktop or Cursor configuration. For teams building complex MCP architectures with multiple servers and transports, the RapidDev team offers architecture consulting to help design reliable setups.

```
# Start your server
node dist/http.js

# In another terminal, run MCP Inspector
npx -y @modelcontextprotocol/inspector --transport http --url http://localhost:3000/mcp

# Then configure your host:
# .cursor/mcp.json
{
  "mcpServers": {
    "my-http-server": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

**Expected result:** MCP Inspector shows your tools and resources, and your host application connects successfully.

## Complete code example

File: `src/streamable-http-server.ts`

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import express from "express";
import { z } from "zod";

// --- Origin validation middleware ---
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || [];

function validateOrigin(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const origin = req.headers.origin;
  if (origin && ALLOWED_ORIGINS.length > 0 && !ALLOWED_ORIGINS.includes(origin)) {
    res.status(403).json({ error: "Origin not allowed" });
    return;
  }
  next();
}

// --- MCP server setup ---
const server = new McpServer({
  name: "streamable-http-production",
  version: "1.0.0",
});

server.tool(
  "search",
  "Search for items",
  { query: z.string(), limit: z.number().optional().default(10) },
  async ({ query, limit }) => {
    const results = Array.from({ length: limit }, (_, i) => ({
      id: i + 1,
      title: `Result ${i + 1} for "${query}"`,
    }));
    return {
      content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
    };
  }
);

// --- Express app with Streamable HTTP transport ---
const app = createMcpExpressApp(server);

app.use("/mcp", validateOrigin);

app.get("/", (_req, res) => {
  res.json({ status: "ok", transport: "Streamable HTTP" });
});

const PORT = parseInt(process.env.PORT || "3000", 10);
app.listen(PORT, () => {
  console.error(`Streamable HTTP server on port ${PORT}`);
});
```

## Common mistakes

- **Not handling the Mcp-Session-Id header in custom middleware** — undefined Fix: If you add custom middleware before the MCP routes, make sure it passes through the Mcp-Session-Id header. The SDK needs it to route requests to the correct session.
- **Blocking streaming responses with buffering middleware** — undefined Fix: Remove response compression or buffering middleware from the /mcp route, as it can prevent SSE streaming from working correctly.
- **Forgetting to validate Origin in production** — undefined Fix: Add Origin validation middleware for any MCP server exposed on the public internet. Without it, any website could potentially interact with your server.
- **Using GET instead of POST for MCP requests** — undefined Fix: Streamable HTTP uses POST for all client-to-server messages and GET only for server-to-client event streams. The SDK handles this automatically, but custom clients must use POST.

## Best practices

- Use createMcpExpressApp or createMcpHonoApp instead of building transport handling manually
- Always validate the Origin header in production deployments
- Add a health check endpoint separate from the /mcp route for load balancer probes
- Log session creation and destruction events for debugging connection issues
- Set appropriate CORS headers if your server will be accessed from browser-based clients
- Use environment variables for the allowed origins list so you can update without redeploying
- Test with MCP Inspector before connecting production hosts
- Monitor session counts to detect connection leaks

## Frequently asked questions

### What is the difference between Streamable HTTP and SSE transport?

Streamable HTTP uses a single endpoint (/mcp) for all communication, while SSE required two separate endpoints (one for sending, one for receiving). Streamable HTTP also supports stateless operation, making it compatible with serverless platforms and load balancers.

### Can I use Streamable HTTP with Cloudflare Workers?

Yes, use the Hono adapter since Hono is natively compatible with Cloudflare Workers. The createMcpHonoApp function returns a Hono app that works directly in edge runtimes.

### Do I need to manage sessions manually?

No, the SDK handles session creation, tracking via Mcp-Session-Id headers, and cleanup automatically. You only need to interact with sessions if you want to add custom logging or enforce session limits.

### Is Streamable HTTP backward compatible with SSE clients?

No, they are different protocols. A client built for SSE transport cannot connect to a Streamable HTTP server. However, you can run both transports on the same server by mounting them on different routes.

### How do I handle CORS for browser-based MCP clients?

Add CORS middleware to your Express or Hono app that allows the necessary origins, methods (POST, GET, DELETE), and headers (Content-Type, Mcp-Session-Id). The SDK does not add CORS headers automatically.

### What happens when the client disconnects unexpectedly?

The SDK detects closed connections and cleans up the session automatically. For long-running operations, the server can continue processing and the client can reconnect using the same Mcp-Session-Id to receive pending results.

---

Source: https://www.rapidevelopers.com/mcp-tutorial/how-to-use-streamable-http-transport
© RapidDev — https://www.rapidevelopers.com/mcp-tutorial/how-to-use-streamable-http-transport
