Guide · Transport
MCP server transport comparison
Every MCP server must choose a transport — or support several. The three options differ in where the server runs, who can connect to it, how it scales, and whether it can be monitored externally. Choosing the wrong transport early means a rewrite later: a stdio-only server can't be shared across a team; an SSE server can't run on Lambda. This guide helps you pick the right transport from the start and understand the migration paths when your requirements change.
Quick decision rule
Personal tool for one developer? Use stdio. Shared API for a team or public? Use Streamable HTTP. Supporting clients that haven't updated to Streamable HTTP? Add SSE alongside it. The McpServer core is transport-agnostic — you can support all three from one server binary by connecting to different transport instances at startup based on a command-line flag or environment variable.
The three transports at a glance
| Property | stdio | SSE (HTTP+SSE) | Streamable HTTP |
|---|---|---|---|
| Where server runs | Local machine (spawned by host) | Remote server or local HTTP | Remote server, serverless, or local HTTP |
| Network required | No — OS pipe | Yes — HTTP | Yes — HTTP |
| Endpoint count | None (stdin/stdout) | Two: GET /sse + POST /messages | One: POST /mcp |
| Simultaneous clients | One (the host process) | Many (one transport instance per client) | Many (one transport per session or stateless) |
| Authentication | None (implicit trust of spawning host) | Any HTTP auth (OAuth, API key, JWT) | Any HTTP auth (OAuth, API key, JWT) |
| Serverless compatible | No | No (persistent connection required) | Yes (stateless mode) |
| Horizontally scalable | No | Only with sticky sessions | Yes in stateless mode; sticky sessions in stateful |
| External monitoring | No — local process only | Yes — HTTP endpoint | Yes — HTTP endpoint |
| Spec status | Current | Legacy (pre-2025-03-26) | Current (2025-03-26+) |
| SDK version required | Any | Any | 1.1.0+ |
Use-case decision table
| Use case | Recommended transport | Reason |
|---|---|---|
| Personal productivity tool (calendar, notes, local scripts) | stdio | No server to operate; distribute via npm; no auth surface to secure |
| Local filesystem tool (read/write user's files) | stdio | File access is naturally scoped to the user's machine; no network needed |
| Distribute via npm for Claude Desktop / Cursor | stdio | npx your-server is the full install; no separate deployment |
| Shared team API (internal knowledge base, company data) | Streamable HTTP | Multiple clients, needs auth, should be monitored for uptime |
| Public MCP API (SaaS product, open ecosystem tool) | Streamable HTTP | Multi-tenant, auth required, can be registered with MCP registries |
| Serverless deployment (Lambda, Cloudflare Workers, Vercel) | Streamable HTTP (stateless mode) | No persistent connections; stateless mode fits request/response model |
| Support legacy clients (older SDK versions) | SSE + Streamable HTTP | Mount both; modern clients use Streamable HTTP; older ones fall back to SSE |
| Browser extension or web app client | SSE | EventSource is natively available in browsers; simpler than fetch-based streaming |
| LLM agent framework integration (LangChain, CrewAI) | Streamable HTTP or stdio | Depends on the framework's MCP adapter; check adapter docs for transport support |
| Development-time tool (build, test, lint, git) | stdio | Runs locally; project path available from environment; no infra overhead |
stdio in depth: strengths and limits
Strengths
- Zero deployment — the host spawns the server; no ports, no certs, no ops.
- Zero attack surface — no network listener; the only way to send it messages is to be the host process.
- Easy distribution — ship as an npm package; users install with
npm install -g your-serverand add one line to their config file. - Full OS access — the server runs as the user's process and inherits the same filesystem permissions, making filesystem tools natural.
Hard limits
- One host at a time — two host processes cannot share one stdio server instance.
- Local only — cannot serve remote clients or be monitored externally.
- No auth — the host is trusted implicitly; any process that can spawn the server can send it messages.
- State resets on disconnect — server process terminates when the host closes stdin; no session persistence.
See the full guide: MCP server stdio transport.
SSE transport in depth: strengths and limits
Strengths
- Browser-native — EventSource is available in every browser without polyfills; no fetch-streaming complexity.
- Well-understood — the dual-endpoint pattern is documented widely and supported by older MCP client versions.
- Streaming by default — the persistent SSE connection is always open; pushing real-time events to the client is simple.
Hard limits
- Persistent connection required — incompatible with serverless; cold-start cost on every new session.
- Sticky sessions needed — load balancers must route GET /sse and POST /messages for the same session to the same instance.
- Two-endpoint complexity — routing configuration, CORS setup, and session pairing are all more complex than a single-endpoint model.
- Legacy spec status — new client SDKs default to Streamable HTTP; SSE support may be dropped in future versions.
See the full guide: MCP server SSE transport.
Streamable HTTP in depth: strengths and limits
Strengths
- Single endpoint — one
POST /mcproute to configure, CORS, and secure. No routing coordination. - Serverless compatible — stateless mode makes each request self-contained; works on Lambda, Cloudflare Workers, and Vercel.
- Flexible response mode — inline JSON for fast tools; SSE streaming for tools with progress notifications. Automatic, not configured.
- Spec current — the preferred transport for new deployments as of March 2025.
Limits
- SDK version requirement — needs
@modelcontextprotocol/sdk1.1.0+. Older SDK versions do not includeStreamableHTTPServerTransport. - Client compatibility — very old client implementations may not support Streamable HTTP; check your clients' SDK versions before dropping SSE.
- Stateful mode still needs sticky sessions — if you use stateful sessions with in-memory state, load balancers still need session affinity.
See the full guide: MCP server Streamable HTTP transport.
Supporting multiple transports from one server
The McpServer core is transport-agnostic. You can connect it to different transport types at startup based on a flag, environment variable, or a CLI argument — or mount all three simultaneously in an Express app to serve different client populations:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
function createServer() {
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// Register all tools once here — same tools regardless of transport
return server;
}
const mode = process.env.MCP_TRANSPORT ?? 'stdio';
if (mode === 'stdio') {
// Local development and Claude Desktop / Cursor integration
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
} else {
// Shared deployment — HTTP with both SSE and Streamable HTTP
const app = express();
app.use(express.json());
const sseSessions = new Map();
// SSE transport — for legacy and browser clients
app.get('/sse', async (req, res) => {
const transport = new SSEServerTransport('/messages', res);
sseSessions.set(transport.sessionId, transport);
transport.onclose = () => sseSessions.delete(transport.sessionId);
await createServer().connect(transport);
});
app.post('/messages', async (req, res) => {
const t = sseSessions.get(req.query.sessionId);
t ? await t.handlePostMessage(req, res) : res.status(404).end();
});
// Streamable HTTP — for modern clients
const httpSessions = new Map();
app.post('/mcp', async (req, res) => {
const sid = req.headers['mcp-session-id'];
if (sid) {
const t = httpSessions.get(sid);
t ? await t.handleRequest(req, res, req.body) : res.status(404).end();
return;
}
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (id) => httpSessions.set(id, transport),
});
transport.onclose = () => httpSessions.delete(transport.sessionId);
await createServer().connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
}
The tool registration code runs once in createServer() and is identical across all transports. Changing or adding tools requires no transport-specific changes.
Migration path: SSE to Streamable HTTP
If you have a production SSE server and want to move to Streamable HTTP:
- Update SDK — bump
@modelcontextprotocol/sdkto 1.1.0+ in package.json and runnpm install. - Add the POST /mcp handler — mount it alongside the existing GET /sse + POST /messages handlers. Do not remove the SSE handlers yet.
- Test with a modern client — connect using an updated MCP client SDK. It will automatically prefer POST /mcp over GET /sse.
- Announce the migration to clients — notify client teams to update their SDK versions. Give them a transition window (typically 4-8 weeks).
- Remove SSE handlers — after the transition window, remove GET /sse + POST /messages and clean up the SSE session map.
Run both transports simultaneously during step 2–4. There's no flag-day cutover — clients migrate at their own pace.
Transport and external monitoring
Only HTTP-based transports (SSE and Streamable HTTP) can be monitored by external probes. Stdio servers are local child processes — no URL, no port, no network reachability.
| Transport | AliveMCP monitoring | What is probed |
|---|---|---|
| stdio | Not possible | Local process — no network endpoint |
| SSE (HTTP+SSE) | Yes | GET /sse → endpoint event → POST /messages → initialize response |
| Streamable HTTP | Yes | POST /mcp → initialize → initialized → tools/list response |
If you deploy a server that started as a stdio-only tool and now needs uptime visibility — for team use, for inclusion in a registry, or for SLA tracking — migrating to Streamable HTTP is the prerequisite for external monitoring. The tool logic doesn't change; only the transport layer is added.
AliveMCP registers and monitors every server in the public MCP registries (MCP.so, Glama, PulseMCP, Smithery). If your HTTP-based server is listed there, it is already being probed. Claim your listing to set custom alert webhooks and see your server's uptime history.
Related questions
Can I start with stdio and add HTTP later without rewriting my tools?
Yes — this is the designed migration path. Register all your tools on a McpServer instance, connect it to StdioServerTransport for local use, and later add an Express app that connects a new McpServer instance (created by the same factory function) to StreamableHTTPServerTransport. The tool handlers are identical. Only the server entry point and the transport layer change.
Does the transport affect tool handler performance?
Minimally. The transport adds serialization/deserialization overhead (JSON stringify/parse) and network RTT for HTTP transports. For stdio, messages travel through OS pipes, which are fast. For HTTP transports, TLS handshake and TCP RTT add latency compared to stdio's pipe. For CPU-intensive tools, transport overhead is negligible relative to handler execution time. For extremely fast tools (sub-millisecond handlers), transport overhead becomes measurable — see MCP server benchmarking for measurement patterns.
Which transport do MCP registries (MCP.so, Glama, Smithery) expect?
Public registries list endpoints — they expect HTTP-based transports (SSE or Streamable HTTP). A stdio server has no URL to register. If you want your server discoverable in the ecosystem, deploy it with an HTTP transport and register the URL. AliveMCP probes registry-listed endpoints and shows their health status on a public dashboard.
What happens if the client and server support different protocol versions?
During the initialize handshake, both parties declare their protocol version. If the versions are incompatible, the server returns a -32600 error. The MCP SDK handles version negotiation automatically — it selects the highest version both sides support. If you target an older version in your server (e.g., protocolVersion: "2024-11-05"), newer clients will negotiate down to that version.
Further reading
- MCP server stdio transport — full setup guide
- MCP server SSE transport — full setup guide
- MCP server Streamable HTTP transport — full setup guide
- MCP server JSON-RPC 2.0 — protocol messages and lifecycle
- MCP server authentication — securing HTTP transports
- MCP server load balancing — scaling HTTP transports
- MCP server deployment — platform options for HTTP transports
- MCP server testing — InMemoryTransport for transport-agnostic tests
- AliveMCP — uptime monitoring for HTTP-deployed MCP servers