Register

MCP transports: stdio and Streamable HTTP

A transport in the Model Context Protocol (MCP) is a binding that defines how JSON-RPC messages between a client and a server are framed and delivered, how request metadata is carried and how cancellation and termination are signaled. A transport does not change the meaning of the messages: the same requests and results travel over every transport. The specification defines two standard transports, stdio for local servers and Streamable HTTP for remote servers.

The first version of the specification, 2024-11-05, defined stdio and HTTP with Server-Sent Events (HTTP+SSE). Streamable HTTP replaced HTTP+SSE in version 2025-03-26. The current version of the specification is 2026-07-28: it removed protocol sessions from Streamable HTTP, and since then any server instance can process any request. The protocol is developed under the Agentic AI Foundation, part of the Linux Foundation. The overall architecture of MCP is described in the article What is MCP.

Overview

Property Value
Layer Transport layer of MCP
Message format JSON-RPC 2.0, UTF-8
Standard transports stdio, Streamable HTTP
stdio framing One JSON-RPC message per line
Streamable HTTP endpoint A single MCP endpoint that accepts POST
Server reply over HTTP A JSON object or an SSE stream scoped to the request
Protocol sessions None since 2026-07-28
Deprecated transport HTTP+SSE from 2024-11-05, deprecated since 2025-03-26
Current specification 2026-07-28

The problem transports solve

MCP servers run in two different ways. A local server is a program on the user's machine, often started by the host itself. A remote server is a network service that many users reach over the internet. These cases need different delivery: a pipe between two processes in one case, HTTP with load balancers, gateways and authorization in the other.

The protocol separates the meaning of messages from their delivery. The data layer defines the JSON-RPC messages: discovery, primitives, notifications, errors. The transport layer defines how these messages reach the other side. A server written once can therefore be deployed locally or remotely, and its logic does not depend on the transport.

History

The table shows how the transports changed between versions of the specification.

Version Changes in transports
2024-11-05 stdio and HTTP+SSE: an SSE stream for server messages and a separate endpoint for POST requests; Origin validation required
2025-03-26 Streamable HTTP replaces HTTP+SSE: one MCP endpoint for POST and GET, optional SSE, sessions with Mcp-Session-Id, resumable streams with Last-Event-ID; JSON-RPC batching
2025-06-18 Batching removed; the MCP-Protocol-Version header required in HTTP requests
2025-11-25 HTTP 403 for an invalid Origin; servers may close an SSE connection, and clients reconnect with Last-Event-ID; stderr allowed for all kinds of logs
2026-07-28 No sessions, no GET stream and no stream resumption; Mcp-Method and Mcp-Name headers; no requests from the server; subscriptions/listen for notifications; HTTP+SSE classified as Deprecated

What a transport defines

All MCP messages are JSON-RPC messages encoded in UTF-8. A transport delivers requests and notifications from the client to the server and responses and notifications from the server to the client. In version 2026-07-28 servers do not send JSON-RPC requests: when a server needs input from the client, it returns an InputRequiredResult within the Multi Round-Trip Requests (MRTR) mechanism.

All protocol metadata travels in the message body. Every request carries its protocol version and the capabilities of the client in the _meta field. A transport may copy selected fields of the body into its own envelope, as Streamable HTTP does with headers. The body remains the source of truth, and a transport that copies metadata defines how mismatches are rejected.

Each transport also defines how a client abandons a request that is still running: on stdio the client sends a notification, on Streamable HTTP it closes the response stream of the request. The stateless request model of 2026-07-28 is described in the article Stateless MCP.

stdio

Launching the server

In the stdio transport the client launches the MCP server as a subprocess and communicates with it over the standard streams of that process. The server reads messages from standard input (stdin) and writes messages to standard output (stdout). The host takes the command from its configuration, for example from the file claude_desktop_config.json of Claude Desktop shown in the MCP documentation:

JSON
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Desktop"
      ]
    }
  }
}

The server process runs with the same privileges as the host. The specification states that a server using stdio should not follow the MCP authorization flow and should take credentials from its environment, for example from environment variables.

Message framing

