Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/fix-sse-disconnect-noise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': patch
---

Suppress `onerror` when an SSE stream disconnects but reconnection will be scheduled. Previously `onerror` fired unconditionally on every stream disconnect, producing `"SSE stream disconnected: TypeError: terminated"` noise every few minutes on long-lived connections even though the transport recovered transparently.
6 changes: 3 additions & 3 deletions packages/client/src/client/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,6 @@ export class StreamableHTTPClientTransport implements Transport {
);
}
} catch (error) {
// Handle stream errors - likely a network disconnect
this.onerror?.(new Error(`SSE stream disconnected: ${error}`));

// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
// Reconnect if: already reconnectable (GET stream) OR received a priming event (POST stream with event ID)
// BUT don't reconnect if we already received a response - the request is complete
Expand All @@ -467,6 +464,9 @@ export class StreamableHTTPClientTransport implements Transport {
} catch (error) {
this.onerror?.(new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
}
} else {
// Stream disconnected and reconnection will not happen; surface the error
this.onerror?.(new Error(`SSE stream disconnected: ${error}`));
}
}
};
Expand Down
9 changes: 3 additions & 6 deletions packages/client/test/client/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,12 +832,9 @@ describe('StreamableHTTPClientTransport', () => {
await vi.advanceTimersByTimeAsync(20); // Trigger reconnection timeout

// ASSERT
expect(errorSpy).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('SSE stream disconnected: Error: Network failure')
})
);
// THE KEY ASSERTION: A second fetch call proves reconnection was attempted.
// onerror is NOT called: reconnection will handle the disconnect transparently
expect(errorSpy).not.toHaveBeenCalled();
// A second fetch call proves reconnection was attempted.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(fetchMock.mock.calls[0]![1]?.method).toBe('GET');
expect(fetchMock.mock.calls[1]![1]?.method).toBe('GET');
Expand Down
Loading