Skip to content

Commit d2bf224

Browse files
committed
Fix streamable HTTP restart after close
1 parent 5fc42e9 commit d2bf224

3 files changed

Lines changed: 19 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
---
4+
5+
Allow StreamableHTTPClientTransport to be restarted after close by clearing its aborted controller.

packages/client/src/client/streamableHttp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ export class StreamableHTTPClientTransport implements Transport {
345345

346346
const reconnect = (): void => {
347347
this._cancelReconnection = undefined;
348-
if (this._abortController?.signal.aborted) return;
348+
if (!this._abortController || this._abortController.signal.aborted) return;
349349
this._startOrAuthSse(options).catch(error => {
350350
this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
351351
try {
@@ -513,6 +513,7 @@ export class StreamableHTTPClientTransport implements Transport {
513513
} finally {
514514
this._cancelReconnection = undefined;
515515
this._abortController?.abort();
516+
this._abortController = undefined;
516517
this.onclose?.();
517518
}
518519
}

packages/client/test/client/streamableHttp.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ describe('StreamableHTTPClientTransport', () => {
149149
await reconnectTransport.close().catch(() => {});
150150
});
151151

152+
it('can be started again after close()', async () => {
153+
await transport.start();
154+
const firstAbortController = transport['_abortController'];
155+
156+
await transport.close();
157+
158+
expect(firstAbortController?.signal.aborted).toBe(true);
159+
expect(transport['_abortController']).toBeUndefined();
160+
await expect(transport.start()).resolves.toBeUndefined();
161+
expect(transport['_abortController']).toBeDefined();
162+
});
163+
152164
it('should terminate session with DELETE request', async () => {
153165
// First, simulate getting a session ID
154166
const message: JSONRPCMessage = {

0 commit comments

Comments
 (0)