MCP on the Wire
JSON-RPC, the lifecycle handshake, and the two transports, stdio and Streamable HTTP.

How MCP messages actually move. The JSON-RPC base, the initialize handshake and capability negotiation that set up every connection, and the two standard transports, stdio for local servers and Streamable HTTP for remote ones, plus why the old HTTP-plus-SSE transport was replaced.
Two programs that need to talk can do it two ways: like a phone call over a direct line in the same building, or by mailing messages back and forth across the internet. One is local and instant, the other reaches anywhere but has to worry about who is on the other end. MCP works over both kinds of channel, and which one you pick changes everything about how you deploy and secure a server.
This is part of a series on MCP. The last piece covered what MCP says. This one covers how it is said: the base protocol, the handshake that opens a connection, and the two ways the bytes travel.
JSON-RPC and the handshake
Every MCP message is JSON-RPC 2.0, a small, boring, well-understood format of requests, responses, and notifications. Boring is the right choice here; the protocol spends its novelty budget on the primitives, not the envelope.
The interesting part is how a connection begins. MCP connections are stateful, and before any real work happens, the two sides negotiate.
The client sends initialize, proposing a protocol version and declaring what it
supports, its client capabilities like sampling, roots, and elicitation. The server
replies with the version it agrees to and its own server-side capabilities like tools, resources, and prompts. The client sends initialized, and normal operation
begins. From then on, each side only uses features the other advertised. If a server
never announced prompts, the client will not go looking for them.
This capability negotiation is quietly the most important design decision in the protocol. It is what lets MCP add new features over time without breaking older clients and servers: an old client simply never negotiates the new capability, and everyone keeps working. It is why the protocol could add authorization, structured output, async tasks, and an extensions framework across four revisions without a flag day. The version string, which is a date, is the contract both sides agree to hold, and there is now a deprecation policy so features that go away give at least a year of warning.
The two transports
MCP defines two standard transports, and a server author mostly does not care which is in use, because the primitives are identical either way.
stdio runs the server as a local subprocess and passes JSON-RPC over standard input and output. It is the simple default for anything on your own machine: no ports, no network, no authorization flow, just a process talking to its parent through pipes. Most local MCP servers use it.
Streamable HTTP is the transport for remote servers. The client POSTs JSON-RPC to
a single endpoint, and the server can stream results and notifications back over
Server-Sent Events on that same endpoint. It supports both quick request-and-response
calls and longer, resumable sessions. One endpoint, requests going up and events
streaming down. The direction of travel across the ecosystem is toward remote,
authenticated servers, so this is the transport most production deployments care
about, and recent revisions have tightened it: servers must reject requests with an
invalid Origin, and the streaming rules for disconnection and resumption were made
explicit so long sessions survive a dropped connection.
Toggle between the two and the tradeoff is clear at a glance:
- Where it runs
- Local subprocess on the same machine
- How bytes travel
- Bytes over stdin / stdout, one long-lived pipe
- Auth
- None needed (same machine, same user)
- Best for
- Desktop apps and local tools (a filesystem or git server)
A note on history
If you read an older MCP tutorial, you may find it describing a different HTTP transport with two separate endpoints, called HTTP+SSE. That was the original design, and it was replaced by Streamable HTTP back in the 2025-03-26 revision. It is worth knowing only so you can recognize outdated material: the two-endpoint SSE setup is not the current transport, though servers may still support it for backward compatibility.
Why this layer matters
Separating the message from its transport is what lets the same MCP server run as a local subprocess during development and as a remote HTTP service in production without changing a line of its tool logic. The transport is a swappable layer underneath a stable protocol. That clean seam is also exactly where security lives, because the moment a server is reachable over the network, you have to answer who is allowed to call it, which is the next piece.