Messages are separated by newlines and must not contain embedded newlines, so every line of the stream is exactly one JSON-RPC message. A request to server/discover written to stdin looks like this:

JSON
{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}

There is no header layer: the method, the arguments and all metadata are in the JSON body. All messages from the server share one channel, stdout: responses, correlated with requests by the JSON-RPC id, notifications related to a running request and notifications of subscriptions/listen subscriptions. The client tells subscriptions apart by the io.modelcontextprotocol/subscriptionId field in _meta.

Standard output and standard error

The server must not write anything to stdout that is not a valid MCP message, and the client must not write anything else to the server's stdin. A single debug print to stdout breaks the stream. The server guides of the MCP documentation therefore warn against print() in Python and console.log() in JavaScript in stdio servers, because both write to standard output by default.

Logs go to standard error (stderr). The server may write any UTF-8 text there: informational, debug and error messages. The client may capture, forward or ignore this stream and should not treat output on stderr as a sign of an error. Since 2026-07-28 the protocol feature Logging is deprecated, and the specification names stderr as the replacement for stdio servers.

Cancellation

To cancel a running request, the client sends the notification notifications/cancelled with the ID of that request:

JSON
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": { "requestId": 3, "reason": "User cancelled the operation" }
}

The server should stop working on the request as soon as practical and must not send any further messages for it. On stdio the server also uses this notification in one case: to end a subscriptions/listen stream it opened earlier.

Shutdown and restart

The client should shut the server down in three steps:

  1. Close the input stream of the child process.
  2. Wait for the server to exit.
  3. If the server does not exit within a reasonable time, terminate the process. On POSIX systems this usually means SIGTERM and then SIGKILL; on Windows TerminateProcess or Job Objects.

The server should exit promptly when its standard input is closed: the specification calls this the only portable signal of a graceful shutdown. If the server process exits unexpectedly, the client should restart it. Requests that were in progress are lost, and the client can send them again to the new process; active subscriptions/listen streams are opened again.

Trust between the processes

According to the MCP documentation on local server security, the client and a stdio server share one trust domain: the server already runs code on the user's machine, and the transport is not a sandbox. Isolation, if needed, is provided by the host or the operator of the machine, for example with a container.

Streamable HTTP

Endpoint and requests

In the Streamable HTTP transport the server runs as an independent process that handles connections from many clients. It provides a single HTTP endpoint, the MCP endpoint, which accepts POST requests, for example https://example.com/mcp.

Every JSON-RPC message from the client is a new HTTP POST request to the MCP endpoint. The body contains exactly one request or one notification. The client must send an Accept header that lists both application/json and text/event-stream, and it must send the request metadata headers described below. A request for a prompt looks like this:

HTTP
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: prompts/get
Mcp-Name: code_review

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": { "code": "def hello():\n    print('world')" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

If the body is a notification and the server accepts it, the server answers 202 Accepted with no body. The core protocol of 2026-07-28 defines no client notifications for Streamable HTTP, so this rule describes only the mechanics of the transport.

Responses

Server-Sent Events (SSE) is an HTTP response format in which the server sends a stream of events over one response. For a request the server chooses one of two forms, and the client must support both:

  • Content-Type: application/json: a single JSON object with the JSON-RPC response.
  • Content-Type: text/event-stream: an SSE stream scoped to this request. The stream can carry notifications related to the request, for example notifications/progress, and then the final response, which should end the stream.

Unlike versions up to 2025-11-25, the server must not send its own JSON-RPC requests on the stream. When it opens an SSE stream, the server should send the header X-Accel-Buffering: no, so that reverse proxies such as nginx do not buffer the events.

Notification streams

The standalone GET stream of earlier versions was removed in 2026-07-28. To receive change notifications, for example notifications/tools/list_changed, the client sends a subscriptions/listen request by POST. The response is an SSE stream that stays open and carries only the notification types the client requested. Progress and other notifications of a single request come only on the response stream of that request.

Request headers

Streamable HTTP copies selected fields of the JSON-RPC body into HTTP headers. Load balancers, gateways and observability tools can then route and inspect requests without parsing the body. The table lists the headers of version 2026-07-28.

