@@ -19,6 +19,7 @@ import { process } from '@modelcontextprotocol/server/_shims';
1919export class StdioServerTransport implements Transport {
2020 private _readBuffer : ReadBuffer = new ReadBuffer ( ) ;
2121 private _started = false ;
22+ private _closed = false ;
2223
2324 constructor (
2425 private _stdin : Readable = process . stdin ,
@@ -38,11 +39,14 @@ export class StdioServerTransport implements Transport {
3839 this . onerror ?.( error ) ;
3940 } ;
4041 _onstdouterror = ( error : Error ) => {
41- // Handle stdout errors (e.g., EPIPE when client disconnects)
42- // Trigger close to clean up gracefully
43- this . close ( ) . catch ( ( ) => {
44- // Ignore errors during close
45- } ) ;
42+ // Handle stdout broken pipe when client disconnects.
43+ if ( ( error as NodeJS . ErrnoException ) . code === 'EPIPE' ) {
44+ this . close ( ) . catch ( ( ) => {
45+ // Ignore errors during close
46+ } ) ;
47+ return ;
48+ }
49+
4650 this . onerror ?.( error ) ;
4751 } ;
4852
@@ -78,6 +82,11 @@ export class StdioServerTransport implements Transport {
7882 }
7983
8084 async close ( ) : Promise < void > {
85+ if ( this . _closed ) {
86+ return ;
87+ }
88+ this . _closed = true ;
89+
8190 // Remove our event listeners first
8291 this . _stdin . off ( 'data' , this . _ondata ) ;
8392 this . _stdin . off ( 'error' , this . _onerror ) ;
@@ -99,23 +108,52 @@ export class StdioServerTransport implements Transport {
99108 send ( message : JSONRPCMessage ) : Promise < void > {
100109 return new Promise ( ( resolve , reject ) => {
101110 const json = serializeMessage ( message ) ;
102-
103- // Handle write errors (e.g., EPIPE when client disconnects)
104- const onError = ( error : Error ) => {
111+ let settled = false ;
112+
113+ const cleanup = ( ) => {
105114 this . _stdout . off ( 'error' , onError ) ;
115+ this . _stdout . off ( 'drain' , onDrain ) ;
116+ } ;
117+
118+ const onDrain = ( ) => {
119+ if ( settled ) {
120+ return ;
121+ }
122+ settled = true ;
123+ cleanup ( ) ;
124+ resolve ( ) ;
125+ } ;
126+
127+ const onError = ( error : Error ) => {
128+ if ( settled ) {
129+ return ;
130+ }
131+ settled = true ;
132+ cleanup ( ) ;
133+
134+ if ( ( error as NodeJS . ErrnoException ) . code === 'EPIPE' ) {
135+ this . close ( ) . catch ( ( ) => {
136+ // Ignore errors during close
137+ } ) ;
138+ resolve ( ) ;
139+ return ;
140+ }
141+
106142 reject ( error ) ;
107143 } ;
108-
144+
109145 this . _stdout . once ( 'error' , onError ) ;
110-
111- if ( this . _stdout . write ( json ) ) {
112- this . _stdout . off ( 'error' , onError ) ;
113- resolve ( ) ;
114- } else {
115- this . _stdout . once ( 'drain' , ( ) => {
116- this . _stdout . off ( 'error' , onError ) ;
146+
147+ try {
148+ if ( this . _stdout . write ( json ) ) {
149+ settled = true ;
150+ cleanup ( ) ;
117151 resolve ( ) ;
118- } ) ;
152+ } else {
153+ this . _stdout . once ( 'drain' , onDrain ) ;
154+ }
155+ } catch ( error ) {
156+ onError ( error as Error ) ;
119157 }
120158 } ) ;
121159 }
0 commit comments