Path: blob/main/src/vs/workbench/contrib/mcp/node/mcpStdioStateHandler.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ChildProcessWithoutNullStreams } from 'child_process';6import { TimeoutTimer } from '../../../../base/common/async.js';7import { IDisposable } from '../../../../base/common/lifecycle.js';8import { killTree } from '../../../../base/node/processes.js';9import { isWindows } from '../../../../base/common/platform.js';1011const enum McpProcessState {12Running,13StdinEnded,14KilledPolite,15KilledForceful,16}1718/**19* Manages graceful shutdown of MCP stdio connections following the MCP specification.20*21* Per spec, shutdown should:22* 1. Close the input stream to the child process23* 2. Wait for the server to exit, or send SIGTERM if it doesn't exit within 10 seconds24* 3. Send SIGKILL if the server doesn't exit within 10 seconds after SIGTERM25* 4. Allow forceful killing if called twice26*/27export class McpStdioStateHandler implements IDisposable {28private static readonly GRACE_TIME_MS = 10_000;2930private _procState = McpProcessState.Running;31private _nextTimeout?: IDisposable;3233public get stopped() {34return this._procState !== McpProcessState.Running;35}3637constructor(38private readonly _child: ChildProcessWithoutNullStreams,39private readonly _graceTimeMs: number = McpStdioStateHandler.GRACE_TIME_MS40) { }4142/**43* Initiates graceful shutdown. If called while shutdown is already in progress,44* forces immediate termination.45*/46public stop(): void {47if (this._procState === McpProcessState.Running) {48let graceTime = this._graceTimeMs;49try {50this._child.stdin.end();51} catch (error) {52// If stdin.end() fails, continue with termination sequence53// This can happen if the stream is already in an error state54graceTime = 1;55}56this._procState = McpProcessState.StdinEnded;57this._nextTimeout = new TimeoutTimer(() => this.killPolite(), graceTime);58} else {59this._nextTimeout?.dispose();60this.killForceful();61}62}6364private async killPolite() {65this._procState = McpProcessState.KilledPolite;66this._nextTimeout = new TimeoutTimer(() => this.killForceful(), this._graceTimeMs);6768if (this._child.pid) {69if (!isWindows) {70await killTree(this._child.pid, false).catch(() => {71this._child.kill('SIGTERM');72});73}74} else {75this._child.kill('SIGTERM');76}77}7879private async killForceful() {80this._procState = McpProcessState.KilledForceful;8182if (this._child.pid) {83await killTree(this._child.pid, true).catch(() => {84this._child.kill('SIGKILL');85});86} else {87this._child.kill();88}89}9091public write(message: string): void {92if (!this.stopped) {93this._child.stdin.write(message + '\n');94}95}9697public dispose() {98this._nextTimeout?.dispose();99}100}101102103