Header Source in the body Required for
MCP-Protocol-Version io.modelcontextprotocol/protocolVersion in _meta Every POST request
Mcp-Method method All requests
Mcp-Name params.name or params.uri tools/call, resources/read, prompts/get
Mcp-Param-{Name} A tool parameter marked with x-mcp-header Calls of tools that declare such parameters

The Mcp-Param-{Name} headers are created from the x-mcp-header property in the input schema of a tool, as described in the article Tools in MCP. Servers may use this property, and clients must support it.

A value of Mcp-Name or Mcp-Param-{Name} that cannot be sent as a plain ASCII header is encoded in Base64 in the form =?base64?{value}?=. Header names are compared without regard to case, while header values, such as method names, are case-sensitive.

Validation of headers

A server that processes the body must reject a request if a header does not match the body, if a required header is missing or if a header contains invalid characters. Before comparing, the server decodes values in Base64 form. The rejection is 400 Bad Request with the JSON-RPC error -32020 (HeaderMismatch):

JSON
{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32020,
    "message": "Header mismatch: Mcp-Method header value 'tools/call' does not match body value 'prompts/get'"
  }
}

The check prevents network components from relying on different sources of truth, for example a load balancer that routes by the header while the server executes the body.

HTTP status codes

The table lists the status codes that the specification defines for the transport.

Status When
200 OK Response to a request: a JSON object or an SSE stream
202 Accepted A notification is accepted; no body
400 Bad Request Header mismatch (-32020), missing required client capability (-32021), unsupported protocol version (-32022), a notification that cannot be accepted
403 Forbidden An invalid Origin header
404 Not Found The server does not implement the method; JSON-RPC error -32601
405 Method Not Allowed GET or DELETE from a client of an earlier version

Authorization adds 401 Unauthorized and 403 Forbidden for missing, invalid or insufficient tokens.

Cancellation and broken streams

On Streamable HTTP closing the SSE response stream is the cancellation of that request. Each request has its own stream, so the disconnect is unambiguous, and no notifications/cancelled message is expected. The server should stop working on the request and must not send any further messages for it.

Streams cannot be resumed since 2026-07-28: the Last-Event-ID header and SSE event IDs were removed. If a response stream breaks, the request in progress is lost. The client must send it again as a new request with a new ID.

No sessions and load balancing

Version 2026-07-28 removed protocol sessions and the Mcp-Session-Id header. According to the release announcement, any request can now land on any server instance behind a plain round-robin load balancer, without shared storage.

State that must survive between calls is now passed explicitly: a tool returns a handle, and the model passes it back as an argument of the next call. The changes of the release and the migration are described in the article Stateless MCP.

Security of local HTTP servers

The transport specification contains three security requirements:

  1. Servers must validate the Origin header of all incoming connections to prevent DNS rebinding attacks. If Origin is present and invalid, the server must answer 403 Forbidden.
  2. A server that runs locally should bind only to localhost (127.0.0.1), not to all network interfaces (0.0.0.0).
  3. Servers should implement proper authentication for all connections.

Without these protections, according to the specification, attackers could use DNS rebinding to reach local MCP servers from remote websites. The MCP documentation adds that a port on 127.0.0.1 is reachable by other local processes and, through the browser, by websites.

Binding to 127.0.0.1 is not an authentication boundary. A local HTTP server needs Origin validation and authentication as well.

Other risks are described in the article MCP security.

Authorization

Authorization in MCP is optional and applies to HTTP-based transports. Implementations over HTTP should follow the authorization framework of the specification, which is based on OAuth 2.1: the client sends a token in the Authorization header of every request. Servers over stdio should not use it and take credentials from the environment instead. The details are in the article Authorization in MCP.

Backward compatibility

Earlier Streamable HTTP revisions

Versions 2025-03-26 to 2025-11-25 used Streamable HTTP with sessions (Mcp-Session-Id, ended with DELETE), a standalone GET stream, server requests on SSE streams and resumption with Last-Event-ID. A server that supports only 2026-07-28 answers GET and DELETE with 405 Method Not Allowed and ignores Mcp-Session-Id and Last-Event-ID.

