Skip to content

Transports: stdio and Streamable HTTP

Spec
2026-07-28
Evidence
E2Reproducedarm64
TypeScript SDK (server)
@modelcontextprotocol/server@2.4.0
TypeScript SDK v2 (Node HTTP)
@modelcontextprotocol/node@2.1.2
Python SDK
mcp@2.3.0
Verified

The message format is the same everywhere: JSON-RPC 2.0. A transport only decides how those messages get from client to server. MCP 2026-07-28 defines two.

stdio Streamable HTTP
Who starts the server The client, as a child process You, as a web service
Where it runs Same machine as the host Anywhere reachable over HTTP
Clients per server One Many
Auth The OS user that started it Your job: OAuth or another scheme
Good for Local tools, files, CLIs Shared, remote or hosted servers

The client launches the server and talks to it over standard input and output:

  • One JSON-RPC message per line. A message must not contain a newline.
  • The server must not write anything to stdout that is not an MCP message. Logs go to stderr, which the client may show, store or ignore.
  • The server must not send requests to the client. When it needs input from the user, it uses multi round-trip requests instead.

In 2026-07-28 even stdio has no handshake. We wrote two lines into the stdin of a TypeScript server, with no initialize first, and got two answers back:

{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{…same…},"name":"add","arguments":{"a":2,"b":3}}}

The server exposes one endpoint, usually /mcp, and every message from the client is its own POST. For a request, the server answers with either:

  • Content-Type: application/json: one JSON-RPC response, or
  • Content-Type: text/event-stream: a short SSE stream for this request only, carrying progress or log notifications and then the response.

Clients must accept both. Our TypeScript and Python servers answered ordinary requests with plain JSON.

There is no GET stream and no session any more. The TypeScript server answered GET /mcp and DELETE /mcp with 405 Method Not Allowed; the Python server, which also serves older clients, answered GET with 400.

Header Value Required
Content-Type application/json Always
Accept application/json, text/event-stream Always
MCP-Protocol-Version Same version as _meta in the body, e.g. 2026-07-28 Always
Mcp-Method The JSON-RPC method, e.g. tools/call Always
Mcp-Name params.name, or params.uri for resources tools/call, prompts/get, resources/read

The headers copy fields from the body so that load balancers and gateways can route without parsing JSON. The server must check that they match the body.

TerminalCall a tool with all required headers
Terminal window
curl -s -X POST http://127.0.0.1:8101/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: add' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}},
"name":"add","arguments":{"a":2,"b":3}}}'

We broke each rule on purpose against a TypeScript SDK 2.4.0 server:

What we sent HTTP JSON-RPC error
No Mcp-Method header 400 -32020 … the required Mcp-Method header is absent
Mcp-Name: foo for a call to add 400 -32020 … the Mcp-Name header names "foo"
Header 2025-11-25, body 2026-07-28 400 -32020 … the MCP-Protocol-Version header names 2025-11-25
Version 2099-01-01 in both 400 -32022 Unsupported protocol version, with "supported":["2026-07-28"]
Method ping 404 -32601 Method not found

-32020 is HeaderMismatch, -32022 is UnsupportedProtocolVersion, and a 404 with -32601 means the method does not exist in this revision. The Python SDK 2.3.0 returns the same codes with shorter messages, for example mcp-method header does not match the request body's method.

The specification requires servers to validate the Origin header, and recommends binding to 127.0.0.1 when running locally and authenticating every connection. The TypeScript SDK’s Node adapter ships localhostHostValidation() and localhostOriginValidation() guards for this. A server on 0.0.0.0 without authentication can be reached by any web page the user opens.

  • Only you, on your machine: stdio. No port, no auth, and the client manages the process.
  • A team or many users: Streamable HTTP behind authentication.
  • Both: write the tools once. In the TypeScript SDK the same server factory works with serveStdio(factory) and createMcpHandler(factory); in Python, mcp.run() and mcp.run("streamable-http").

HTTP+SSE, the transport from 2024-11-05, is deprecated. Don’t start new servers on it.

Sources

MCPDev is community documentation. It is not affiliated with the Model Context Protocol project or with the vendors of the clients it covers.