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}}}Streamable HTTP
Section titled “Streamable HTTP”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, orContent-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.
Headers on every request
Section titled “Headers on every request”| 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.
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}}}'What the errors mean
Section titled “What the errors mean”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.
Security basics
Section titled “Security basics”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.
Which one to pick
Section titled “Which one to pick”- 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)andcreateMcpHandler(factory); in Python,mcp.run()andmcp.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.