How it works
The Model Context Protocol splits into two layers: a message layer (JSON-RPC calls for tools, resources, and prompts) and a transport layer that moves those messages. The protocol semantics are identical either way. What the transport changes is where the server runs and who can reach it.
Two transports are in common use:
- stdio: the client spawns the server as a local child process and speaks JSON-RPC over its standard input and output. There is no port, no socket, no network hop. The server inherits the client's process context and dies when the client kills it.
- Streamable HTTP: the server is a long-lived network endpoint. The client sends requests over HTTP and receives responses — including streamed, server-initiated messages — over the same connection. (An earlier HTTP-plus-SSE transport is now deprecated in favor of this.)
The practical dividing line is locality. stdio is for servers that live on the same machine as the client and need the user's local filesystem, tools, or credentials. HTTP is for connectors that live elsewhere — a hosted service, a shared team server, a remote tool provider — and must be reached across a network with its own authentication.
Why it matters in an agent harness
Transport is a trust-boundary decision, not a plumbing detail. The choice determines your blast radius before a single tool call runs.
A stdio server runs as your agent's process. It sees the same environment variables, the same working directory, the same ambient credentials. That is convenient and it is dangerous: a malicious or buggy local server has whatever reach the harness has. Containment for stdio means process-level sandboxing — restricted filesystem, dropped privileges, no inherited secrets you did not intend to share.
An HTTP server sits behind a network boundary you can actually inspect. You get a URL you can allowlist, auth you can scope and rotate, and traffic you can log at the edge. But you also inherit remote-endpoint risk: the server can change under you between calls, and every request leaves your machine. For a harness, the HTTP boundary is easier to observe and harder to fully trust, because the code you are calling is not code you control.
Either way, the harness should treat transport as part of the permission model. Which servers a given agent may attach, over which transport, is a policy — not a default the model gets to pick.
MCP Transport vs. the message layer
The two are frequently conflated. Keeping them separate clarifies where a failure lives.
| Transport layer | Message layer | |
|---|---|---|
| Concern | How bytes move | What the call means |
| Examples | stdio, Streamable HTTP | tools/list, tools/call, resources/read |
| Failure looks like | connection dropped, process died, 401/timeout | wrong arguments, tool error, bad result |
| You choose it for | locality, trust boundary, auth | capability and tool design |
When a tool call fails, the transport tells you whether the channel broke; the message layer tells you whether the request was wrong. Confusing the two sends you debugging the wrong layer.
The Rifty take
We treat transport as a permission decision made by the operator, not a convenience chosen by the agent. Prefer stdio with real sandboxing for anything that touches local state, and prefer scoped, rotatable auth for anything reached over HTTP. We accept the extra setup of an explicit allowlist because the alternative — an agent attaching arbitrary servers over an unbounded channel — is exactly the blast radius harness engineering exists to bound.
Implementation checks
- Pin which transports each agent may use; do not let the model select servers freely.
- Sandbox stdio servers so an inherited process does not equal inherited access to your secrets and filesystem.
- Give HTTP connectors scoped, rotatable credentials and an explicit URL allowlist; log requests at the boundary.
- Handle transport-level failure distinctly from tool-level failure — retry a dropped connection, but do not retry a malformed call.
- Treat any server's tool descriptions and results as untrusted input to the model, regardless of transport; the channel does not vouch for the content.
- Set connection timeouts and health checks so a hung server does not stall the loop indefinitely.
Frequently asked questions
When should I use stdio versus HTTP for an MCP server?
Use stdio for servers that run on the same machine and need local files, tools, or credentials — the client spawns them as a subprocess with no network hop. Use Streamable HTTP for remote or shared connectors reached across a network, where you need scoped auth and an inspectable boundary.
Does the transport change what an MCP server can do?
No. The protocol semantics — tools, resources, and prompts exposed as JSON-RPC — are identical across transports. Transport changes where the server runs, who can reach it, and what trust boundary applies. It affects your security and observability posture, not the capability set the server advertises.
Why is a local stdio server a security concern?
A stdio server runs as a child of your agent's process, inheriting its environment variables, working directory, and ambient credentials. A malicious or buggy server gets whatever reach the harness has. Containment requires process-level sandboxing — restricted filesystem, dropped privileges, and no secrets you did not deliberately share.
What replaced the older HTTP+SSE transport?
Streamable HTTP replaced the earlier HTTP-plus-SSE approach for remote MCP servers. It carries requests and streamed server-initiated messages over HTTP as a single long-lived endpoint. Treat SSE-only transports as legacy and prefer Streamable HTTP for new remote connectors that your harness attaches.
How should a harness handle transport-level failures?
Distinguish them from tool-level failures. A dropped connection, dead subprocess, timeout, or 401 is a channel problem — safe to retry or reconnect. A malformed argument or tool error is a message-layer problem and retrying it verbatim just repeats the mistake. Separate handling keeps you debugging the correct layer.