Skip to content

Transports

Everything you learned in the previous modules — server/discover, tools/list, tools/call, resources/read, subscriptions/listen — is a JSON-RPC 2.0 message. MCP does not change those messages when it moves them across a machine boundary. What changes is the transport: the binding that says how bytes get from one process to another.

MCP defines two transports:

  • stdio — the client launches the server as a child process and talks over its standard input and output.
  • Streamable HTTP — the server is an independent process behind a single HTTP endpoint that accepts POST.

The data layer is the inner layer; the transport is the outer layer. An SDK lets you register a tool once and serve it over either transport without touching the handler.

flowchart LR
  subgraph host["Host (AI application)"]
    c1["MCP client"]
    c2["MCP client"]
  end
  c1 -- "stdio: stdin/stdout of a child process" --> s1["Local server (subprocess)"]
  c2 -- "Streamable HTTP: POST /mcp" --> s2["Remote server (any host)"]
  s1 --- d["JSON-RPC data layer: tools, resources, prompts"]
  s2 --- d
Two transports, one data layer
ConcernstdioStreamable HTTP
Who starts the serverThe client spawns itIt runs on its own (a web service)
Process lifetimeTied to the client that spawned itIndependent; many clients share it
Clients servedTypically oneMany, concurrently
CredentialsEnvironment variables and local filesOAuth 2.1 bearer tokens on every request
Network overheadNone — pipes on one machineOne HTTP round trip per message
Access to the user’s machineDirect (filesystem, local tools)None unless the user grants it
Typical examplesFilesystem server, git server, a CLI wrapperSentry, a SaaS integration, an internal API gateway

The rule of thumb: if the server needs the user’s local machine (files, local databases, dev tools), use stdio. If the server fronts a service that many users share, use Streamable HTTP.

Every MCP request carries its protocol version and client capabilities in _meta (see JSON-RPC and _meta). The two transports carry that metadata differently:

  • stdio has no header layer. Everything — the version, capabilities, client identity, method, arguments — lives inline in the JSON-RPC body. One message per line.
  • Streamable HTTP keeps the body identical but mirrors selected fields into HTTP headers (MCP-Protocol-Version, Mcp-Method, Mcp-Name) so load balancers, gateways and observability tools can route and inspect requests without parsing the JSON. The server validates that the headers and the body agree.

That mirroring is the only place the two transports diverge at the message level, and it exists purely for intermediaries.

The stdio framing — one newline-delimited JSON-RPC message per line over a reliable bidirectional byte stream — is not tied to standard streams. The spec says custom transports built on Unix domain sockets, TCP or similar channels should reuse that framing and the stdio message rules, replacing only the subprocess-specific parts (launch, stderr, shutdown-by-closing-the-stream, restart). So if you ever need a third binding, start from stdio’s rules, not from scratch.

LessonWhat you learn
stdioFraming, the stdout rule, shutdown, cancellation, host config files
Streamable HTTPThe single POST endpoint, JSON vs SSE responses, required headers, subscriptions/listen
Building an HTTP servercreateMcpHandler and mcp.run() in practice, stateless design
Backward compatibilityModern vs legacy eras, how clients detect which one they are talking to
What differs between the stdio and Streamable HTTP transports?
Which situation calls for the stdio transport?
Why does Streamable HTTP mirror fields like the method name into HTTP headers?