MCP uses JSON-RPC to encode messages. JSON-RPC messages MUST be UTF-8 encoded.
The protocol currently defines two standard transport mechanisms for client-server
communication:
- stdio, communication over standard in and standard out
- Streamable HTTP
Clients SHOULD support stdio whenever possible.
It is also possible for clients and servers to implement
custom transports in a pluggable fashion.
stdio
In the stdio transport:
- The client launches the MCP server as a subprocess.
- The server reads JSON-RPC messages from its standard input (
stdin) and sends messages
to its standard output (stdout).
- Messages are individual JSON-RPC requests, notifications, or responses.
- Messages are delimited by newlines, and MUST NOT contain embedded newlines.
- The server MAY write UTF-8 strings to its standard error (
stderr) for any
logging purposes including informational, debug, and error messages.
- The client MAY capture, forward, or ignore the server’s
stderr output
and SHOULD NOT assume stderr output indicates error conditions.
- The server MUST NOT write anything to its
stdout that is not a valid MCP message.
- The client MUST NOT write anything to the server’s
stdin that is not a valid MCP
message.
Streamable HTTP
In the Streamable HTTP transport, the server operates as an independent process that
can handle multiple client connections. This transport uses HTTP POST and GET requests.
Server can optionally make use of
Server-Sent Events (SSE) to stream
multiple server messages. This permits basic MCP servers, as well as more feature-rich
servers supporting streaming and server-to-client notifications and requests.
The server MUST provide a single HTTP endpoint path (hereafter referred to as the
MCP endpoint) that supports both POST and GET methods. For example, this could be a
URL like https://example.com/mcp.
Security Warning
When implementing Streamable HTTP transport:
- 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
- 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
Without these protections, attackers could use DNS rebinding to interact with local MCP servers from remote websites.
Sending Messages to the Server
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 to the MCP endpoint.
- The client MUST include an
Accept header, listing both application/json and
text/event-stream as supported content types.
- The client MUST include the standard MCP request headers
on each POST request.
- The body of the HTTP POST request MUST be a single JSON-RPC request, notification, or response to a server-sent request.
- If the body is a JSON-RPC notification or response to a server-sent request:
- If the server accepts the input, the server MUST return HTTP status code 202
Accepted with no body.
- If the server cannot accept the input, 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.
- If the body is a JSON-RPC request, the server MUST either
return
Content-Type: text/event-stream, to initiate an SSE stream, or
Content-Type: application/json, to return one JSON object. The client MUST
support both these cases.
- If the server initiates an SSE stream:
- The server SHOULD immediately send an SSE event consisting of an event
ID and an empty
data field in order to prime the client to reconnect
(using that event ID as Last-Event-ID).
- After the server has sent an SSE event with an event ID to the client, the
server MAY close the connection (without terminating the SSE stream)
at any time in order to avoid holding a long-lived connection. The client
SHOULD then “poll” the SSE stream by attempting to reconnect.
- If the server does close the connection prior to terminating the SSE stream,
it SHOULD send an SSE event with a standard
retry field before
closing the connection. The client MUST respect the retry field,
waiting the given number of milliseconds before attempting to reconnect.
- The SSE stream SHOULD eventually include a JSON-RPC response for the
JSON-RPC request sent in the POST body.
- The server MAY send JSON-RPC requests and notifications before sending the
JSON-RPC response. These messages MUST relate to the originating client
request.
- The server MAY terminate the SSE stream if the session
expires.
- After the JSON-RPC response has been sent, the server SHOULD terminate the
SSE stream.
- Disconnection MAY occur at any time (e.g., due to network conditions).
Therefore:
- Disconnection SHOULD NOT be interpreted as the client cancelling its request.
- To cancel, the client SHOULD explicitly send an MCP
CancelledNotification.
- To avoid message loss due to disconnection, the server MAY make the stream
resumable.
Listening for Messages from the Server
- The client MAY issue an HTTP GET to the MCP endpoint. This can be used to open an
SSE stream, allowing the server to communicate to the client, without the client first
sending data via HTTP POST.
- The client MUST include an
Accept header, listing text/event-stream as a
supported content type.
- The server MUST either return
Content-Type: text/event-stream in response to
this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server
does not offer an SSE stream at this endpoint. Per RFC 9110 §15.5.6, if the server returns HTTP 405, it
MUST include an Allow header listing the methods it does support (e.g.,
Allow: POST).
- If the server initiates an SSE stream:
- The server MAY send JSON-RPC notifications and pings on the stream.
- These messages SHOULD be unrelated to any concurrently-running JSON-RPC
request from the client, except that
roots/list,
sampling/createMessage, and elicitation/create requests MUST NOT be
sent on standalone streams.
- The server MUST NOT send a JSON-RPC response on the stream unless
resuming a stream associated with a previous client
request.
- The server MAY close the SSE stream at any time.
- If the server closes the connection without terminating the stream, it
SHOULD follow the same polling behavior as described for POST requests:
sending a
retry field and allowing the client to reconnect.
- The client MAY close the SSE stream at any time.
Multiple Connections
- The client MAY remain connected to multiple SSE streams simultaneously.
- The server MUST send each of its JSON-RPC messages on only one of the connected
streams; that is, it MUST NOT broadcast the same message across multiple streams.
- The risk of message loss MAY be mitigated by making the stream
resumable.
Resumability and Redelivery
To support resuming broken connections, and redelivering messages that might otherwise be
lost:
- Servers MAY attach an
id field to their SSE events, as described in the
SSE standard.
- If present, the ID MUST be globally unique across all streams within that
session—or all streams with that specific client, if session
management is not in use.
- Event IDs SHOULD encode sufficient information to identify the originating
stream, enabling the server to correlate a
Last-Event-ID to the correct stream.
- If the client wishes to resume after a disconnection (whether due to network failure
or server-initiated closure), it SHOULD issue an HTTP GET to the MCP endpoint,
and include the
Last-Event-ID
header to indicate the last event ID it received.
- The server MAY use this header to replay messages that would have been sent
after the last event ID, on the stream that was disconnected, and to resume the
stream from that point.
- The server MUST NOT replay messages that would have been delivered on a
different stream.
- This mechanism applies regardless of how the original stream was initiated (via
POST or GET). Resumption is always via HTTP GET with
Last-Event-ID.
In other words, these event IDs should be assigned by servers on a per-stream basis, to
act as a cursor within that particular stream.
Session Management
An MCP “session” consists of logically related interactions between a client and a
server, beginning with the initialization phase. To support
servers which want to establish stateful sessions:
- A server using the Streamable HTTP transport MAY assign a session ID at
initialization time, by including it in an
MCP-Session-Id header on the HTTP
response containing the InitializeResult.
- The session ID SHOULD be globally unique and cryptographically secure (e.g., a
securely generated UUID, a JWT, or a cryptographic hash).
- The session ID MUST only contain visible ASCII characters (ranging from 0x21 to
0x7E).
- The client MUST handle the session ID in a secure manner, see Session Hijacking mitigations for more details.
- If an
MCP-Session-Id is returned by the server during initialization, clients using
the Streamable HTTP transport MUST include it in the MCP-Session-Id header on
all of their subsequent HTTP requests.
- Servers that require a session ID SHOULD respond to requests without an
MCP-Session-Id header (other than initialization) with HTTP 400 Bad Request.
- The server MAY terminate the session at any time, after which it MUST respond
to requests containing that session ID with HTTP 404 Not Found.
- When a client receives HTTP 404 in response to a request containing an
MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest
without a session ID attached.
- Clients that no longer need a particular session (e.g., because the user is leaving
the client application) SHOULD send an HTTP DELETE to the MCP endpoint with the
MCP-Session-Id header, to explicitly terminate the session.
- The server MAY respond to this request with HTTP 405 Method Not Allowed,
indicating that the server does not allow clients to terminate sessions. If the
server returns HTTP 405, it MUST include an
Allow header listing the methods
it does support.
Sequence Diagram
If using HTTP, the client MUST include the MCP-Protocol-Version: <protocol-version> HTTP header on all subsequent requests to the MCP
server, allowing the MCP server to respond based on the MCP protocol version.
For example: MCP-Protocol-Version: 2025-06-18
The protocol version sent by the client SHOULD be the one negotiated during
initialization.
For backwards compatibility, if the server does not receive an MCP-Protocol-Version
header, and has no other way to identify the version - for example, by relying on the
protocol version negotiated during initialization - the server SHOULD assume protocol
version 2025-03-26.
If the server receives a request with an invalid or unsupported
MCP-Protocol-Version, it MUST respond with 400 Bad Request.
Standard MCP Request Headers
The Streamable HTTP transport requires clients to include the following headers on POST
requests, mirrored from the JSON-RPC request body:
| 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 |
These headers are REQUIRED for compliance.
Examples
tools/call request:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "Seattle, WA"
}
}
}
resources/read request:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: resources/read
Mcp-Name: file:///projects/myapp/config.json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///projects/myapp/config.json"
}
}
initialize request (no Mcp-Name needed):
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Method: initialize
{
"jsonrpc": "2.0",
"id": 4,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
Notification:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
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 rejecting a request due to header validation failure, servers MUST return HTTP
status 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. |
This error code is in the JSON-RPC implementation-defined server error range (-32000 to
-32099).
Example error response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32001,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}
Validation failure conditions include:
- A required standard header (
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.
MCP servers MAY designate specific tool parameters to be mirrored into HTTP headers
using an x-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
The x-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 contain only ASCII characters (excluding space and
:)
- MUST be case-insensitively unique among all
x-mcp-header values in the
inputSchema
- MUST only be applied to parameters with primitive types (number, string, boolean)
Clients MUST reject tool definitions where any 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.
Example tool definition:
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}
Resulting HTTP request:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_sql",
"arguments": {
"region": "us-west1",
"query": "SELECT * FROM users"
}
}
}
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-is
number: Convert to decimal string representation (e.g., 42, 3.14)
boolean: Convert to lowercase "true" or "false"
Per RFC 9110, HTTP
header field values must consist of visible ASCII characters (0x21-0x7E), space (0x20),
and horizontal tab (0x09). When a value cannot be safely represented as a plain ASCII
header value (e.g., it contains non-ASCII characters, control characters, or has
leading/trailing whitespace), clients MUST use Base64 encoding of the UTF-8
representation with the following format:
Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=
The prefix =?base64? and suffix ?= indicate that the value is Base64-encoded.
Servers and intermediaries that need to inspect these values MUST decode them
accordingly.
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=?= |
Client Behavior
When constructing a tools/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-Method header and, if applicable, Mcp-Name header to the request
- Inspect the tool’s
inputSchema for properties marked with x-mcp-header and 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
Intermediate servers that do not recognize an Mcp-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} headers that contain 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 |
SSE Stream Configuration
When initiating SSE streams, servers SHOULD include the X-Accel-Buffering: no
header in HTTP responses that return Content-Type: text/event-stream. This header
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.
Backwards Compatibility
Clients and servers can maintain backwards 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.
Clients wanting to support older servers should:
- 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 an
InitializeRequest to the server URL, with an Accept header as
defined above:
- If it succeeds, the client can assume this is a server supporting the new Streamable
HTTP transport.
- If it fails with the following HTTP status codes “400 Bad Request”, “404 Not
Found” or “405 Method Not Allowed”:
- Issue a GET request to the server URL, expecting that this will open an SSE stream
and return an
endpoint event as the first event.
- When the
endpoint event arrives, the client can assume this is a server running
the old HTTP+SSE transport, and should use that transport for all subsequent
communication.
Custom Transports
Clients and servers MAY implement additional custom transport mechanisms to suit
their specific needs. The protocol is transport-agnostic and can be implemented over any
communication channel that supports bidirectional message exchange.
Implementers who choose to support custom transports MUST ensure they preserve the
JSON-RPC message format and lifecycle requirements defined by MCP. Custom transports
SHOULD document their specific connection establishment and message exchange patterns
to aid interoperability.