In plain English
When two programs need to talk, they need a transport — a concrete pipe for the bytes to travel through. MCP (Model Context Protocol) is the shared language that hosts and servers speak, but the transport is how that language actually moves from one side to the other. MCP defines exactly two official transports: stdio and Streamable HTTP.

Think of the difference like a phone call versus a letter. With stdio, the host (your AI app) dials up a local program on the same machine and the two talk back and forth through that open connection — low latency, no network, very direct. With Streamable HTTP, the server lives somewhere on the internet, and every exchange goes through standard web requests — the equivalent of sending letters that can be tracked, authenticated, and load-balanced across many post offices.
stdio is short for standard input/output — the same streams that shell commands like cat and grep use. The host launches the MCP server as a child process, writes JSON-RPC messages to its stdin, and reads responses from its stdout. No sockets, no ports, no auth setup. Streamable HTTP is a single HTTP endpoint the server exposes. The host POSTs requests to it and the server either replies with a plain JSON body or upgrades the response to a Server-Sent Events stream when the server needs to push multiple messages back.
Why it matters
Choosing the wrong transport forces you to redesign your deployment later. A local tool that starts simple on stdio can hit hard ceilings the moment a second developer needs it. A remote service that starts on Streamable HTTP adds auth and network latency that may be unnecessary for a single-user script. Getting the choice right from the start saves you a painful migration.
There is also a historical reason to understand transports: early MCP (spec version 2024-11-05) shipped with HTTP+SSE as the remote transport — a dedicated SSE endpoint for server-to-client streaming paired with a separate POST endpoint for client messages. This design had real friction: two endpoints to manage, persistent SSE connections that fail behind many proxies and CDN edges, and awkward load-balancing semantics. The 2025-03-26 spec replaced HTTP+SSE with Streamable HTTP, which collapses everything to one endpoint. The old SSE transport is now deprecated and is scheduled to be removed by providers in mid-2026.
Understanding transports also matters for security. stdio servers run on the user's own machine with the user's own filesystem permissions — there is no network attack surface, but every tool invocation has full local access. Streamable HTTP servers sit behind HTTP auth layers (OAuth 2.1 is the recommended auth scheme), giving you the ability to restrict which clients can call which tools and to audit every request in a central log.
How each transport works
stdio in detail
The host process spawns the server binary as a child — for example, running npx @modelcontextprotocol/server-filesystem /home/user/docs. From that point on, all communication is newline-delimited JSON-RPC 2.0 over the process's stdin and stdout. Each message is one complete JSON object on a single line; embedded newlines are not allowed inside a message. The server may write UTF-8 log strings to stderr; the host can capture or ignore them. When the host is done, it terminates the subprocess — the OS cleans up automatically.
// Client → Server (written to server's stdin)
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-host","version":"1.0"}}}
// Server → Client (written to server's stdout)
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"filesystem","version":"1.2"}}}Streamable HTTP in detail
The server exposes one HTTP endpoint — typically /mcp — that accepts both POST and GET. The client POSTs a JSON-RPC message (or a JSON-RPC batch) to this URL. The server decides how to respond: for simple request-response exchanges it returns a 200 OK with a Content-Type: application/json body; when the response involves long-running work or server-initiated notifications, it upgrades to Content-Type: text/event-stream and streams multiple Server-Sent Events over that same response. The client can also open a persistent SSE channel with a GET to the same endpoint, letting the server push notifications at any time.
Session management is optional. If the server wants to maintain state across requests it issues a Mcp-Session-Id header in the InitializeResult response. The client then echoes that header on every subsequent request. For stateless deployments — including serverless functions or edge workers — the server skips session IDs entirely: every request carries the full context it needs.
- Host spawns server as child process
- Host writes JSON-RPC line to server stdin
- Server writes JSON-RPC line to stdout
- Server logs go to stderr (optional)
- Host terminates process when done
- No network, no auth, no ports
- Server runs as independent HTTP service
- Client POSTs JSON-RPC to /mcp endpoint
- Server replies JSON or upgrades to SSE stream
- Client GETs /mcp to receive push notifications
- Optional Mcp-Session-Id for stateful sessions
- Standard HTTP auth (OAuth 2.1, API keys)
When to use which
The right transport usually follows from who will run the server and who will call it.
| Scenario | Recommended transport | Reason |
|---|---|---|
| Single developer, local tools (filesystem, shell, local DB) | stdio | Zero setup, no auth complexity, fastest latency |
| IDE plugin bundled with the editor (Cursor, VS Code) | stdio | Editor launches server process, same machine |
| Company-wide internal tool accessed by a team | Streamable HTTP | One deployment, centralized auth and access control |
| SaaS integration (GitHub, Notion, Stripe official MCP) | Streamable HTTP | Must serve many tenants, needs OAuth, audit logs |
| Serverless or edge function | Streamable HTTP (stateless) | Stateless mode avoids session affinity problems |
| Local prototype being shared with a co-worker | Streamable HTTP | stdio cannot serve across the network |
| CI/CD pipeline step on the same runner | stdio | No external network, process isolation is sufficient |
Latency profile is worth knowing concretely. stdio adds roughly the cost of a process spawn plus IPC — benchmarks put typical round-trip times at 4–9 ms for in-process calls. Streamable HTTP adds a full TCP round-trip plus any TLS handshake, typically 20–100 ms depending on geography. For interactive chat this difference is imperceptible; for an agent loop making thousands of tool calls in a tight batch, it can accumulate meaningfully.
SSE transport: what it was and why it was replaced
The original remote transport in MCP spec 2024-11-05 was HTTP+SSE. It split communication across two separate endpoints: the server opened a persistent SSE channel at /sse for server-to-client messages, and the client POSTed to /messages for client-to-server calls. This had a fundamental problem — you needed a persistent SSE connection that survived load balancers, timeouts, and CDN edges. Many infrastructure setups that handle millions of standard HTTP requests would silently drop a long-lived SSE connection after 30–90 seconds.
Streamable HTTP solves this by making SSE optional and per-response rather than always-on. The server streams when it needs to stream (during long-running tool calls) and responds with plain JSON when it does not. The connection is stateless by default. There is no persistent SSE socket to babysit, no two-endpoint split to configure, and no special proxy rules needed.
2024-11-05spec — HTTP+SSE was the remote transport; stdio was local2025-03-26spec — Streamable HTTP introduced; HTTP+SSE deprecated- TypeScript SDK 1.10.0 (April 2025) — first SDK to ship Streamable HTTP support
2025-11-25spec — Streamable HTTP is the canonical remote transport; SSE kept only for backwards compat- Mid-2026 — SSE removal deadlines from major providers (Atlassian Rovo: June 30, 2026)
Going deeper
Authorization on Streamable HTTP follows OAuth 2.1 with PKCE. The spec defines a discovery mechanism: a client fetches /.well-known/oauth-authorization-server from the server host to learn token endpoints. This means MCP servers can participate in standard enterprise identity flows — SSO, short-lived tokens, per-user scopes — without inventing a bespoke auth layer. For stdio servers, credentials are typically passed as environment variables when the host spawns the process.
Resumability is a feature introduced for long-running tool calls. When a Streamable HTTP server assigns an event-id to each SSE event, a disconnected client can send a Last-Event-ID header on reconnect and the server replays only the missed events. This makes tool calls robust against transient network drops — the client does not have to re-issue the request and risk triggering side effects twice.
Custom transports are technically allowed. The MCP spec defines an abstract transport interface (initialize/send/close plus an onmessage handler) that SDKs expose. In-process transports — where client and server live in the same Node.js process — are common in tests and for sandboxed tool runners. WebSocket transports have been explored for browser-native clients. That said, the spec currently only standardises stdio and Streamable HTTP, so non-standard transports require explicit agreement on both sides.
Observability differs substantially between transports. With stdio, every message flows through process pipes on one machine — you can intercept with a proxy shim or instrument the SDK directly. With Streamable HTTP, you get everything HTTP infrastructure gives you: access logs, distributed tracing (OpenTelemetry semantic conventions for MCP are defined in the OTel spec), request-level rate limiting, and WAF rules. For enterprise deployments, this is one of the strongest arguments for choosing Streamable HTTP even when stdio would technically work.
FAQ
Can I run an MCP server over WebSocket instead of stdio or Streamable HTTP?
The MCP spec only standardises stdio and Streamable HTTP. WebSocket is not an official transport and requires both sides to explicitly agree on the protocol. For most use cases Streamable HTTP with its optional SSE streaming already provides the bidirectional, low-latency behaviour that makes WebSockets attractive.
Why does the Streamable HTTP transport use SSE for streaming if SSE was deprecated?
The deprecated transport is HTTP+SSE — the original remote transport that used a persistent SSE connection as its primary channel. Streamable HTTP is different: SSE is only used optionally and per-response when the server needs to push multiple events. There is no always-on SSE socket. The SSE wire format is reused as a streaming mechanism, not as a standalone transport.
Is stdio secure enough for a production developer tool?
For single-user local tooling, yes. stdio servers run under the user's own OS account, so they can only access what the user can access. There is no network-facing port to attack. The risk is not remote attackers but local trust: you are running arbitrary code the server author wrote, so vet your MCP servers the same way you vet any npm package or shell script.
Does Streamable HTTP need a persistent connection?
No. In stateless mode (the recommended default) every request is independent — you can deploy to a serverless function or an edge worker with no special connection management. Only if the server opts into stateful sessions (by issuing a Mcp-Session-Id) does the client need to send that header on subsequent requests.
How do I migrate an existing HTTP+SSE server to Streamable HTTP?
The core change is collapsing your /sse and /messages endpoints into a single /mcp endpoint that handles both GET (for opening a push stream) and POST (for sending messages). The 2025-11-25 spec recommends keeping the old SSE endpoint active alongside the new one during a transition period so existing clients are not broken. The TypeScript SDK's StreamableHTTPServerTransport handles this automatically.
Can one MCP server binary support both stdio and Streamable HTTP at the same time?
Yes. Most SDKs let you instantiate either transport at startup based on a flag or environment variable. A common pattern is if (process.env.MCP_TRANSPORT === 'http') { start StreamableHTTP } else { start Stdio }. This lets the same server code work for local developers (stdio) and cloud deployments (Streamable HTTP) without a fork.