MCP Transport Layer

stdio for local servers, SSE for legacy remote, Streamable HTTP for modern stateless remote — when to use each and how they differ.

March 18, 20263 min read

An MCP server is a process that speaks JSON-RPC 2.0. The transport is the mechanism that carries those messages between the client and server.

MCP supports three transports:

TransportWhen to use
stdioLocal servers — runs as a subprocess of the client
SSE (Server-Sent Events)Legacy remote servers — deprecated in newer spec
Streamable HTTPModern remote servers — single endpoint, stateless

MCP Transport Comparison ExpandMCP Transport Comparison


stdio — The Local Default

stdio is the simplest transport, and it's what I use for everything local. The MCP client spawns your server as a child process and communicates via stdin/stdout. JSON-RPC messages flow in on stdin, responses come back on stdout.

TypeScript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const transport = new StdioServerTransport(); await server.connect(transport);

This is what claude_desktop_config.json uses. Claude Desktop reads the command and args fields, spawns the process, and talks to it over stdio.

Advantages: Zero network overhead, simple setup, works without any server infrastructure. Limitation: Only works when client and server run on the same machine. A remotely hosted MCP server can't use stdio.


SSE — Server-Sent Events (Legacy)

SSE was the original remote transport for MCP. It uses:

  • One SSE endpoint (GET /sse) for server → client messages (the streaming channel)
  • One POST endpoint for client → server messages

This bidirectional setup over HTTP works but is stateful — each session requires a persistent SSE connection. That means your server needs session affinity (sticky routing), which complicates load balancing and scaling.

SSE is supported in the MCP SDK but deprecated in newer versions of the spec. I'd avoid it for new servers. Existing servers using SSE still work, but migrate to Streamable HTTP when you get the chance.


Streamable HTTP — The Modern Standard

Streamable HTTP is the current recommended transport for remote MCP servers. It uses a single endpoint (POST /mcp) for everything.

TypeScript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import express from "express"; const app = express(); app.use(express.json()); app.post("/mcp", async (req, res) => { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await server.connect(transport); await transport.handleRequest(req, res, req.body); }); app.listen(3000);

Why it's better than SSE:

  • Stateless — no persistent connection required
  • Single endpoint — simpler routing and load balancing
  • Works behind standard HTTP infrastructure (CDNs, proxies, serverless)

How streaming works: For responses that stream (like sampling), the server uses HTTP chunked transfer encoding. The client reads chunks as they arrive. The endpoint is the same — just the response type differs.


Picking the Right Transport

Plain text
Is the server running on the same machine as the client? Yes → stdio No → Streamable HTTP Do you have legacy code using SSE? Yes → Plan a migration to Streamable HTTP No → Skip SSE entirely

For learning and local development, stdio is always the right choice. For a server you want to deploy and share — a weather API, a team's shared issue tracker, a company's internal tools — Streamable HTTP is the path.


Remote Server Considerations

When you move from stdio to a remote transport, new concerns appear that I think are worth taking seriously from the start:

  • Authentication: How does the server know who's calling? API keys, OAuth, JWTs.
  • Authorization: Which user can call which tools? Not all callers should have the same access.
  • Rate limiting: Remote servers can be called by multiple clients concurrently.
  • HTTPS: Always use TLS for remote servers — MCP messages can contain sensitive data.

The MCP spec leaves auth to the implementer. For simple cases, an API key in an HTTP header works. For team or production deployments, use a proper auth flow.

Further Reading

Enjoyed this? Get more like it.

Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.