Part 5 of 7 · 4 min read
Choosing a transport
Covers: Choosing a transport; A checklist.
Choosing a transport
| Question | stdio | Streamable HTTP |
|---|---|---|
| Where does the server run? | on the user's machine, started by the host | anywhere, shared |
| Who can reach it? | only the parent process | anyone who can reach the URL: needs auth |
| Whose permissions does it have? | the user's | its own, plus whatever token it is given |
| Start cost | a process per server | a connection per request |
| Scaling | one process per client | any instance can answer; stateless suits a load balancer |
| Typical failure | stdout pollution, crash, hung exit | proxy buffering, broken streams, exposed endpoint |
| Cancel | message | close the stream |
Measured in the example on this machine (loopback, a small call, 150 repetitions): starting the stdio server took about 47 to 51 ms, against about 1.5 ms to connect to an HTTP server that was already running; a call took about 0.4 ms on stdio and about 1.3 ms over HTTP; and the request was 297 bytes on stdio against 470 bytes over HTTP (request line, headers and body, because every HTTP request carries headers). On a real network, with TLS and a proxy, the gap changes; the shape is the point: stdio is cheaper per call and costs a process, HTTP costs a little per call and costs nothing to start.
Tip: a local stdio server runs with the user's full permissions
Launching an MCP server from a configuration file means running that program, with access to the user's files and network. That is the supply-chain problem of any package you install. Module 5 treats it with trust boundaries; module 8 covers sandboxing.
A checklist
- stdout belongs to the protocol. Logs go to stderr. Treat a non-message line as a diagnostic, not a crash.
- Shut down in order: close stdin, wait, terminate, kill. Servers exit on end of input.
- Set timeouts and cancel on timeout. Tools that run long must check the cancel flag and report progress.
- Probe, then fall back, and never key the fallback to one error code. Remember the answer per server.
- Over HTTP, send the mirrored headers and make the server check them. Reject on mismatch with -32020.
- Validate
Origin, bind to 127.0.0.1 locally, authenticate every connection. - Handle both JSON and SSE replies; ignore comment lines; set
X-Accel-Buffering: no. - Retry only what is safe: reads, or writes that carry an idempotency key. Otherwise tell the model the outcome is uncertain.
- After a reconnect, re-read anything you cache. No notification is not no change.
- Do not use the old HTTP+SSE transport for new work.
- Test the transport with the failures above, not just the happy path.
Interview angle
"When would you use stdio and when Streamable HTTP?" Say: stdio is for a server the host starts as a local subprocess: no network, no auth to design, but it runs with the user's permissions and costs a process per server. Streamable HTTP is for a shared or remote server: one endpoint, POST per message, replies as JSON or an event stream, and in the newer revision stateless, so it sits behind a load balancer; but it is exposed, so you validate Origin, bind locally for local use, authenticate, and check that mirrored headers match the body. Then show the details that mark experience: cancellation differs (a message on stdio, closing the stream on HTTP), a broken connection after the work is done needs idempotency keys, and notifications are not replayed so a client re-reads after reconnecting. A weak answer says "stdio for local, HTTP for remote" and stops, or says MCP uses WebSockets.
Second question of the level: "How would you secure an MCP server that exposes internal data?" The transport part of the answer is section 8: TLS, Origin, loopback binding for local use, authentication, header validation. The rest (tokens, scopes, tool poisoning, trust boundaries) is module 5.
Check yourself
A tool call runs for 90 seconds, the connection drops at 80, and the server finishes the work at 85. What can the client safely do, and what must the tool support? (Retry only if the tool is idempotent or takes a request id the server remembers. Otherwise a retry repeats the work.)
For the curious What a transport does not change
A transport carries messages and decides framing, cancellation and shutdown. It never changes what a message means, so a server written once can be put behind stdio or HTTP. The HTTP side adds duties that stdio does not have: checking Origin, binding to localhost by default, authentication, and comparing mirrored headers with the body.
What you will use daily at work
| Practice | What it looks like |
|---|---|
| stdio for local tools | The host starts the server as a child process and talks over its pipes. |
| HTTP for shared servers | One endpoint with authentication, behind your normal gateway. |
| Timeouts and cancellation | Every call has a limit and a way to stop it. |
Draft: confirm which transport your platform supports for remote servers.