Skip to content
Merged
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
feat: implement closeStandaloneSSEStream
Implement the ability to close standalone GET SSE streams, enabling
polling behavior for server-initiated notifications (SEP-1699).

Changes:
- closeStandaloneSSEStream() now closes the GET stream by ending
  the response and removing from stream mapping
- Events are now stored even when standalone stream is disconnected,
  allowing replay on client reconnect with Last-Event-ID

This complements the existing closeSSEStream() for POST streams.
  • Loading branch information
felixweinberger committed Dec 1, 2025
commit de41e4703c937753de5299b11255656efae59bc4
18 changes: 12 additions & 6 deletions src/server/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,11 @@ export class StreamableHTTPServerTransport implements Transport {
* Use this to implement polling behavior for server-initiated notifications.
*/
closeStandaloneSSEStream(): void {
// TODO: implement - currently does nothing, stream won't close
const stream = this._streamMapping.get(this._standaloneSseStreamId);
if (stream) {
stream.end();
this._streamMapping.delete(this._standaloneSseStreamId);
}
}

async send(message: JSONRPCMessage, options?: { relatedRequestId?: RequestId }): Promise<void> {
Expand All @@ -841,19 +845,21 @@ export class StreamableHTTPServerTransport implements Transport {
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
throw new Error('Cannot send a response on a standalone SSE stream unless resuming a previous client request');
}
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
if (standaloneSse === undefined) {
// The spec says the server MAY send messages on the stream, so it's ok to discard if no stream
return;
}

// Generate and store event ID if event store is provided
// Store even if stream is disconnected so events can be replayed on reconnect
let eventId: string | undefined;
if (this._eventStore) {
// Stores the event and gets the generated event ID
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
}

const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
if (standaloneSse === undefined) {
// Stream is disconnected - event is stored for replay, nothing more to do
return;
}

// Send the message to the standalone SSE stream
this.writeSSEEvent(standaloneSse, message, eventId);
return;
Expand Down
Loading