The short answer
If your Model Context Protocol (MCP) server runs on the same machine as your MCP client — your laptop, a single Docker host, a local VM — stdio is almost always the right transport. It is faster, simpler to debug, and has no network surface to babysit.
If your MCP server runs as a remote service that one or more clients (including you, on different devices) connect to over a network, use Streamable HTTP. The MCP spec has deprecated the older HTTP+SSE transport; SSE is kept around for backwards compatibility but new work should sit on Streamable HTTP.
The rest of this article is the trade-off in detail, and what each choice does to your incident response.
Why transport choice matters for a solo founder
You are running an MCP server so some agent — your own product, a coding assistant, a customer-facing workflow — can call tools and read resources over JSON-RPC 2.0. The protocol is deliberately wire-format agnostic: the messages are the same regardless of transport. What changes is everything around them: how the process is started, how it dies, how you observe it, and how you recover when something goes wrong at 2 a.m.
For a one-person backend, the right transport is the one that:
- matches where the client and server actually live
- gives you a sane reconnect story when things inevitably drop
- is observable from a single laptop screen with the tools you already use
- does not require you to stand up TLS, auth, and rate limiting just to call a tool locally
stdio, in one paragraph
stdio is process-to-process communication. Your MCP client launches the server as a subprocess. The client writes JSON-RPC messages to the server’s stdin; the server writes responses to stdout. There is no network, no port, no TLS, no CORS, no firewall. The OS pipe is the security boundary.
This is the transport the MCP spec recommends whenever the client can launch the server directly, and it is the most widely deployed in practice. The wire format is the same JSON-RPC 2.0 the protocol uses everywhere else; messages are newline-delimited and must not contain embedded newlines. Logging must go to stderr, never stdout, because stdout is reserved for protocol messages.
Where stdio shines
- Local tools and CLI agents. One user, one machine, one process. This is the default.
- Strict security requirements. No socket is opened to the network, so there is nothing to bind to a wrong interface.
- Lowest possible latency. OS pipes are noticeably faster than HTTP round-trips for short tool calls; community write-ups describe throughput orders of magnitude above HTTP transports, though your real numbers depend on payload size and host hardware.
- Tiny operational surface. If the process exits, you see it in the parent process; if it crashes, you see the stack trace on stderr.
Where stdio breaks down
- One client per process. stdio does not natively multiplex many clients onto one server; you either spawn many processes or build your own routing layer.
- No remote access. The moment you want to call the same server from a second laptop, a phone, or a hosted agent, stdio is the wrong answer.
- Hidden failure modes. A child process can hang without the parent noticing immediately; a broken pipe on the client side can look like a slow tool to the user.
The HTTP family: SSE, then Streamable HTTP
The MCP spec originally defined a remote transport called HTTP+SSE. The server exposed two endpoints: one long-lived GET that streamed server-to-client events, and a POST endpoint that clients used to send messages. This worked, but it required holding an open HTTP connection for every client, which is awkward to scale and awkward to put behind most infrastructure. Several community write-ups describe running into compute-cost and platform issues when hosting the older SSE transport because idle clients kept connections live.
The current spec replaces that with Streamable HTTP. The headline changes:
- A single HTTP endpoint accepts both POSTs (client to server) and, optionally, an SSE upgrade for streaming responses. This is what the SDK documents as the recommended path for anything deployed.
- Sessions can be stateful or stateless. Stateless mode is the configuration the SDK explicitly recommends for production scalability because you can scale horizontally without sticky sessions.
- Streamable HTTP supports resumability, which means a dropped connection can be reattached and replayed from the last event ID rather than starting over.
- Authentication is a real Authorization header; OAuth flows are first-class.
The deprecated HTTP+SSE transport is still in the SDK for backwards compatibility with older clients, but it is no longer where the spec or the official docs point new work.
Where Streamable HTTP shines
- Multi-client and remote deployments. Several clients can talk to one server across the network.
- Horizontal scaling. Stateless mode plus a load balancer is the pattern the SDK recommends for production.
- Auth, audit, and rate limiting. You can put it behind the same gateway you already use for the rest of your product, with the same headers, logs and policies.
- Resumable streams. When a network blip kills a long-running SSE response, the client can resume from the last event rather than re-running the tool call.
Where Streamable HTTP costs you
- You now operate a service. Ports, TLS, reverse proxy, health checks, graceful shutdown. For a solo founder this is a real tax, even if it is a familiar one.
- Reconnect behavior varies by client. Some MCP clients retry transparently, others surface the error to the agent, and the resumable-stream support requires both ends to participate. Plan to test it before you trust it.
- Debugging is harder. A failing tool call might be a transport issue, a server bug, a proxy timeout, or a TLS handshake; you have to disambiguate.
A decision framework for a solo founder
Walk through these questions in order. The first one that lands decides the transport.
- Does the client and server run on the same machine? Use stdio. Stop reading and ship.
- Does a remote agent need to call this server from a different host? Use Streamable HTTP, stateless if you do not need per-session state, with a reverse proxy in front.
- Are you maintaining a desktop or sandboxed app that cannot easily launch subprocesses? This is the edge case the MCP maintainers have flagged on GitHub: SSE’s main alternative for sandboxed clients is custom transports, not “just use SSE.” For a new build today, that still usually means Streamable HTTP from the client side, with whatever sandbox-friendly local IPC you need.
- Are you stuck supporting a legacy MCP client that only speaks the old HTTP+SSE? Keep an SSE-compatible endpoint around, but isolate it; new clients and new servers should not be on the old transport.
What transport choice does to your monitoring
This is the part most comparison posts skip, and it is the part that decides whether you sleep at night.
Monitoring stdio
You are monitoring a child process. The signals you have:
- Exit code and stderr. Capture both. Wrap the launcher so a non-zero exit triggers an alert and stderr lines are searchable.
- Process count. If you expect one server per client, a sudden drop or spike is itself a signal.
- Round-trip latency on the parent side. You can time each JSON-RPC request-response pair from the client. stdio latencies should be boring; outliers usually mean the server is doing real work, not that the transport is slow.
- No network metrics. There is no port, no TLS handshake, no proxy. That is a feature.
The practical habit: log stderr to a file with rotation, tail it during incidents, and treat any non-zero exit as a page.
Monitoring Streamable HTTP
You are monitoring a network service. The signals you have, and should wire up:
- HTTP status codes by route. 4xx and 5xx on the MCP endpoint are your first signal. Separate initialization failures from tool-call failures.
- Latency percentiles, not averages. p50, p95 and p99 on POST latency; SSE stream duration if you stream responses.
- Active sessions. A count of in-flight MCP sessions, especially if you run stateful mode. Sudden drops can mean clients are failing to reconnect; sudden spikes can mean a single client is misbehaving.
- Reconnect events. Whether your SDK exposes them or you have to infer them from session churn, watch the rate. A healthy remote MCP server has a low, steady reconnect rate; a flood is an incident.
- Resumable-stream retries. If a client resumes from an event ID, that is a sign of a dropped stream. Count it.
- Standard web observability. Access logs, request IDs, structured logs around tool calls, error rates by tool. The MCP layer should be one route in your existing observability stack, not a separate universe.
The practical habit: put the MCP endpoint behind the same reverse proxy as the rest of your product, inherit its access logs and rate limits, and add an MCP-specific dashboard for sessions and reconnect rate.
Reconnect behavior, in practice
You will get asked about this the first time something drops. Here is the honest answer per transport.
- stdio reconnect. There is no “reconnect.” If the subprocess dies, the parent is responsible for respawning it. Most SDKs let you configure this; some MCP clients do it automatically, others require you to wire it. Test by killing the server process and watching what the client does. If it silently respawns and re-initializes, you are good. If it surfaces an error to the agent, you need to decide whether that is acceptable.
- Streamable HTTP reconnect. The HTTP endpoint is just HTTP: standard timeouts and retries apply. The interesting case is when a server has started streaming an SSE response and the connection drops mid-stream. If both ends support resumable streams, the client can resume from the last event ID; if not, the tool call effectively restarts. For long-running tools, that matters a lot.
- Legacy SSE reconnect. The old HTTP+SSE transport held a long-lived GET open. Reconnects were usually “re-open the GET and hope the server can re-establish state,” which is one of the reasons the spec moved on.
Self-hosting Streamable HTTP without painting yourself into a corner
If you decide to self-host a remote MCP server, the configuration that ages best is the boring one:
- Run the server behind a reverse proxy you already trust (Caddy, Nginx, Traefik, your cloud load balancer). TLS termination and access logs come for free.
- Enable stateless mode in the SDK unless you have a specific reason to keep sessions on a single node. Stateless is what the SDK docs recommend for production scalability.
- Mount the MCP route on a clear path (the SDK default is
/mcp; pick one and stick to it). - Reuse your existing auth. If your product already issues bearer tokens or has an OAuth flow, plug the MCP endpoint into it rather than inventing a parallel scheme.
- Add a single health check route that does no tool work but proves the server is alive and the JSON-RPC handshake succeeds.
- Wire access logs and error rates into the same dashboard as the rest of your product so you do not have to context-switch during an incident.
This is not novel advice; it is the same advice you would follow for any internal HTTP API. The reason it is worth saying is that “just expose MCP on the internet” without a proxy is how solo founders end up with TLS misconfiguration as their first MCP incident.
Common pitfalls to avoid
- Picking SSE because a tutorial from 2024 used it. The spec deprecated it; new code should be Streamable HTTP, with SSE compatibility only if you actually need to talk to older clients.
- Writing logs to stdout. On stdio, stdout is the wire. A stray log line there will look like a malformed JSON-RPC message to the client and you will spend an hour wondering why the handshake fails.
- Treating “stdio is faster” as “stdio is always better.” Speed is a side effect of co-location. Once the server is remote, the speed gap closes and the operational gap opens.
- Skipping reconnect testing. Whatever you choose, kill the server, kill the network, and watch what happens. The first time you find out your client does not retry is the night you have a customer waiting.
FAQ
Is SSE still supported? Yes, for backwards compatibility with older clients. It is no longer where the spec points new work, and the SDK still ships it but recommends Streamable HTTP for production.
Can a single server expose both stdio and Streamable HTTP? Yes. Your tool, resource, and prompt logic is written once; the transport is just setup. Most SDKs let you run both from the same codebase with different launch configurations.
Which transport should I default to for a new product? stdio while the server runs on the same machine as the client, Streamable HTTP the moment it goes remote. The decision is mostly about deployment context, not preference.
How do I know my reconnect logic actually works? Test it. Kill the server process, drop the network, restart the proxy. The reconnect story that has never been tested is the reconnect story that fails on a Friday evening.
Does transport choice affect token cost or latency to the model? Indirectly. stdio removes network overhead between client and tool server, which trims wall-clock latency on short calls. Streamable HTTP adds network and proxy hops but lets you scale and authenticate, which usually matters more once you have more than one user.
Sources
- Ginger Labs — MCP Transport Comparison: stdio vs SSE vs Streamable HTTP
- Kirk Ryan — stdio vs Streamable HTTP: Choosing the Right MCP Transport
- Grizzly Peak Software Library — MCP Transport Options: stdio vs SSE vs WebSocket
- Model Context Protocol specification — Transports
- GitHub discussion #63 in modelcontextprotocol/modelcontextprotocol — stdio vs SSE transport question
- Sourcecraft docs — MCP transport in the Code Assistant plugin for VS Code







