MCP Transport Options
Three ways MCP servers communicate -- stdio for local, SSE (deprecated), and Streamable HTTP for remote. When to use each, how sessions work, and testing with the MCP Inspector.
Everything you've built so far uses stdio -- standard input/output. It runs locally on your machine and communicates by piping text between processes. That's perfect for development.
For production -- when you want a company to run an MCP server that anyone can connect to -- you need a remote transport. This post covers all three options.
The Three Transports
stdio -- The Local Transport
You've been using this all along. The process starts, hangs on stdin, and your MCP client pipes messages in and reads responses out.
node mcp.js
# Process starts, waits for input on stdin
# Client sends: { "jsonrpc": "2.0", "id": 1, "method": "tools/list", ... }
# Server responds to stdout
# Client reads the responseWhen to use stdio:
- Local development and testing
- Tools that need file system access (can't do remotely)
- Claude Desktop, Tome, Cursor local integrations
- Anything that can't or shouldn't be on the network
Why it'll never go away: If you have a tool that creates, reads, or deletes files on your computer, it has to run locally. You can't delegate file system access to a remote server. stdio is the only option for these cases.
SSE -- Server-Sent Events (Deprecated)
SSE was the first attempt at a remote MCP transport, released November 2024. Deprecated March 2025 -- a 4-month lifespan.
The idea was good: use the web's existing SSE technology (one-way real-time push from server to client) to bridge the gap between stdio and the internet.
Why it was deprecated:
- Stateful -- session state lived in memory on one server
- Not horizontally scalable -- can't load-balance across multiple instances
- Network fragility -- dropped connection = lost session, no recovery
- Two endpoints -- had a
/messagesendpoint AND a SSE stream endpoint to manage
Do you need to know it? Just know it exists. You'll encounter legacy servers that still use it (Neon supports it for backward compatibility). Don't build new ones.
Streamable HTTP -- The Modern Remote Transport
Released after SSE's deprecation, Streamable HTTP is the right way to run a remote MCP server.
Key improvements over SSE:
| Feature | SSE | Streamable HTTP |
|---|---|---|
| Endpoints | Two (/messages + SSE stream) | One (/mcp) |
| State | In-memory, one server | Session ID in HTTP header |
| Network drops | Session lost | Resumable via session ID |
| Horizontal scaling | Hard | Easy -- store sessions in Redis |
| Serialization | Must be serializable | Must be serializable |
Building a Streamable HTTP Server
Here's the complete implementation using Express:
// streamable.js
import express from "express";
import { randomUUID } from "crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import registerJobsTools from "./jobs-based-tools.js";
const app = express();
app.use(express.json());
// In-memory session store (use Redis in production)
const sessions = {};
function createMcpServer() {
const server = new McpServer({ name: "issue-server", version: "1.0.0" });
registerJobsTools(server);
return server;
}
app.post("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (req.body.method === "initialize") {
// New session
const newSessionId = randomUUID();
const transport = new StreamableHTTPServerTransport({ sessionId: newSessionId });
const server = createMcpServer();
await server.connect(transport);
sessions[newSessionId] = { transport, server };
res.setHeader("Mcp-Session-Id", newSessionId);
await transport.handleRequest(req, res);
} else if (sessionId && sessions[sessionId]) {
// Existing session
const { transport } = sessions[sessionId];
await transport.handleRequest(req, res);
} else {
res.status(400).json({ error: "No valid session" });
}
});
// Cleanup on close
app.delete("/mcp", (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (sessionId && sessions[sessionId]) {
delete sessions[sessionId];
}
res.status(200).end();
});
app.listen(3001, () => {
console.log("MCP server running on http://localhost:3001/mcp");
});Install Express:
npm install expressRun it:
node streamable.js
# MCP server running on http://localhost:3001/mcpDNS Rebinding Protection
The code includes this flag:
new StreamableHTTPServerTransport({
sessionId: newSessionId,
// ⚠️ Only disable in development:
// dangerouslyDisableDnsRebindingProtection: true
});DNS rebinding is an attack where a malicious site hijacks your session by serving responses from a different origin than the one your browser established the connection with.
The MCP Inspector
The MCP Inspector is an official debugging tool for MCP servers -- a web interface that lets you call tools, read resources, and inspect messages without needing a full client.
# Run the inspector (connects to your running Streamable HTTP server)
npx @modelcontextprotocol/inspector
# Choose transport type: Streamable HTTP
# URL: http://localhost:3001/mcp
# Click ConnectWhat you can do with it:
- See all registered tools with their schemas
- Call any tool directly with test inputs
- See the raw request and response
- View the full message history
- Test resources and prompts
This is far more convenient than writing raw echo commands. Use it for all Streamable HTTP development.
Production Session Management
The in-memory sessions object works for development. For production with multiple server instances, move sessions to Redis:
// Production pattern (conceptual)
import Redis from "ioredis";
const redis = new Redis();
// On session create:
await redis.set(`mcp-session:${sessionId}`, JSON.stringify(sessionData), "EX", 3600);
// On each request:
const sessionData = await redis.get(`mcp-session:${sessionId}`);This makes your MCP server horizontally scalable -- any instance can handle any request from any client.
Lab -- Understand the Session Flow
Lab 2 -- Pick the Right Transport
Key Takeaways
- stdio -- local only. Will always exist. Use for local tools, file access, development.
- SSE -- deprecated March 2025. Stateful, fragile, not scalable. Don't build new ones.
- Streamable HTTP -- modern remote standard. One endpoint, session IDs, resumable, scalable.
- Session IDs live in the
Mcp-Session-IdHTTP header -- any server instance can handle any request - MCP Inspector is your best debugging tool for Streamable HTTP servers
- Disable DNS rebinding protection only in dev, never in production
What's Next
You know how to build and deploy MCP servers. Now let's put them to work in a real coding workflow -- installing pre-built MCP servers into Claude Code and Cursor, and seeing what "vibe coding" actually looks like.
Keep reading