AI

WritingMCP series

MCP on the Wire

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

By Sachin Gupta7 min read
Portrait of Sachin Gupta rendered in binary

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 MCP lifecycle: the client sends initialize proposing a protocol version and declaring its capabilities; the server replies with the agreed version and its own capabilities; the client sends initialized and normal operation begins; both sides only use features the other advertised

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.

Two transports: stdio runs the server as a local subprocess and passes JSON-RPC over standard input and output; Streamable HTTP is for remote servers, where the client POSTs to one endpoint and the server streams results back over Server-Sent Events on the same endpoint

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:

Interactive · MCP transports: stdio vs HTTPOpen in Visualizers →
Both carry JSON-RPC 2.0 · pick the channel
client ⇄ pipe ⇄ server process
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)
The messages are identical; only the channel changes. stdio is the phone call in the same building; Streamable HTTP is the letter across the internet, with all the auth that implies.

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.

Related

Tagged