A client that also supports versions with the initialize handshake first sends a request in the current format. On 400 Bad Request it inspects the body: a recognized JSON-RPC error of the current version means a modern server, while an empty body or an unknown error means an older one, and the client falls back to initialize. On stdio the client probes with server/discover. The fallback must not depend on one specific error code, because older servers answer unknown requests in different ways or not at all.

HTTP+SSE transport

The HTTP+SSE transport of version 2024-11-05 used two endpoints. The client opened an SSE stream, the server sent an endpoint event with the address for messages, and the client sent all messages as POST requests to that address.

HTTP+SSE has been deprecated since 2025-03-26. Version 2026-07-28 classified it as Deprecated under the feature lifecycle policy (SEP-2596): new implementations should not adopt it. According to the registry of deprecated features, it becomes eligible for removal three months after SEP-2596 reached Final status.

A server can keep the old endpoints next to the new MCP endpoint. A client first sends a POST request to the server URL. If the answer is 400, 404 or 405 without a recognized JSON-RPC error of the current version, it sends a GET request, and an endpoint event as the first event means HTTP+SSE.

Custom transports

Clients and servers may implement their own transports. The protocol does not depend on the transport and can work over any channel that supports bidirectional message exchange. A custom transport must preserve the JSON-RPC message format, the message patterns and the per-request metadata model. It should document how a connection is established, how messages are framed and how cancellation works.

A custom transport over a reliable bidirectional byte stream, such as Unix domain sockets or TCP, should reuse the stdio framing: one JSON-RPC message per line.

stdio and Streamable HTTP compared

The table compares the two standard transports.

stdio Streamable HTTP
Typical deployment Local server on the user's machine Remote server, network service
Who starts the server The client, as a subprocess The operator; the server runs independently
Clients of one server process One, the client that started it Many
Framing One JSON-RPC message per line One message per POST request
Request metadata Only in the body In the body and in headers
Messages from the server One shared stdout channel A JSON object or an SSE stream per request
Change notifications On stdout, marked with the subscription ID On the SSE stream of subscriptions/listen
Cancellation notifications/cancelled Closing the response stream
Logs stderr On the server side, for example OpenTelemetry
Credentials From the environment of the process Authorization framework based on OAuth 2.1
Main security concern Shared trust domain with the host Origin validation, authentication

The choice of transport follows from where the server has to run. According to the MCP documentation, reading local files, driving a local tool or reaching hardware are reasons to run locally, while a server that only wraps a remote API gains nothing from it. A remote server over Streamable HTTP runs no code on the user's machine and uses the authorization flow instead of a token in a local configuration file.

The server logic does not depend on the transport, so one server can support both: the same tools, resources and prompts are then available locally and remotely.

Terms

Term Meaning
Transport Binding that defines how MCP messages are framed and delivered
stdio Transport over the standard streams of a subprocess started by the client
Streamable HTTP Transport in which each message is an HTTP POST to one MCP endpoint
MCP endpoint Single HTTP path of a server that accepts POST requests with MCP messages
Server-Sent Events (SSE) HTTP response format with a stream of events from the server
HTTP+SSE Transport of 2024-11-05 with two endpoints; deprecated since 2025-03-26
DNS rebinding Attack in which a website reaches a server on the user's machine through the browser
Custom transport Transport outside the specification that keeps its messages and metadata

Sources

Series

Model Context Protocol (MCP)
  1. What is MCP (Model Context Protocol)
  2. Tools in MCP
  3. Resources and resource templates in MCP
  4. Prompts in MCP
  5. MCP transports: stdio and Streamable HTTPCurrent
  6. Stateless MCP: changes in 2026-07-28
  7. Authorization in MCP
  8. MCP security
  9. MCP extensions
  10. MCP server over an existing API
  11. Testing MCP servers: the basics
  12. Evaluating an MCP server with models
  13. Regression testing of an MCP server
  14. Publishing an MCP server to the MCP Registry

Wiki articles are written and maintained by the ComplexQA team.