Streamable HTTP was introduced in protocol version 2025-03-26 as a replacement
for the HTTP+SSE transport from protocol version 2024-11-05.
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.
- 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) 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) (SEP-2322).
- Long-lived change notifications (such as list changes and resource updates)
are delivered on the response stream of a
subscriptions/listenrequest.
https://example.com/mcp.
Security & Endpoint
When implementing Streamable HTTP transport:- Servers MUST validate the
Originheader on all incoming connections to prevent DNS rebinding attacks.- If the
Originheader is present and invalid, servers MUST respond with HTTP 403 Forbidden. The HTTP response body MAY comprise a JSON-RPC error response that has noid.
- If the
- When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0).
- Servers SHOULD implement proper authentication for all connections.
Sending Messages
Every JSON-RPC message sent from the client MUST be a new HTTP POST request to the MCP endpoint.- The client MUST use HTTP POST to send JSON-RPC messages.
- The client MUST include an
Acceptheader listing bothapplication/jsonandtext/event-streamas supported content types. - The client MUST include the request metadata headers on each POST request.
- The body of the HTTP POST MUST be a single JSON-RPC request or notification. The client MUST NOT send JSON-RPC responses.
- If the body is a JSON-RPC notification:
- If the server accepts it, the server MUST return HTTP status code
202 Acceptedwith 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 noid.
- If the server accepts it, the server MUST return HTTP status code
- If the body is a JSON-RPC request, the server MUST return either
Content-Type: application/json(a single JSON object) orContent-Type: text/event-stream(an SSE response stream). The client MUST support both.
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/progressornotifications/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
InputRequiredResultper MRTR (SEP-2322), not delivered as separate requests on this or any other stream. This is a change from Streamable HTTP in protocol versions2025-03-26through2025-11-25, where servers could send such requests on SSE streams. - The final JSON-RPC response SHOULD terminate the stream.
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.
Resumable SSE streams via Last-Event-ID are not supported.
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: 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 anInputRequiredResult containing inputRequests,
and the client retries the original request with the matching
inputResponses (see Multi Round-Trip Requests):
Change notifications. Clients that want server-initiated change
notifications open a long-lived stream with
subscriptions/listen; the response stream stays
open and carries only the notification types the client opted in to:
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 for the full rules.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 anMCP-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).
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
listing its supported versions. See
Versioning: Protocol Version Negotiation
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 server that does not host the
modern MCP endpoint (see 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.
Standard Request Headers
| Header Name | Source Field | Required For |
|---|---|---|
Mcp-Method | method | All requests and notifications |
Mcp-Name | params.name or params.uri | tools/call, resources/read, prompts/get requests |
tools/call request:
resources/read request:
Custom Headers from Tool Parameters
MCP servers MAY designate specific tool parameters to be mirrored into HTTP headers using anx-mcp-header extension property in the parameter’s
schema within the tool’s inputSchema. See
Tool Definitions for
details on how to annotate tool parameters.
While the use of x-mcp-header is optional for servers, clients MUST
support this feature. When a server’s tool definition includes
x-mcp-header annotations, conforming clients MUST mirror the
designated parameter values into HTTP headers.
Schema Extension
Thex-mcp-header property specifies the name portion used to construct
the header name Mcp-Param-{name}.
Constraints on x-mcp-header values:
- MUST NOT be empty
- MUST match HTTP field-name token syntax (
1*tchar, RFC 9110 Section 5.1) - MUST NOT contain control characters, including carriage return (CR,
\r) or line feed (LF,\n) - MUST be case-insensitively unique among all
x-mcp-headervalues in theinputSchema - MUST only be applied to parameters with primitive types (integer,
string, boolean). Parameters with type
numberare not permitted. Integer values MUST be within the safe range for JavaScript (−253+1 to 253−1) - MAY be applied to properties at any nesting depth within the
inputSchema, not only top-level properties
x-mcp-header value violates these constraints. Rejection means
the client MUST exclude the invalid tool from the result of tools/list.
Clients SHOULD log a warning when rejecting a tool definition, including
the tool name and the reason for rejection. This ensures that a single
malformed tool definition does not prevent other valid tools from being used.
Clients using other transports (e.g., stdio) MAY ignore x-mcp-header
annotations entirely.
Example tool definition:
Value Encoding
Clients MUST encode parameter values before including them in HTTP headers to ensure safe transmission and prevent injection attacks. Type conversion: Convert the parameter value to its string representation:string: Use the value as-isinteger: Convert to decimal string representation (e.g.,42,-7)boolean: Convert to lowercase"true"or"false"
=?base64? and suffix ?= indicate that the value is
Base64-encoded. These markers are case-sensitive and MUST appear exactly
as shown (lowercase). Servers and intermediaries that need to inspect these
values MUST decode them accordingly.
To avoid ambiguity, clients MUST also Base64-encode any plain-ASCII
value that matches the sentinel pattern (i.e., starts with =?base64?
and ends with ?=).
Encoding examples:
| Original Value | Reason | Encoded Header Value |
|---|---|---|
"us-west1" | Plain ASCII | Mcp-Param-Region: us-west1 |
"Hello, 世界" | Contains non-ASCII | Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
" padded " | Leading/trailing spaces | Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | Contains newline | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
"=?base64?literal?=" | Matches sentinel pattern | Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?= |
Client Behavior
When constructing atools/call request via HTTP transport, the client
MUST:
- Extract the values for any standard headers from the request body (e.g.,
method,params.name,params.uri). - Append the
Mcp-Methodheader and, if applicable,Mcp-Nameheader to the request. - Inspect the tool’s
inputSchemafor properties marked withx-mcp-headerand extract the value for each parameter. - Encode the values according to the Value Encoding rules.
- Append a
Mcp-Param-{Name}: {Value}header to the request.
If the client does not have the tool’s
inputSchema (e.g., tools/list
has not yet been called) or the cached schema is stale (e.g., its TTL has
expired), the client SHOULD send the request without custom
Mcp-Param-* headers. If the server rejects the request because required
custom headers are missing, the client SHOULD call tools/list to
obtain the current inputSchema, then retry the original request with the
appropriate headers. Clients MAY pre-load tool definitions via other
means (e.g., from a previous session or configuration) to enable header
emission without a prior tools/list call.Server Behavior for Custom Headers
Intermediate servers that do not recognize anMcp-Param-{Name} header
MUST forward it and otherwise ignore it, as required by the
HTTP Semantics RFC.
Servers MUST reject requests with a recognized Mcp-Param-{Name} header
that contains invalid characters (see Value Encoding).
Any server that processes the message body MUST validate that encoded
header values, after decoding if Base64-encoded, match the corresponding
values in the request body. Servers MUST reject requests with a
400 Bad Request HTTP status and JSON-RPC error code -32001
(HeaderMismatch) if any validation fails.
| Scenario | Client Behavior | Server Behavior |
|---|---|---|
| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
Parameter value is null | Client MUST omit the header | Server MUST NOT expect the header |
| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
Case Sensitivity
Header names (called “field names” in RFC 9110) are case-insensitive. Clients and servers MUST use case-insensitive comparisons for header names. Header values (such as method names) are case-sensitive.Server Validation
Servers that process the request body MUST reject requests where the values specified in the headers do not match the corresponding values in the request body. This prevents potential security vulnerabilities when different components in the network rely on different sources of truth (e.g., a load balancer routing on the header value while the MCP server executes based on the body value).When validating integer parameter values, servers SHOULD compare the
header value and the body value numerically rather than as strings (e.g.,
42.0 and 42 are considered equal).400 Bad Request and SHOULD include a JSON-RPC error
response using the following error code:
| Code | Name | Description |
|---|---|---|
-32001 | HeaderMismatch | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
-32000 to -32099).
Example error response:
- A required standard header (
MCP-Protocol-Version,Mcp-Method,Mcp-Name) is missing. - A header value does not match the corresponding request body value.
- A header value contains invalid characters.
Intermediaries MUST return an appropriate HTTP error status (e.g.,
400 Bad Request) for validation failures but are not required to return
a JSON-RPC error response.Intermediaries that enforce policy based on mirrored headers (e.g., routing
or rate-limiting by tenant) SHOULD verify that the
MCP-Protocol-Version
header indicates a version that requires header–body validation. If the
version is older or the header is absent, the intermediary SHOULD reject
the request rather than trusting unvalidated header values.Backward Compatibility
A client that supports both modern (per-request-metadata) MCP versions and a legacy version that requires aninitialize handshake MAY detect which
era the server implements by attempting a modern request first. On
400 Bad Request, the client SHOULD inspect the response body before
falling back: modern servers also use 400 for
UnsupportedProtocolVersionError,
MissingRequiredClientCapabilityError, and header-validation failures.
- If the body contains a recognized modern JSON-RPC error, the server speaks
a modern version of MCP — retry using the advertised
supportedversions or correct the request, rather than falling back. - If the body is empty or is not a recognized modern JSON-RPC error, fall
back to
initializeand continue with the legacy version for subsequent requests.
Earlier Streamable HTTP Revisions
Protocol versions2025-03-26 through 2025-11-25
also used the Streamable HTTP transport, but in a different shape: servers could assign a session via
the Mcp-Session-Id header (terminated with HTTP DELETE), clients could open
a standalone SSE stream with HTTP GET to receive server-initiated messages,
servers could send JSON-RPC requests on SSE streams, and streams were
resumable via Last-Event-ID. None of these mechanisms are part of this
revision.
A server that supports only this revision and receives such traffic from an
older client SHOULD respond as follows:
- HTTP GET or DELETE to the MCP endpoint: respond with
405 Method Not Allowed. - An
Mcp-Session-Idheader on a request: ignore it, and do not mint or echo session IDs. - A
Last-Event-IDheader: ignore it; streams are not resumable.
HTTP+SSE Transport (2024-11-05)
Clients and servers can maintain backward compatibility with the deprecated HTTP+SSE transport (from protocol version 2024-11-05) as follows: Servers wanting to support older clients should:- Continue to host both the SSE and POST endpoints of the old transport,
alongside the new “MCP endpoint” defined for the Streamable HTTP transport.
- It is also possible to combine the old POST endpoint and the new MCP endpoint, but this may introduce unneeded complexity.
- Accept an MCP server URL from the user, which may point to either a server using the old transport or the new transport.
- Attempt to POST a request to the server URL, with an
Acceptheader as defined above:- If it succeeds, the client can assume this is a server supporting the new Streamable HTTP transport.
- If it fails with HTTP status code
400 Bad Request,404 Not Found, or405 Method Not Allowedand the response body is not a recognized modern JSON-RPC error (a modern server returns one for unsupported version, unknown method, or header-validation failure):- Issue a GET request to the server URL, expecting that this will open
an SSE stream and return an
endpointevent as the first event. - When the
endpointevent arrives, the client can assume this is a server running the old HTTP+SSE transport, and should use that transport for all subsequent communication.
- Issue a GET request to the server URL, expecting that this will open
an SSE stream and return an