fix: handle EPIPE errors in StdioServerTransport to prevent process crash (#1568)

Co-authored-by: User <user@example.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
不做了睡大觉 2026-03-26 04:15:35 +08:00 • committed by GitHub
commit f1ade75b67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -19,6 +19,7 @@ import { process } from '@modelcontextprotocol/server/_shims';
export class StdioServerTransport implements Transport {
private _readBuffer: ReadBuffer = new ReadBuffer();
private _started = false;
private _closed = false;
constructor(
private _stdin: Readable = process.stdin,
@ -37,6 +38,12 @@ export class StdioServerTransport implements Transport {
_onerror = (error: Error) => {
this.onerror?.(error);
};
_onstdouterror = (error: Error) => {
this.onerror?.(error);
this.close().catch(() => {
// Ignore errors during close — we're already in an error path
});
};
/**
* Starts listening for messages on `stdin`.
@ -51,6 +58,7 @@ export class StdioServerTransport implements Transport {
this._started = true;
this._stdin.on('data', this._ondata);
this._stdin.on('error', this._onerror);
this._stdout.on('error', this._onstdouterror);
}
private processReadBuffer() {
@ -69,9 +77,15 @@ export class StdioServerTransport implements Transport {
}
async close(): Promise<void> {
if (this._closed) {
return;
}
this._closed = true;
// Remove our event listeners first
this._stdin.off('data', this._ondata);
this._stdin.off('error', this._onerror);
this._stdout.off('error', this._onstdouterror);
// Check if we were the only data listener
const remainingDataListeners = this._stdin.listenerCount('data');
@ -87,12 +101,37 @@ export class StdioServerTransport implements Transport {
}
send(message: JSONRPCMessage): Promise<void> {
return new Promise(resolve => {
if (this._closed) {
return Promise.reject(new Error('StdioServerTransport is closed'));
}
return new Promise((resolve, reject) => {
const json = serializeMessage(message);
if (this._stdout.write(json)) {
let settled = false;
const onError = (error: Error) => {
if (settled) return;
settled = true;
this._stdout.off('error', onError);
this._stdout.off('drain', onDrain);
reject(error);
};
const onDrain = () => {
if (settled) return;
settled = true;
this._stdout.off('error', onError);
this._stdout.off('drain', onDrain);
resolve();
} else {
this._stdout.once('drain', resolve);
};
this._stdout.once('error', onError);
if (this._stdout.write(json)) {
if (settled) return;
settled = true;
this._stdout.off('error', onError);
resolve();
} else if (!settled) {
this._stdout.once('drain', onDrain);
}
});
}