Streamable HTTP
specification/draft/basic/transports/streamable-http
Nearest release: v2.1.246, published 8 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
specification/draft/basic/transports/streamable-http New page · 731 lines, new page
# Streamable HTTP ## Security & Endpoint ## Sending Messages ## Receiving Messages ## Message Flow ## Cancellation ## Request Metadata ### Protocol Version Header ### Standard Request Headers ### Custom Headers from Tool Parameters #### Schema Extension #### Value Encoding #### Client Behavior #### Server Behavior for Custom Headers ### Case Sensitivity ### Server Validation ## Backward Compatibility ### Earlier Streamable HTTP Revisions ### HTTP+SSE Transport (2024-11-05)
A whole new page. There's nothing to diff it against, so here is what it says.
# Streamable HTTP
<div id="enable-section-numbers" />
<Info>
Streamable HTTP was introduced in protocol version 2025-03-26 as a replacement
for the [HTTP+SSE transport][http-sse] from protocol version 2024-11-05.
</Info>
<Info>
Revision 2026-07-28 changed the behavior of Streamable HTTP. Clients must
ensure they handle backwards compatibility correctly. Changes included:
* Removal of the GET stream endpoint.
* Removal of protocol-level sessions.
See the [changelog](/specification/draft/changelog) and
[Backward Compatibility](#backward-compatibility) below.
</Info>
In the **Streamable HTTP** transport, the server operates as an independent
process that can handle multiple client connections. At a glance:
* The server exposes a single HTTP endpoint (the **MCP endpoint**) that
accepts POST.
* The client sends every JSON-RPC request or notification as its own HTTP
POST.
* The server answers each request with either a single JSON object or a
[Server-Sent Events][sse] (SSE) stream scoped to that request, carrying
request-related notifications followed by the final response.
* Server-to-client interactions (sampling, elicitation, roots) are embedded
in results as input requests per
[Multi Round-Trip Requests (MRTR)][mrtr] ([SEP-2322][sep-2322]).
* Long-lived change notifications (such as list changes and resource updates)
are delivered on the response stream of a
[`subscriptions/listen`][subscriptions-listen] request.
See [Message Flow](#message-flow) for sequence diagrams of these
interactions.
The server **MUST** provide a single HTTP endpoint path (hereafter referred to
as the **MCP endpoint**) that supports POST. For example, this could be a URL
like `https://example.com/mcp`.
[http-sse]: /specification/2024-11-05/basic/transports#http-with-sse
[sse]: https://en.wikipedia.org/wiki/Server-sent_events
## Security & Endpoint
When implementing Streamable HTTP transport:
1. Servers **MUST** validate the `Origin` header on all incoming connections
to prevent DNS rebinding attacks.
* If the `Origin` header is present and invalid, servers **MUST** respond
with HTTP 403 Forbidden. The HTTP response body **MAY** comprise a
JSON-RPC *error response* that has no `id`.
2. When running locally, servers **SHOULD** bind only to localhost
(127.0.0.1) rather than all network interfaces (0.0.0.0).
3. Servers **SHOULD** implement proper authentication for all connections.
Without these protections, attackers could use DNS rebinding to interact with
local MCP servers from remote websites.
## Sending Messages
Every JSON-RPC message sent from the client **MUST** be a new HTTP POST
request to the MCP endpoint.
1. The client **MUST** use HTTP POST to send JSON-RPC messages.
2. The client **MUST** include an `Accept` header listing both
`application/json` and `text/event-stream` as supported content types.
3. The client **MUST** include the [request metadata headers](#request-metadata)
on each POST request.
4. The body of the HTTP POST **MUST** be a single JSON-RPC *request* or
*notification*. The client **MUST NOT** send JSON-RPC *responses*.
5. If the body is a JSON-RPC *notification*:
* If the server accepts it, the server **MUST** return HTTP status code
`202 Accepted` with no body.
* If the server cannot accept it, it **MUST** return an HTTP error status
code (e.g., `400 Bad Request`). The HTTP response body **MAY** comprise
a JSON-RPC *error response* that has no `id`.
6. If the body is a JSON-RPC *request*, the server **MUST** return either
`Content-Type: application/json` (a single JSON object) or
`Content-Type: text/event-stream` (an SSE response stream). The client
**MUST** support both.
<Note>
This revision of the core protocol defines no client-to-server
*notifications* over Streamable HTTP. The only client-sent notification in
the core protocol, `notifications/cancelled`, is used only on the
[stdio](/specification/draft/basic/transports/stdio) transport; on
Streamable HTTP, closing the SSE response stream is itself the cancellation
signal and no `notifications/cancelled` message is expected (see
[Cancellation][cancellation]). The notification rules above describe the
transport mechanics for a notification POST; header requirements for
notification POSTs are not defined by this revision.
</Note>
## Receiving Messages
When the server returns an SSE response stream
(`Content-Type: text/event-stream`):
* The server **MAY** send JSON-RPC *notifications* — for example,
[`notifications/progress`][notifications-progress]
or [`notifications/message`][notifications-message] —
before the final response. These notifications **MUST** relate to the
originating client request.
* The server **MUST NOT** send independent JSON-RPC *requests* on this stream.
Server-to-client interactions (sampling, elicitation, list-roots) are
embedded as input requests inside an
[`InputRequiredResult`][input-required-result] per
[MRTR][mrtr] ([SEP-2322][sep-2322]), not delivered as separate requests on
this or any other stream. This is a change from Streamable HTTP in protocol
versions `2025-03-26` through `2025-11-25`, where servers could send such
requests on SSE streams.
* The final JSON-RPC *response* **SHOULD** terminate the stream.
Long-lived notification streams are obtained by sending a
[`subscriptions/listen`][subscriptions-listen]
request. The server's response is itself an SSE stream that stays open and
delivers the change notifications the client opted in to (such as
`notifications/tools/list_changed` or `notifications/resources/updated`).
Request-scoped notifications like `notifications/progress` and
`notifications/message` are **not** delivered on the listen stream — they
flow only on the response stream of the request they relate to.
When initiating an SSE stream, servers **SHOULD** include the
`X-Accel-Buffering: no` header in the HTTP response. This instructs reverse
proxies (such as nginx) to disable response buffering, ensuring that SSE
events are delivered to clients immediately rather than being held in a
buffer. Without this header, proxies may accumulate messages before sending
them to the client, introducing unwanted latency and potentially breaking the
real-time nature of SSE communication.
<Note>
For long-lived streams — in particular the
[`subscriptions/listen`][subscriptions-listen] response stream — servers are
encouraged to periodically emit an SSE comment line (a line beginning with a
colon, e.g. `:\r\n`) as a keep-alive. This keeps the connection from being
closed by intermediaries or client idle timeouts during quiet periods when no
notifications are flowing. Per the [SSE specification][sse], any line beginning
with a colon is a comment that carries no event data; clients must ignore such
lines and must not treat them as malformed input.
</Note>
Resumable SSE streams via `Last-Event-ID` are not supported.
[notifications-progress]: /specification/draft/basic/patterns/progress
[notifications-message]: /specification/draft/server/utilities/logging
[input-required-result]: /specification/draft/schema#inputrequiredresult
[mrtr]: /specification/draft/basic/patterns/mrtr
[sep-2322]: /seps/2322-MRTR
[subscriptions-listen]: /specification/draft/basic/patterns/subscriptions
## Message Flow
The following diagrams illustrate the message flows on a single MCP endpoint.
**Requests and responses.** Each request is its own POST; the server chooses
per request whether to respond with a single JSON object or an SSE stream:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
note over Client,Server: Simple response
Client->>Server: POST tools/call (JSON-RPC request)
Server-->>Client: 200 OK, application/json<br/>JSON-RPC response
note over Client,Server: Streaming response
Client->>Server: POST tools/call (JSON-RPC request)
note over Server: Opens SSE stream<br/>scoped to this request
Server-->>Client: SSE: notifications/progress
Server-->>Client: SSE: notifications/progress
Server-->>Client: SSE: JSON-RPC response
note over Client,Server: Stream closes
note over Client,Server: Notification
Client->>Server: POST (JSON-RPC notification)
Server-->>Client: 202 Accepted
```
**Server-to-client interactions (MRTR).** When the server needs input from
the client — sampling, elicitation, or roots — it does not send its own
JSON-RPC request. It returns an
[`InputRequiredResult`][input-required-result] containing `inputRequests`,
and the client retries the original request with the matching
`inputResponses` (see [Multi Round-Trip Requests][mrtr]):
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: POST tools/call (id: 1)
note over Server: Needs user input or<br/>an LLM completion
Server-->>Client: InputRequiredResult<br/>(inputRequests: elicitation/create)
note over Client: Gathers the requested input
Client->>Server: POST tools/call (id: 2)<br/>(original params + inputResponses)
Server-->>Client: Final result
```
**Change notifications.** Clients that want server-initiated change
notifications open a long-lived stream with
[`subscriptions/listen`][subscriptions-listen]; the response stream stays
open and carries only the notification types the client opted in to:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: POST subscriptions/listen<br/>(notification filter)
Server-->>Client: SSE: notifications/subscriptions/acknowledged
note over Client,Server: Stream stays open
Server-->>Client: SSE: notifications/tools/list_changed
Server-->>Client: SSE: notifications/resources/updated
note over Client,Server: Until the client or server closes the stream
```
## Cancellation
Closing the SSE response stream **MUST** be treated by the server as
cancellation of that request. Because each request has its own response
stream, the transport-level disconnect is unambiguous. The server **SHOULD**
stop work on the cancelled request as soon as practical and **MUST NOT** send
any further messages for it. See
[Cancellation][cancellation] for the full rules.
[cancellation]: /specification/draft/basic/patterns/cancellation
## Request Metadata
The Streamable HTTP transport mirrors selected JSON-RPC body fields into HTTP
headers so that intermediaries (load balancers, gateways, observability
tooling) can route and inspect requests without parsing the body.
### Protocol Version Header
Every POST request to the MCP endpoint **MUST** include an
`MCP-Protocol-Version` header.
For example: `MCP-Protocol-Version: 2026-07-28`
The header value **MUST** match the
`io.modelcontextprotocol/protocolVersion` field carried in the request body's
`_meta`. If the values do not match, the server **MUST** reject the request
with `400 Bad Request` and a `HeaderMismatch` JSON-RPC error
(see [Server Validation](#server-validation)).
If the server does not implement the requested protocol version (whether the
version is unknown to the server, or is a known version the server has chosen
not to support), it **MUST** respond with `400 Bad Request` and an
[`UnsupportedProtocolVersionError`][unsupported-version]
listing its supported versions. See
[Versioning: Protocol Version Negotiation][lifecycle-version]
for the negotiation flow.
If the server does not implement the requested RPC method, it **MUST** respond
with `404 Not Found` and a JSON-RPC error with code `-32601`
(`Method not found`). The JSON-RPC error body distinguishes this case from a
`404` returned by a legacy [HTTP+SSE][http-sse] server that does not host the
modern MCP endpoint (see [Backward Compatibility](#backward-compatibility)).
A server that supports clients implementing protocol versions earlier than
`2025-06-18` (which did not define the `MCP-Protocol-Version` header) **MAY**
treat a request that omits the header as protocol version `2025-03-26`. A
server that does not support such clients **MUST** reject a request without
the header per [Server Validation](#server-validation).
[unsupported-version]: /specification/draft/schema#unsupportedprotocolversionerror
[lifecycle-version]: /specification/draft/basic/versioning#protocol-version-negotiation
### Standard Request Headers
| Header Name | Source Field | Required For |
| ------------ | ----------------------------- | ------------------------------------------------------ |
| `Mcp-Method` | `method` | All requests |
| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
These headers are **REQUIRED** for compliance.
If the `Mcp-Name` source value cannot be safely represented as a plain ASCII
header value, clients **MUST** encode it using the Base64 sentinel format
described in [Value Encoding](#value-encoding).
**`tools/call` request:**
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Cut at 300 lines. The page has the rest.