@@ -4,6 +4,37 @@ import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core';
44import { ReadBuffer , serializeMessage } from '@modelcontextprotocol/core' ;
55import { process } from '@modelcontextprotocol/server/_shims' ;
66
7+ /**
8+ * Options for configuring `StdioServerTransport`.
9+ */
10+ export interface StdioServerTransportOptions {
11+ /**
12+ * The readable stream to use for input. Defaults to `process.stdin`.
13+ */
14+ stdin ?: Readable ;
15+
16+ /**
17+ * The writable stream to use for output. Defaults to `process.stdout`.
18+ */
19+ stdout ?: Writable ;
20+
21+ /**
22+ * The PID of the client (host) process. When set, the transport periodically
23+ * checks if the host process is still alive and self-terminates if it is gone.
24+ *
25+ * This prevents orphaned server processes when the host crashes or is killed
26+ * without cleanly shutting down the server. Follows the same pattern used by
27+ * the Language Server Protocol in vscode-languageserver-node.
28+ */
29+ clientProcessId ?: number ;
30+
31+ /**
32+ * How often (in milliseconds) to check if the host process is alive.
33+ * Only used when `clientProcessId` is set. Defaults to 3000 (3 seconds).
34+ */
35+ watchdogIntervalMs ?: number ;
36+ }
37+
738/**
839 * Server transport for stdio: this communicates with an MCP client by reading from the current process' `stdin` and writing to `stdout`.
940 *
@@ -19,11 +50,29 @@ import { process } from '@modelcontextprotocol/server/_shims';
1950export class StdioServerTransport implements Transport {
2051 private _readBuffer : ReadBuffer = new ReadBuffer ( ) ;
2152 private _started = false ;
53+ private _clientProcessId ?: number ;
54+ private _watchdogInterval ?: ReturnType < typeof setInterval > ;
55+ private _watchdogIntervalMs : number ;
56+ private _stdin : Readable ;
57+ private _stdout : Writable ;
2258
23- constructor (
24- private _stdin : Readable = process . stdin ,
25- private _stdout : Writable = process . stdout
26- ) { }
59+ constructor ( options ?: StdioServerTransportOptions ) ;
60+ constructor ( stdin ?: Readable , stdout ?: Writable ) ;
61+ constructor ( stdinOrOptions ?: Readable | StdioServerTransportOptions , stdout ?: Writable ) {
62+ if ( stdinOrOptions && typeof stdinOrOptions === 'object' && ! ( 'read' in stdinOrOptions ) ) {
63+ // Options object form
64+ const options = stdinOrOptions as StdioServerTransportOptions ;
65+ this . _stdin = options . stdin ?? process . stdin ;
66+ this . _stdout = options . stdout ?? process . stdout ;
67+ this . _clientProcessId = options . clientProcessId ;
68+ this . _watchdogIntervalMs = options . watchdogIntervalMs ?? 3000 ;
69+ } else {
70+ // Legacy positional args form
71+ this . _stdin = ( stdinOrOptions as Readable ) ?? process . stdin ;
72+ this . _stdout = stdout ?? process . stdout ;
73+ this . _watchdogIntervalMs = 3000 ;
74+ }
75+ }
2776
2877 onclose ?: ( ) => void ;
2978 onerror ?: ( error : Error ) => void ;
@@ -51,6 +100,37 @@ export class StdioServerTransport implements Transport {
51100 this . _started = true ;
52101 this . _stdin . on ( 'data' , this . _ondata ) ;
53102 this . _stdin . on ( 'error' , this . _onerror ) ;
103+ this . _startHostWatchdog ( ) ;
104+ }
105+
106+ private _startHostWatchdog ( ) : void {
107+ if ( this . _clientProcessId === undefined || this . _watchdogInterval ) {
108+ return ;
109+ }
110+
111+ const pid = this . _clientProcessId ;
112+ this . _watchdogInterval = setInterval ( ( ) => {
113+ try {
114+ // Signal 0 does not kill the process; it checks if it exists.
115+ process . kill ( pid , 0 ) ;
116+ } catch {
117+ // Host process is gone. Self-terminate.
118+ this . _stopHostWatchdog ( ) ;
119+ void this . close ( ) ;
120+ }
121+ } , this . _watchdogIntervalMs ) ;
122+
123+ // Ensure the watchdog timer does not prevent the process from exiting.
124+ if ( typeof this . _watchdogInterval === 'object' && 'unref' in this . _watchdogInterval ) {
125+ this . _watchdogInterval . unref ( ) ;
126+ }
127+ }
128+
129+ private _stopHostWatchdog ( ) : void {
130+ if ( this . _watchdogInterval ) {
131+ clearInterval ( this . _watchdogInterval ) ;
132+ this . _watchdogInterval = undefined ;
133+ }
54134 }
55135
56136 private processReadBuffer ( ) {
@@ -69,6 +149,8 @@ export class StdioServerTransport implements Transport {
69149 }
70150
71151 async close ( ) : Promise < void > {
152+ this . _stopHostWatchdog ( ) ;
153+
72154 // Remove our event listeners first
73155 this . _stdin . off ( 'data' , this . _ondata ) ;
74156 this . _stdin . off ( 'error' , this . _onerror ) ;
0 commit comments