Remote vs Local MCP Servers: Choosing a Transport
stdio, Streamable HTTP, and when each one earns its keep.
One protocol, several pipes
MCP keeps a clean separation between the data layer — the JSON-RPC 2.0 messages that never change — and the transport layer that carries them. The same initialize handshake and the same tools/call run identically whether the bytes travel over a pipe between two local processes or over HTTPS to a server three continents away. The interesting question is not what the messages say; it is which pipe to pick.
stdio: the local default
When the server runs on the same machine, stdio is the right answer, and the spec agrees: "Clients SHOULD support stdio whenever possible." The host launches the server as a subprocess and talks to it over standard input and output, one JSON-RPC message per line.
Two rules define stdio, and breaking either one breaks the server. First, standard output carries protocol messages and nothing else — every log line must go to stderr, or you corrupt the stream. Second, there is no OAuth here; local servers take their secrets from environment variables, which is why every Desktop config has an env block.
stdio is fast, private — nothing leaves the machine — and trivial to debug. For a personal tool, a file system bridge, or a git wrapper, it is almost always what you want.
The transport that got replaced
The original 2024 remote transport, HTTP+SSE, used two endpoints and a mandatory long-lived connection. It worked, but it could not resume after a dropped connection, was awkward to load-balance, and did not fit serverless platforms at all. The March 2025 revision replaced it with Streamable HTTP. You will still meet HTTP+SSE in older servers; treat it as legacy.
Streamable HTTP: the remote standard
Streamable HTTP collapses everything onto a single endpoint — say /mcp — that accepts both POST and GET. The client POSTs a request with an Accept header offering both JSON and an event stream; the server replies with plain JSON for a simple call, or upgrades to Server-Sent Events when it needs to stream. Responses that need no body come back as 202 Accepted, and a GET opens a server-to-client stream for out-of-band messages.
Two details make it production-grade. Sessions: the server may issue an MCP-Session-Id on initialize, which the client then echoes on every later request; a 404 means the session expired and the client re-initializes. Resumability: each SSE event carries an id, and a reconnecting client sends Last-Event-ID to replay what it missed — no lost messages across a flaky link.
Don't skip the headers
Two headers matter for correctness and safety. Since the June 2025 revision, every post-initialize HTTP request must carry MCP-Protocol-Version; omit it and a server assumes the older default. And a local HTTP server must validate the Origin header and bind to localhost — without that, a malicious web page can reach it through DNS rebinding. Remote servers add OAuth 2.1 on top, with the MCP server itself acting as an OAuth Resource Server.
How I choose
The decision is almost always about where the server runs and who uses it. Same machine, single user, secrets already in the environment? Use stdio. A service shared by a team, deployed behind a load balancer, or running on a serverless platform? Streamable HTTP with OAuth. When in doubt, start with stdio — it is the lowest-ceremony way to a working server, and the data layer you build on top is exactly the same either way.