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:
{
"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:
{"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:
{
"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:
- Close the input stream of the child process.
- Wait for the server to exit.
- If the server does not exit within a reasonable time, terminate the process. On POSIX systems this usually means
SIGTERMand thenSIGKILL; on WindowsTerminateProcessor 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:
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 examplenotifications/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):
{
"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:
- Servers must validate the
Originheader of all incoming connections to prevent DNS rebinding attacks. IfOriginis present and invalid, the server must answer403 Forbidden. - A server that runs locally should bind only to localhost (127.0.0.1), not to all network interfaces (0.0.0.0).
- 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
Originvalidation 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
- Transports overview, specification 2026-07-28(opens in a new tab)
- stdio, specification 2026-07-28(opens in a new tab)
- Streamable HTTP, specification 2026-07-28(opens in a new tab)
- Subscriptions, specification 2026-07-28(opens in a new tab)
- Base protocol overview, specification 2026-07-28(opens in a new tab)
- Key changes in 2026-07-28(opens in a new tab)
- Deprecated features, specification 2026-07-28(opens in a new tab)
- Transports, specification 2025-11-25(opens in a new tab)
- Transports, specification 2025-03-26(opens in a new tab)
- Transports, specification 2024-11-05(opens in a new tab)
- Local Server Security, MCP documentation(opens in a new tab)
- Build an MCP server, MCP documentation(opens in a new tab)
- The 2026-07-28 Specification, MCP blog, 28 July 2026(opens in a new tab)