parent
8d55531dab
commit
1b53a415ea
2 changed files with 191 additions and 10 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue