Skip to content

[RFC] Explicitly require JSON-RPC batch support #228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 26, 2025
12 changes: 0 additions & 12 deletions docs/specification/draft/architecture/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,6 @@ implementation:
- Protocol designed for future extensibility
- Backwards compatibility is maintained

## Message Types

MCP defines three core message types based on
[JSON-RPC 2.0](https://www.jsonrpc.org/specification):

- **Requests**: Bidirectional messages with method and parameters expecting a response
- **Responses**: Successful results or errors matching specific request IDs
- **Notifications**: One-way messages requiring no response

Each message type follows the JSON-RPC 2.0 specification for structure and delivery
semantics.

## Capability Negotiation

The Model Context Protocol uses a capability-based negotiation system where clients and
Expand Down
100 changes: 75 additions & 25 deletions docs/specification/draft/basic/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,6 @@ weight: 2

{{< callout type="info" >}} **Protocol Revision**: draft {{< /callout >}}

All messages between MCP clients and servers **MUST** follow the
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification. The protocol defines
three fundamental types of messages:

| Type | Description | Requirements |
| --------------- | -------------------------------------- | -------------------------------------- |
| `Requests` | Messages sent to initiate an operation | Must include unique ID and method name |
| `Responses` | Messages sent in reply to requests | Must include same ID as request |
| `Notifications` | One-way messages with no reply | Must not include an ID |

**Responses** are further sub-categorized as either **successful results** or **errors**.
Results can follow any JSON object structure, while errors must include an error code and
message at minimum.

## Protocol Layers

The Model Context Protocol consists of several key components that work together:

- **Base Protocol**: Core JSON-RPC message types
Expand All @@ -40,16 +24,82 @@ These protocol layers establish clear separation of concerns while enabling rich
interactions between clients and servers. The modular design allows implementations to
support exactly the features they need.

See the following pages for more details on the different components:
## Messages

{{< cards >}}
{{< card link="/specification/draft/basic/lifecycle" title="Lifecycle" icon="refresh" >}}
{{< card link="/specification/draft/server/resources" title="Resources" icon="document" >}}
{{< card link="/specification/draft/server/prompts" title="Prompts" icon="chat-alt-2" >}}
{{< card link="/specification/draft/server/tools" title="Tools" icon="adjustments" >}}
{{< card link="/specification/draft/server/utilities/logging" title="Logging" icon="annotation" >}}
{{< card link="/specification/draft/client/sampling" title="Sampling" icon="code" >}}
{{< /cards >}}
All messages between MCP clients and servers **MUST** follow the
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification. The protocol defines
these types of messages:

### Requests

Requests are sent from the client to the server or vice versa, to initiate an operation.

```typescript
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: {
[key: string]: unknown;
};
}
```

- Requests **MUST** include a string or integer ID.
- Unlike base JSON-RPC, the ID **MUST NOT** be `null`.
- The request ID **MUST NOT** have been previously used by the requestor within the same
session.

### Responses

Responses are sent in reply to requests, containing the result or error of the operation.

```typescript
{
jsonrpc: "2.0";
id: string | number;
result?: {
[key: string]: unknown;
}
error?: {
code: number;
message: string;
data?: unknown;
}
}
```

- Responses **MUST** include the same ID as the request they correspond to.
- **Responses** are further sub-categorized as either **successful results** or
**errors**. Either a `result` or an `error` **MUST** be set. A response **MUST NOT**
set both.
- Results **MAY** follow any JSON object structure, while errors **MUST** include an
error code and message at minimum.
- Error codes **MUST** be integers.

### Notifications

Notifications are sent from the client to the server or vice versa, as a one-way message.
The receiver **MUST NOT** send a response.

```typescript
{
jsonrpc: "2.0";
method: string;
params?: {
[key: string]: unknown;
};
}
```

- Notifications **MUST NOT** include an ID.

### Batching

JSON-RPC also defines a means to
[batch multiple requests and notifications](https://www.jsonrpc.org/specification#batch),
by sending them in an array. MCP implementations **MAY** support sending JSON-RPC
batches, but **MUST** support receiving JSON-RPC batches.

## Auth

Expand Down
6 changes: 6 additions & 0 deletions docs/specification/draft/basic/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ The client **MUST** initiate this phase by sending an `initialize` request conta
}
```

The initialize request **MUST NOT** be part of a JSON-RPC
[batch](https://www.jsonrpc.org/specification#batch), as other requests and notifications
are not possible until initialization has completed. This also permits backwards
compatibility with prior protocol versions that do not explicitly support JSON-RPC
batches.

The server **MUST** respond with its own capabilities and information:

```json
Expand Down
71 changes: 0 additions & 71 deletions docs/specification/draft/basic/messages.md

This file was deleted.

128 changes: 71 additions & 57 deletions docs/specification/draft/basic/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ It is also possible for clients and servers to implement
In the **stdio** transport:

- The client launches the MCP server as a subprocess.
- The server receives JSON-RPC messages on its standard input (`stdin`) and writes
responses to its standard output (`stdout`).
- The server reads JSON-RPC messages from its standard input (`stdin`) and sends messages
to its standard output (`stdout`).
- Messages may be JSON-RPC requests, notifications, responses—or a JSON-RPC
[batch](https://www.jsonrpc.org/specification#batch) containing one or more requests
and/or notifications.
- Messages are delimited by newlines, and **MUST NOT** contain embedded newlines.
- The server **MAY** write UTF-8 strings to its standard error (`stderr`) for logging
purposes. Clients **MAY** capture, forward, or ignore this logging.
Expand Down Expand Up @@ -66,61 +69,72 @@ The server **MUST** provide a single HTTP endpoint path (hereafter referred to a
**MCP endpoint**) that supports both POST and GET methods. For example, this could be a
URL like `https://example.com/mcp`.

### Message Exchange

1. Every JSON-RPC message sent from the client **MUST** be a new HTTP POST request to the
MCP endpoint.

2. When the client sends a JSON-RPC _request_ to the MCP endpoint via POST:

- The client **MUST** include an `Accept` header, listing both `application/json` and
`text/event-stream` as supported content types.
- The server **MUST** either return `Content-Type: text/event-stream`, to initiate an
SSE stream, or `Content-Type: application/json`, to return a single JSON-RPC
_response_. The client **MUST** support both these cases.
- If the server initiates an SSE stream:
- The SSE stream **SHOULD** eventually include a JSON-RPC _response_ message.
- The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending a
JSON-RPC _response_. These messages **SHOULD** relate to the originating client
_request_.
- The server **SHOULD NOT** close the SSE stream before sending the JSON-RPC
_response_, unless the [session](#session-management) expires.
- After the JSON-RPC _response_ has been sent, the server **MAY** close the SSE
stream at any time.
- 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](#resumability-and-redelivery).

3. When the client sends a JSON-RPC _notification_ or _response_ to the MCP endpoint via
POST:

- If the server accepts the message, it **MUST** return HTTP status code 202 Accepted
with no body.
- If the server cannot accept the message, 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`.

4. The client **MAY** also 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 a JSON-RPC _request_.
- 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.
- If the server initiates an SSE stream:
- The server **MAY** send JSON-RPC _requests_ and _notifications_ on the stream.
These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC
_request_ from the client.
- The server **MUST NOT** send a JSON-RPC _response_ on the stream **unless**
[resuming](#resumability-and-redelivery) a stream associated with a previous
client request.
- The server **MAY** close the SSE stream at any time.
- The client **MAY** close the SSE stream at any time.
### Sending Messages to the Server

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 to the MCP endpoint.
2. The client **MUST** include an `Accept` header, listing both `application/json` and
`text/event-stream` as supported content types.
3. The body of the POST request **MUST** be one of the following:
- A single JSON-RPC _request_, _notification_, or _response_
- An array [batching](https://www.jsonrpc.org/specification#batch) one or more
_requests and/or notifications_
- An array [batching](https://www.jsonrpc.org/specification#batch) one or more
_responses_
Comment on lines +82 to +85

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies mutual exclusion between requests/notifications and responses. Is that the intent?

It seems interesting to support the batching of all of them in a single batch. The structure of JSON-RPC supports this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is indeed the intent, based on how JSON-RPC defines batching in https://www.jsonrpc.org/specification#batch.

I don't think plain JSON-RPC permits mixing the two, although it's not really a bidirectional spec, so hard to say. My main concern here would be remaining compatible with any off-the-shelf JSON-RPC libraries that one might happen to grab.

4. If the input consists solely of (any number of) JSON-RPC _responses_ or
_notifications_:
- 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`.
5. If the input contains any number of JSON-RPC _requests_, 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.
6. If the server initiates an SSE stream:
- The SSE stream **SHOULD** eventually include one JSON-RPC _response_ per each
JSON-RPC _request_ sent in the POST body. These _responses_ **MAY** be
[batched](https://www.jsonrpc.org/specification#batch).
- The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending a
JSON-RPC _response_. These messages **SHOULD** relate to the originating client
Copy link

@daviddenton daviddenton Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a wrinkle/complexity in SDK and client implementations regarding to how this relation to an original request is transmitted inside the message - especially if the messages are replayed as the response to a stream GET request. For progress to requests we have progress tokens whereas there is no such facility for the Sampling. Maybe we could specify using a Progress Token inside of the meta of the SamplingRequest in this case? This would be consistent at least with progress.

As an example - consider the possibility that Claude Desktop gains the ability to do Sampling/Progress - since it communicates purely through a serialised stream of StdIO - there is no way to relate a Sampling Request to an outgoing request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Relate to" is specified quite loosely, on purpose. There doesn't need to be an identifier that links the two, this is basically just saying that servers should not abuse the per-request SSE stream to send unrelated requests.

_request_. These _requests_ and _notifications_ **MAY** be
[batched](https://www.jsonrpc.org/specification#batch).
- The server **SHOULD NOT** close the SSE stream before sending a JSON-RPC _response_
per each received JSON-RPC _request_, unless the [session](#session-management)
expires.
- After all JSON-RPC _responses_ have been sent, the server **SHOULD** close 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](#resumability-and-redelivery).

### Listening for Messages from the Server

1. 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.
2. The client **MUST** include an `Accept` header, listing `text/event-stream` as a
supported content type.
3. 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.
4. If the server initiates an SSE stream:
- The server **MAY** send JSON-RPC _requests_ and _notifications_ on the stream. These
_requests_ and _notifications_ **MAY** be
[batched](https://www.jsonrpc.org/specification#batch).
- These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC
_request_ from the client.
- The server **MUST NOT** send a JSON-RPC _response_ on the stream **unless**
[resuming](#resumability-and-redelivery) a stream associated with a previous client
request.
- The server **MAY** close the SSE stream at any time.
- The client **MAY** close the SSE stream at any time.

### Multiple Connections

Expand Down
Loading