MCP transports in 2026: stdio and Streamable HTTP Under the 2026-07-28 Model Context Protocol specification, MCP servers use two standard transports for JSON-RPC 2.0 messages: stdio for local processes launched by the host, where stdout carries protocol messages only and logs go to stderr, and Streamable HTTP for remote services, where request headers identify the method and tool and a header that disagrees with the body returns error -32020. The author, writing in October 2026, identifies proxy idle timeouts as the main operational risk because a broken response stream loses its request, and notes the older HTTP+SSE transport has been deprecated since 2025-03-26. The official Python SDK, mcp 2.3.0, routes logging output to stderr once an MCPServer is created, though a test on Windows showed an unflushed print() stayed buffered until the server stopped and then reached the client as an unparseable line. MCP transports in 2026: stdio and Streamable HTTP What surprised me in the 2026-07-28 transport rules is how much is now mirrored into HTTP headers: a gateway in front of a Model Context Protocol MCP server can see which method and tool a request is for without reading its body. The failures to watch for are small: a stray line on stdout, or a proxy that cuts a quiet call. This is part 7 of my series on what an MCP server does under the 2026-07-28 specification, after caching hints and pagination in part 6 https://pournasserian.com/writing/mcp-2026-caching-and-pagination . The facts are as I read them in October 2026. In brief 1. MCP has two standard transports for its JSON-RPC 2.0 messages: stdio, typically for a local process the host launches, and Streamable HTTP, typically for a remote service. 2. On stdio, stdout carries protocol messages and nothing else. Logs go to stderr, and credentials come from the environment. 3. On Streamable HTTP, headers say what each request is, so a gateway can act on them, and a header that disagrees with the body gets -32020 . 4. A broken response stream loses its request, so as I see it, proxy idle timeouts are the main operational risk. Two transports Side by side: | | stdio | Streamable HTTP | |---|---|---| | Typical deployment | A local process launched by the host | A remote service, in the cloud or on premises | | Connection | One process, stdin and stdout pipes | Independent HTTP POSTs | | Authorization | Credentials from the environment | OAuth 2.1 bearer tokens on every request, where authorization is used | | Cancellation | notifications/cancelled with the request ID | Close the response stream | | Notification streams | Multiplexed on the one channel, tagged with subscriptionId | A subscriptions/listen POST with a long-lived response stream | | Logging | Write to stderr , never stdout | OpenTelemetry or your platform’s logging | A third, older transport, HTTP+SSE HTTP with Server-Sent Events, or SSE , has been deprecated since 2025-03-26; part 2 https://pournasserian.com/writing/mcp-2026-what-changed has its removal timeline. stdio: stdout belongs to the protocol The host launches your server as a child process and exchanges JSON-RPC messages over its stdin and stdout. The rules: - Only protocol messages go to stdout. A stray print statement corrupts the stream. - Credentials come from the environment. Implementations using stdio SHOULD NOT follow the authorization specification https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization . In Python, log with the logging module: the official Python software development kit SDK , mcp 2.3.0, routes its output to stderr once you create an MCPServer : python import logging from mcp.server import MCPServer mcp = MCPServer "docs" also sets up logging on stderr, at INFO log = logging.getLogger "docs" @mcp.tool def search docs query: str - str: """Search the documentation.""" log.info "search docs query=%r", query stderr, never stdout return f"No results for {query r}" if name == " main ": mcp.run stdio by default: stdout carries the protocol Under the SDK’s client over stdio, the call returned and the log line went to stderr. The SDK points stdout at stderr while it serves stdio, a safety net I wouldn’t lean on: in my test on Windows, an unflushed print sat in Python’s buffer until the server stopped, then reached the client as a line it couldn’t parse. Shipping a local server A local server is simple to operate but runs arbitrary code on the user’s machine, so its risks are the supply chain and trust. My advice: publish signed, versioned packages, keep dependencies minimal, read secrets from the environment, and support the hosts’ sandboxing. VS Code https://code.visualstudio.com/docs/agent-customization/mcp-servers , for example, can sandbox stdio servers on macOS and Linux as of October 2026, with filesystem and network allowlists. Streamable HTTP: one POST per message The client sends each JSON-RPC message as an HTTP POST to the server’s single MCP endpoint. The server answers with one JSON response, or an SSE stream that carries request-scoped notifications, such as progress, then the final result. Here a gateway or Web Application Firewall WAF decides from the headers alone: The 2026-07-28 revision drops three things from Streamable HTTP https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http : - Sessions: the Mcp-Session-Id header is gone; part 5 https://pournasserian.com/writing/mcp-2026-stateless-requests shows where state goes instead. - The GET endpoint: notifications move to subscriptions/listen . - Resumability: a broken response stream loses the request in flight, and the client MUST re-issue it with a new ID. The request headers | Header | Sent on | What it carries | |---|---|---| | MCP-Protocol-Version | Every request | The protocol version, such as 2026-07-28 | | Mcp-Method | Every request | The JSON-RPC method, such as tools/call | | Mcp-Name | tools/call , prompts/get and resources/read | The tool or prompt name, or the resource’s Uniform Resource Identifier URI | | Mcp-Param-{name} | tools/call , for a parameter marked with x-mcp-header | That argument’s value | | Authorization | Every request to a server that uses OAuth | Bearer