MCPcopy
hub / github.com/colbymchenry/codegraph / StdioTransport

Class StdioTransport

src/mcp/transport.ts:264–326  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

262 * so the daemon outlives its launcher.
263 */
264export class StdioTransport extends LineBasedJsonRpcTransport {
265 private rl: readline.Interface | null = null;
266 private opts: Required<StdioTransportOptions>;
267
268 constructor(opts: StdioTransportOptions = {}) {
269 super();
270 this.opts = {
271 exitOnClose: opts.exitOnClose ?? true,
272 onClose: opts.onClose ?? (() => { /* no-op */ }),
273 };
274 }
275
276 start(handler: MessageHandler): void {
277 this.messageHandler = handler;
278
279 this.rl = readline.createInterface({
280 input: process.stdin,
281 output: process.stdout,
282 terminal: false,
283 });
284
285 this.rl.on('line', async (line) => {
286 await this.handleLine(line);
287 });
288
289 // readline 'close' fires on a clean stdin EOF. But a socket-backed stdin
290 // (the VS Code stdio shape) can fail with an 'error' (ECONNRESET/hangup)
291 // that readline doesn't surface as 'close' — unhandled, it escalated to
292 // the global uncaughtException handler (which keeps running), orphaning
293 // the server and, on Linux, busy-spinning a POLLHUP fd at 100% CPU. Treat
294 // 'error' as terminal too, and destroy stdin so the fd leaves epoll (#799).
295 let closed = false;
296 const onStreamEnd = (): void => {
297 if (closed) return;
298 closed = true;
299 try { process.stdin.destroy(); } catch { /* already gone */ }
300 this.opts.onClose();
301 if (this.opts.exitOnClose) {
302 process.exit(0);
303 }
304 };
305 this.rl.on('close', onStreamEnd);
306 process.stdin.on('error', onStreamEnd);
307 }
308
309 stop(): void {
310 if (this.stopped) return;
311 this.stopped = true;
312 this.rejectPending('Transport stopped');
313 if (this.rl) {
314 this.rl.close();
315 this.rl = null;
316 }
317 }
318
319 protected write(line: string): void {
320 process.stdout.write(line + '\n');
321 }

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected