Implement SEP-1699: Support SSE Polling via Server-Side Disconnect #604
+396
−22
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #529
Motivation and Context
This PR implements modelcontextprotocol/modelcontextprotocol#1699 (Server-Initiated SSE Stream Disconnection), enabling MCP servers to disconnect SSE streams at will while allowing clients to reconnect via
Last-Event-ID.This implementation follows modelcontextprotocol/typescript-sdk#1061 as a reference.
ServerSseMessagenow includes aretry: Option<Duration>field following the SSE specification'sretry:field for client reconnection timing. Themessagefield changed fromArc<ServerJsonRpcMessage>toOption<Arc<ServerJsonRpcMessage>>to support priming events, which carry an event ID and retry interval but no message payload.LocalSessionHandlegains two methods for server-initiated disconnection:close_sse_stream()closes request-specific (POST) streams andclose_standalone_sse_stream()closes standalone (GET) streams. Both methods optionally send a priming event before closing to inform clients of the recommended reconnection delay.StreamableHttpServerConfignow automatically enables priming events whenstateful_modeis true (the default). The default retry interval is 3 seconds, matching the TypeScript SDK behavior.How Has This Been Tested?
Added new integration tests to verify the priming behavior.
Breaking Changes
The
messagefield inServerSseMessagechanged fromArc<ServerJsonRpcMessage>toOption<Arc<ServerJsonRpcMessage>>to support priming events, which have no message payload. Existing code constructingServerSseMessagedirectly will need to wrap the message inSome().I considered several approaches to avoid the breaking change to
ServerSseMessage. One option was to add anis_priming: boolflag while keepingmessageasArc<ServerJsonRpcMessage>:This would check the flag in
sse_stream_response()to emit empty data for priming events. However, priming events would still carry a meaninglessmessagefield, which is semantically incorrect.Another option was to convert
ServerSseMessagefrom a struct to an enum:This would be type-safe but should be a bigger breaking change (struct to enum), and arguably more disruptive than the current approach.
I chose
Option<Arc<...>>because it accurately represents that priming events have no message, andServerSseMessageis primarily used internally rather than by external users.I'm open to feedback on whether a different approach would be preferred.
Types of changes
Checklist
Additional context
Client-side reconnection logic was already implemented in
client_side_sse.rs.