From 5f03e884bf820cdc410bdc5d914e6957edd96053 Mon Sep 17 00:00:00 2001 From: Harsh Mathur Date: Thu, 9 Jul 2026 15:55:40 +0000 Subject: [PATCH] fix(server): close stdio transport on stdin close --- packages/server/src/server/stdio.ts | 7 ++++++ packages/server/test/server/stdio.test.ts | 30 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/server/src/server/stdio.ts b/packages/server/src/server/stdio.ts index e8fda03257..069c108857 100644 --- a/packages/server/src/server/stdio.ts +++ b/packages/server/src/server/stdio.ts @@ -54,6 +54,9 @@ export class StdioServerTransport implements Transport { _onerror = (error: Error) => { this.onerror?.(error); }; + _onstdinclose = () => { + this.close().catch(() => {}); + }; _onstdouterror = (error: Error) => { this.onerror?.(error); this.close().catch(() => { @@ -74,6 +77,8 @@ export class StdioServerTransport implements Transport { this._started = true; this._stdin.on('data', this._ondata); this._stdin.on('error', this._onerror); + this._stdin.on('close', this._onstdinclose); + this._stdin.on('end', this._onstdinclose); this._stdout.on('error', this._onstdouterror); } @@ -101,6 +106,8 @@ export class StdioServerTransport implements Transport { // Remove our event listeners first this._stdin.off('data', this._ondata); this._stdin.off('error', this._onerror); + this._stdin.off('close', this._onstdinclose); + this._stdin.off('end', this._onstdinclose); this._stdout.off('error', this._onstdouterror); // Check if we were the only data listener diff --git a/packages/server/test/server/stdio.test.ts b/packages/server/test/server/stdio.test.ts index fe79e3679c..17ef4f4ba1 100644 --- a/packages/server/test/server/stdio.test.ts +++ b/packages/server/test/server/stdio.test.ts @@ -138,6 +138,36 @@ test('should not fire onclose twice when close() is called after stdout error', expect(closeCount).toBe(1); }); +test('should close when stdin closes', async () => { + const server = new StdioServerTransport(input, output); + + let closeCount = 0; + server.onclose = () => { + closeCount++; + }; + + await server.start(); + input.emit('close'); + await server.close(); + + expect(closeCount).toBe(1); +}); + +test('should close when stdin ends', async () => { + const server = new StdioServerTransport(input, output); + + let closeCount = 0; + server.onclose = () => { + closeCount++; + }; + + await server.start(); + input.emit('end'); + await server.close(); + + expect(closeCount).toBe(1); +}); + test('should reject send() when stdout errors before drain', async () => { let completeWrite: ((error?: Error | null) => void) | undefined; const slowOutput = new Writable({