@@ -107,19 +107,24 @@ export class McpClient {
107107 > ( ) ;
108108 private stderrBuffer = "" ;
109109 private notificationHandler : McpNotificationHandler | null = null ;
110+ private disconnectHandler : ( ( reason : string ) => void ) | null = null ;
111+ private intentionallyDisconnected = false ;
110112
111113 constructor (
112114 private readonly serverName : string ,
113115 private readonly command : string ,
114116 private readonly args : string [ ] = [ ] ,
115117 private readonly env ?: Record < string , string > ,
116- onNotification ?: McpNotificationHandler
118+ onNotification ?: McpNotificationHandler ,
119+ onDisconnect ?: ( reason : string ) => void
117120 ) {
118121 this . notificationHandler = onNotification ?? null ;
122+ this . disconnectHandler = onDisconnect ?? null ;
119123 }
120124
121125 async connect ( timeoutMs : number ) : Promise < void > {
122126 return new Promise ( ( resolve , reject ) => {
127+ this . intentionallyDisconnected = false ;
123128 const childEnv = {
124129 ...process . env ,
125130 ...this . env ,
@@ -145,17 +150,35 @@ export class McpClient {
145150 } ) ;
146151 }
147152
153+ let resolved = false ;
154+ const safeReject = ( err : Error ) => {
155+ if ( ! resolved ) {
156+ resolved = true ;
157+ reject ( err ) ;
158+ }
159+ } ;
160+
148161 this . process . on ( "error" , ( err ) => {
149- reject ( this . withStderr ( `Failed to start MCP server "${ this . serverName } " (${ this . command } ): ${ err . message } ` ) ) ;
162+ safeReject (
163+ this . withStderr ( `Failed to start MCP server "${ this . serverName } " (${ this . command } ): ${ err . message } ` )
164+ ) ;
150165 } ) ;
151166
152167 this . process . on ( "close" , ( code ) => {
153- const error = this . withStderr ( `MCP server "${ this . serverName } " exited with code ${ code } ` ) ;
168+ const reason = `MCP server "${ this . serverName } " exited with code ${ code } ` ;
169+ const error = this . withStderr ( reason ) ;
154170 for ( const [ , pending ] of this . pendingRequests ) {
155171 clearTimeout ( pending . timer ) ;
156172 pending . reject ( error ) ;
157173 }
158174 this . pendingRequests . clear ( ) ;
175+ this . reader ?. close ( ) ;
176+ this . reader = null ;
177+ this . process = null ;
178+ if ( ! this . intentionallyDisconnected && this . disconnectHandler ) {
179+ this . disconnectHandler ( reason ) ;
180+ }
181+ safeReject ( error ) ;
159182 } ) ;
160183
161184 if ( this . process . stderr ) {
@@ -264,6 +287,7 @@ export class McpClient {
264287 }
265288
266289 disconnect ( ) : void {
290+ this . intentionallyDisconnected = true ;
267291 if ( this . reader ) {
268292 this . reader . close ( ) ;
269293 this . reader = null ;
@@ -278,6 +302,10 @@ export class McpClient {
278302 }
279303 }
280304
305+ isConnected ( ) : boolean {
306+ return this . process !== null && this . process . exitCode === null ;
307+ }
308+
281309 private sendRequest ( method : string , params : Record < string , unknown > , timeoutMs = 30_000 ) : Promise < unknown > {
282310 return new Promise ( ( resolve , reject ) => {
283311 const id = this . nextId ++ ;
0 commit comments