Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fa81940
Initial take on transport-agnostic resumable streams
connor4312 Jun 27, 2025
7700c47
update
connor4312 Jun 27, 2025
a02fa0e
Response => Result
jonathanhefner Jun 29, 2025
3f0e51e
Repurpose `stream/poll` for stream status
jonathanhefner Jun 29, 2025
0fd29c8
Add `stream/poll/all`
jonathanhefner Jun 29, 2025
dd71c19
Paginate `stream/poll/all`
jonathanhefner Jun 30, 2025
6daaa7f
Merge pull request #2 from jonathanhefner/connor4312/543-streams
jonathanhefner Jun 30, 2025
3971b3b
add docs
connor4312 Jun 30, 2025
f28eabb
rm basemetadata
connor4312 Jun 30, 2025
31877a3
add a client capability
connor4312 Jul 2, 2025
90df79a
generate:json
connor4312 Jul 2, 2025
f962e00
format
connor4312 Jul 2, 2025
3130fee
comments
connor4312 Jul 2, 2025
916a187
Fix typos and address feedback
jonathanhefner Jul 6, 2025
594449f
Merge pull request #3 from jonathanhefner/connor4312/543-streams/feed…
jonathanhefner Jul 7, 2025
cbb379d
Merge branch 'main' into connor4312/543-streams/feedback-2
jonathanhefner Jul 14, 2025
124ba59
Allow at-will disconnect
jonathanhefner Jul 14, 2025
3e4e66d
Remove `stream/poll/all` for now
jonathanhefner Jul 14, 2025
3896196
Add `resumeToken` and make `streamId` sharable
jonathanhefner Jul 14, 2025
521c9b2
Add JSDoc `@category` tags
jonathanhefner Jul 14, 2025
ef8b9ec
Tweak documentation
jonathanhefner Jul 14, 2025
b90eace
Update generated files
jonathanhefner Jul 14, 2025
98434f3
Format with Prettier
jonathanhefner Jul 14, 2025
81f8599
Merge pull request #4 from jonathanhefner/connor4312/543-streams/feed…
jonathanhefner Jul 14, 2025
6225ba2
Tweak documentation
jonathanhefner Jul 15, 2025
fa99a55
Merge pull request #5 from jonathanhefner/connor4312/543-streams/feed…
jonathanhefner Jul 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"specification/draft/basic/index",
"specification/draft/basic/lifecycle",
"specification/draft/basic/transports",
"specification/draft/basic/streams",
"specification/draft/basic/authorization",
"specification/draft/basic/security_best_practices",
{
Expand Down
1 change: 1 addition & 0 deletions docs/specification/draft/basic/lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Key capabilities include:
| Client | `roots` | Ability to provide filesystem [roots](/specification/draft/client/roots) |
| Client | `sampling` | Support for LLM [sampling](/specification/draft/client/sampling) requests |
| Client | `elicitation` | Support for server [elicitation](/specification/draft/client/elicitation) requests |
| Client | `streams` | Support for [resumable streams](/specification/draft/basic/streams) |
| Client | `experimental` | Describes support for non-standard experimental features |
| Server | `prompts` | Offers [prompt templates](/specification/draft/server/prompts) |
| Server | `resources` | Provides readable [resources](/specification/draft/server/resources) |
Expand Down
230 changes: 230 additions & 0 deletions docs/specification/draft/basic/streams.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
---
title: Streams
---

<div id="enable-section-numbers" />

<Info>**Protocol Revision**: draft</Info>

MCP defines a transport-agnostic mechanism for resumable streams that allow clients and servers to maintain state across disconnections. Using resumable streams:

- Clients and servers can disconnect and reconnect without losing progress.
- Servers can communicate expiration timeouts and reclaim resources thereafter.
- Clients can poll stream status after disconnect without having to fetch undelivered messages.
- All of the above works regardless of transport (HTTP, WebSocket, stdio, etc.).

## Lifecycle

A stream follows a defined lifecycle:

1. **Creation**: Server creates a stream and sends a [`notifications/stream/create`][] notification with a unique stream ID.
2. **Live**: Messages are sent as part of the stream.
3. **Completion**: Server sends a [`notifications/stream/end`][] notification when the stream is complete.

In the event of disconnection, the client may send a [`stream/poll`][] to check the stream's status, and a [`stream/resume`][] request to receive messages from the stream.

```mermaid
sequenceDiagram
participant Client
participant Server

Client->>+Server: Request (e.g., tools/call)
Note over Server: Server creates stream
Server-->>Client: notifications/stream/create<br>{ streamId: "123", resumeToken: "abc" }
loop
Server-->>Client: Messages (e.g., notifications/progress)
end
Server--x-Client: Disconnection occurs

Note over Client: Client polls stream status (optional)
Client->>+Server: stream/poll<br>{ streamId: "123", resumeToken: "abc" }
Server-->>-Client: StreamPollResult

Note over Client: Client decides to resume
Client->>+Server: stream/resume<br>{ streamId: "123", resumeToken: "abc" }
Server-->>Client: Undelivered messages
loop
Server-->>Client: Messages (e.g., notifications/progress)
end
Server-->>Client: CallToolResult

Note over Server: Server terminates stream
Server-->>-Client: notifications/stream/end<br>{ streamId: "123" }
```

[`notifications/stream/create`]: /specification/draft/schema#notifications%2Fstream%2Fcreate
[`notifications/stream/end`]: /specification/draft/schema#notifications%2Fstream%2Fend
[`stream/poll`]: /specification/draft/schema#stream%2Fpoll
[`stream/resume`]: /specification/draft/schema#stream%2Fresume

### Creation

When a client sends a request that might cause a long-running response, the server **MAY** create a stream by sending a [`notifications/stream/create`][] notification with a unique stream ID, a resume token, and resume interval parameters.

```json
{
"jsonrpc": "2.0",
"method": "notifications/stream/create",
"params": {
"stream": {
"streamId": "550e8400-e29b-41d4-a716-446655440000",
"resumeToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"requestId": "1",
"resumeInterval": {
"min": 5,
"max": 3600
}
}
}
}
```

- `streamId` and `resumeToken` are used to resume or poll the stream in the event of disconnection.
- `resumeInterval.min` specifies the minimum seconds a client **SHOULD** wait before resuming or polling the stream in order to prevent excessive reconnections.
- `resumeInterval.max` specifies the maximum seconds a client may wait before the server considers the stream abandoned.

### Disconnection and Resumption

Either the client or server **MAY** disconnect at any time. After a disconnection, the client can resume the stream by sending a [`stream/resume`][] request with the stream ID and resume token.

```json
{
"jsonrpc": "2.0",
"method": "stream/resume",
"params": {
"streamId": "550e8400-e29b-41d4-a716-446655440000",
"resumeToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```

When a client sends a `stream/resume` request, the server should:

1. Send all undelivered messages for the stream to the client.
2. If the stream has ended, send a `notifications/stream/end` notification after sending all other undelivered messages.
3. If the stream ID or resume token is invalid, respond with an error.

```mermaid
sequenceDiagram
participant Client
participant Server

Client->>+Server: Request (e.g., tools/call)
Server-->>Client: notifications/stream/create<br>{ streamId: "123", resumeToken: "abc" }
Server-->>Client: Initial messages
Server--x-Client: Disconnection occurs

Client->>+Server: stream/resume<br>{ streamId: "123", resumeToken: "abc" }
Server-->>Client: Undelivered messages
Server-->>-Client: notifications/stream/end<br>{ streamId: "123" }
```

<Note>

When using the [Streamable HTTP transport](/specification/draft/basic/transports#streamable-http) to stream messages, a server cannot definitively know whether a message has been delivered. Clients use the `Last-Event-ID` HTTP header upon reconnect to indicate the last delivered messages.

In order to provide message delivery guarantees, servers may use monotonic SSE event IDs, and maintain a buffer of recently sent messages (a two minute window aligns with common TCP idle timeout configurations). When a client resumes a stream, the `Last-Event-ID` header can be used to determine which messages in the buffer should be discarded before sending the remainder to the client.

</Note>

### Polling

Clients can check a stream's status without fetching all undelivered messages by sending a [`stream/poll`][] request with the stream ID and resume token.

```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "stream/poll",
"params": {
"streamId": "550e8400-e29b-41d4-a716-446655440000",
"resumeToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```

The response will indicate the status of the stream as well as whether there are pending messages.

```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"streamId": "550e8400-e29b-41d4-a716-446655440000",
"status": "live",
"pendingMessages": true,
"hasRequest": false,
"hasError": false
}
}
```

- `status`: The stream status (`"live"` or `"completed"`)
- `pendingMessages`: Whether there are pending messages
- `hasRequest`: Whether any pending messages include a server-to-client request
- `hasError`: Whether any pending messages include an error result

### Abandonment

If a client does not resume or poll a stream within the `resumeInterval.max` period, the server **MAY** consider the stream abandoned and reclaim resources. The next time the client attempts to resume or poll the stream, the server **MAY** respond with an error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The end is missing here

```mermaid
sequenceDiagram
participant Client
participant Server

Client->>+Server: Request (e.g., tools/call)
Server-->>Client: notifications/stream/create<br>{ resumeInterval: { max: 3600 } }
Server-->>Client: Initial messages
Server--x-Client: Disconnection occurs

Note over Client: Client waits > 3600 seconds

Client->>+Server: stream/resume
Server-->>-Client: Error (stream abandoned)
```

### Termination

When a stream completes, the server **MUST** send a [`notifications/stream/end`][] notification.

```json
{
"jsonrpc": "2.0",
"method": "notifications/stream/end",
"params": {
"streamId": "550e8400-e29b-41d4-a716-446655440000"
}
}
```

## Using Streams with Tool Calls

A common pattern is to use streams with tool calls that might take a long time to complete. When using streams with tool calls:

1. The server sends a `notifications/stream/create` notification immediately after receiving the tool call request.
2. The server can send progress updates and other messages as part of the stream.
3. When the tool call completes, the server sends the result.
4. The server ends the stream with a `notifications/stream/end` notification.

```mermaid
sequenceDiagram
participant Client
participant Server

Client->>+Server: tools/call
Server-->>Client: notifications/stream/create<br>{ streamId: "123", resumeToken: "abc" }

loop
Server-->>Client: notifications/progress
Server-->>Client: Other messages
end

Server-->>Client: CallToolResult
Server-->>-Client: notifications/stream/end<br>{ streamId: "123" }
```

## Security Considerations

- Resume tokens should be treated as sensitive information as they can be used to retrieve message history.
- Servers should validate that clients have appropriate permissions to resume a stream.
7 changes: 4 additions & 3 deletions docs/specification/draft/basic/transports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ MCP endpoint.
- The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending the
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_
for the received JSON-RPC _request_, unless the [session](#session-management)
expires.
- The server **SHOULD NOT** close the SSE stream before sending either the JSON-RPC
_response_ for the received JSON-RPC _request_ or a
[stream creation notification](/specification/draft/basic/streams.mdx#creation),
unless the [session](#session-management) expires.
- After the JSON-RPC _response_ has been sent, the server **SHOULD** close the SSE
stream.
- Disconnection **MAY** occur at any time (e.g., due to network conditions).
Expand Down
Loading