Port of #2239 (v1.x) to v2 (#2275)

This commit is contained in:
Konstantin Konstantinov 2026-06-10 20:14:36 +03:00 • committed by GitHub
commit 1b53a415ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 191 additions and 10 deletions

View file

@ -17,14 +17,25 @@ import { process } from '@modelcontextprotocol/server/_shims';
* ```
*/
export class StdioServerTransport implements Transport {
private _readBuffer: ReadBuffer = new ReadBuffer();
private _readBuffer: ReadBuffer;
private _started = false;
private _closed = false;
constructor(
private _stdin: Readable = process.stdin,
private _stdout: Writable = process.stdout
) {}
private _stdout: Writable = process.stdout,
options?: {
/**
* Maximum size of the read buffer in bytes. If a single message exceeds
* this size the transport will emit an error and close.
*
* Defaults to 10 MB.
*/
maxBufferSize?: number;
}
) {
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
}
onclose?: () => void;
onerror?: (error: Error) => void;
@ -32,8 +43,13 @@ export class StdioServerTransport implements Transport {
// Arrow functions to bind `this` properly, while maintaining function identity.
_ondata = (chunk: Buffer) => {
this._readBuffer.append(chunk);
this.processReadBuffer();
try {
this._readBuffer.append(chunk);
this.processReadBuffer();
} catch (error) {
this.onerror?.(error as Error);
this.close().catch(() => {});
}
};
_onerror = (error: Error) => {
this.onerror?.(error);

View file

@ -179,3 +179,51 @@ test('should fire onerror before onclose on stdout error', async () => {
expect(events).toEqual(['error', 'close']);
});
test('should respect custom maxBufferSize option', async () => {
const server = new StdioServerTransport(input, output, { maxBufferSize: 100 });
let receivedError: Error | undefined;
server.onerror = err => {
receivedError = err;
};
let closeCount = 0;
server.onclose = () => {
closeCount++;
};
await server.start();
// Push 101 bytes without a newline — exceeds the 100-byte limit
input.push(Buffer.alloc(101, 0x41));
await new Promise(resolve => setTimeout(resolve, 10));
expect(receivedError?.message).toMatch(/ReadBuffer exceeded maximum size/);
expect(closeCount).toBe(1);
});
test('should fire onerror and close when ReadBuffer overflows', async () => {
const server = new StdioServerTransport(input, output);
let receivedError: Error | undefined;
server.onerror = err => {
receivedError = err;
};
let closeCount = 0;
server.onclose = () => {
closeCount++;
};
await server.start();
// Push data exceeding the default 10 MB limit without a newline
const chunk = Buffer.alloc(11 * 1024 * 1024, 0x41);
input.push(chunk);
// Allow the close() promise to settle
await new Promise(resolve => setTimeout(resolve, 10));
expect(receivedError?.message).toMatch(/ReadBuffer exceeded maximum size/);
expect(closeCount).toBe(1);
});