{"version":3,"file":"stdio.cjs","names":["process","env: Record<string, string>","ReadBuffer","PassThrough","SdkError","SdkErrorCode","serializeMessage"],"sources":["../src/client/stdio.ts"],"sourcesContent":["import type { ChildProcess, IOType } from 'node:child_process';\nimport process from 'node:process';\nimport type { Stream } from 'node:stream';\nimport { PassThrough } from 'node:stream';\n\nimport type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal';\nimport { ReadBuffer, SdkError, SdkErrorCode, serializeMessage } from '@modelcontextprotocol/core-internal';\nimport spawn from 'cross-spawn';\n\nexport type StdioServerParameters = {\n    /**\n     * The executable to run to start the server.\n     */\n    command: string;\n\n    /**\n     * Command line arguments to pass to the executable.\n     */\n    args?: string[];\n\n    /**\n     * The environment to use when spawning the process.\n     *\n     * If not specified, the result of {@linkcode getDefaultEnvironment} will be used.\n     */\n    env?: Record<string, string>;\n\n    /**\n     * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.\n     *\n     * The default is `\"inherit\"`, meaning messages to stderr will be printed to the parent process's stderr.\n     */\n    stderr?: IOType | Stream | number;\n\n    /**\n     * The working directory to use when spawning the process.\n     *\n     * If not specified, the current working directory will be inherited.\n     */\n    cwd?: string;\n\n    /**\n     * Maximum size of the read buffer in bytes. If a single message exceeds\n     * this size the transport will emit an error and close.\n     *\n     * Defaults to 10 MB.\n     */\n    maxBufferSize?: number;\n};\n\n/**\n * Environment variables to inherit by default, if an environment is not explicitly given.\n */\nexport const DEFAULT_INHERITED_ENV_VARS =\n    process.platform === 'win32'\n        ? [\n              'APPDATA',\n              'HOMEDRIVE',\n              'HOMEPATH',\n              'LOCALAPPDATA',\n              'PATH',\n              'PROCESSOR_ARCHITECTURE',\n              'SYSTEMDRIVE',\n              'SYSTEMROOT',\n              'TEMP',\n              'USERNAME',\n              'USERPROFILE',\n              'PROGRAMFILES'\n          ]\n        : /* list inspired by the default env inheritance of sudo */\n          ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];\n\n/**\n * Returns a default environment object including only environment variables deemed safe to inherit.\n */\nexport function getDefaultEnvironment(): Record<string, string> {\n    const env: Record<string, string> = {};\n\n    for (const key of DEFAULT_INHERITED_ENV_VARS) {\n        const value = process.env[key];\n        if (value === undefined) {\n            continue;\n        }\n\n        if (value.startsWith('()')) {\n            // Skip functions, which are a security risk.\n            continue;\n        }\n\n        env[key] = value;\n    }\n\n    return env;\n}\n\n/**\n * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.\n *\n * This transport is only available in Node.js environments.\n */\nexport class StdioClientTransport implements Transport {\n    private _process?: ChildProcess;\n    private _readBuffer: ReadBuffer;\n    private _serverParams: StdioServerParameters;\n    private _stderrStream: PassThrough | null = null;\n\n    onclose?: () => void;\n    onerror?: (error: Error) => void;\n    onmessage?: (message: JSONRPCMessage) => void;\n\n    constructor(server: StdioServerParameters) {\n        this._serverParams = server;\n        this._readBuffer = new ReadBuffer({ maxBufferSize: server.maxBufferSize });\n        if (server.stderr === 'pipe' || server.stderr === 'overlapped') {\n            this._stderrStream = new PassThrough();\n        }\n    }\n\n    /**\n     * Starts the server process and prepares to communicate with it.\n     */\n    async start(): Promise<void> {\n        if (this._process) {\n            throw new Error(\n                'StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.'\n            );\n        }\n\n        return new Promise((resolve, reject) => {\n            this._process = spawn(this._serverParams.command, this._serverParams.args ?? [], {\n                // merge default env with server env because mcp server needs some env vars\n                env: {\n                    ...getDefaultEnvironment(),\n                    ...this._serverParams.env\n                },\n                stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'],\n                shell: false,\n                windowsHide: process.platform === 'win32',\n                cwd: this._serverParams.cwd\n            });\n\n            this._process.on('error', error => {\n                reject(error);\n                this.onerror?.(error);\n            });\n\n            this._process.on('spawn', () => {\n                resolve();\n            });\n\n            this._process.on('close', _code => {\n                this._process = undefined;\n                this.onclose?.();\n            });\n\n            this._process.stdin?.on('error', error => {\n                this.onerror?.(error);\n            });\n\n            this._process.stdout?.on('data', chunk => {\n                try {\n                    this._readBuffer.append(chunk);\n                    this.processReadBuffer();\n                } catch (error) {\n                    this.onerror?.(error as Error);\n                    this.close().catch(() => {});\n                }\n            });\n\n            this._process.stdout?.on('error', error => {\n                this.onerror?.(error);\n            });\n\n            if (this._stderrStream && this._process.stderr) {\n                this._process.stderr.pipe(this._stderrStream);\n            }\n        });\n    }\n\n    /**\n     * The `stderr` stream of the child process, if {@linkcode StdioServerParameters.stderr} was set to `\"pipe\"` or `\"overlapped\"`.\n     *\n     * If `stderr` piping was requested, a `PassThrough` stream is returned _immediately_, allowing callers to\n     * attach listeners before the `start` method is invoked. This prevents loss of any early\n     * error output emitted by the child process.\n     */\n    get stderr(): Stream | null {\n        if (this._stderrStream) {\n            return this._stderrStream;\n        }\n\n        return this._process?.stderr ?? null;\n    }\n\n    /**\n     * The child process pid spawned by this transport.\n     *\n     * This is only available after the transport has been started.\n     */\n    get pid(): number | null {\n        return this._process?.pid ?? null;\n    }\n\n    private processReadBuffer() {\n        while (true) {\n            try {\n                const message = this._readBuffer.readMessage();\n                if (message === null) {\n                    break;\n                }\n\n                this.onmessage?.(message);\n            } catch (error) {\n                this.onerror?.(error as Error);\n            }\n        }\n    }\n\n    /**\n     * Reap a disposable probe sibling (see the version-negotiation sibling\n     * flow): signal-first teardown awaiting process `exit` — never the `close`\n     * event, so a helper process holding the child's stdio pipes can never\n     * block disposal. Not part of the public transport lifecycle.\n     *\n     * @internal\n     */\n    private async _dispose(): Promise<void> {\n        const proc = this._process;\n        this._process = undefined;\n        if (proc && proc.exitCode === null && proc.signalCode === null) {\n            const exited = new Promise<void>(resolve => proc.once('exit', () => resolve()));\n            try {\n                proc.stdin?.end();\n            } catch {\n                // ignore\n            }\n            try {\n                proc.kill('SIGTERM');\n            } catch {\n                // ignore\n            }\n            await Promise.race([exited, new Promise(resolve => setTimeout(resolve, 1000).unref())]);\n            if (proc.exitCode === null && proc.signalCode === null) {\n                try {\n                    proc.kill('SIGKILL');\n                } catch {\n                    // ignore\n                }\n            }\n            await exited;\n        }\n        // The child is gone — release the PARENT-side pipe handles too. A helper\n        // process holding the inherited write ends would otherwise keep them (and\n        // with them the host's event loop: stdout carries a flowing 'data'\n        // listener from start()) alive until the helper exits.\n        try {\n            proc?.stdout?.destroy();\n        } catch {\n            // ignore\n        }\n        try {\n            proc?.stdin?.destroy();\n        } catch {\n            // ignore\n        }\n        try {\n            proc?.stderr?.destroy();\n        } catch {\n            // ignore\n        }\n        this._readBuffer.clear();\n    }\n\n    async close(): Promise<void> {\n        if (this._process) {\n            const processToClose = this._process;\n            this._process = undefined;\n\n            const closePromise = new Promise<void>(resolve => {\n                processToClose.once('close', () => {\n                    resolve();\n                });\n            });\n\n            try {\n                processToClose.stdin?.end();\n            } catch {\n                // ignore\n            }\n\n            await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);\n\n            if (processToClose.exitCode === null) {\n                try {\n                    processToClose.kill('SIGTERM');\n                } catch {\n                    // ignore\n                }\n\n                await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);\n            }\n\n            if (processToClose.exitCode === null) {\n                try {\n                    processToClose.kill('SIGKILL');\n                } catch {\n                    // ignore\n                }\n            }\n        }\n\n        this._readBuffer.clear();\n    }\n\n    send(message: JSONRPCMessage): Promise<void> {\n        return new Promise(resolve => {\n            if (!this._process?.stdin) {\n                throw new SdkError(SdkErrorCode.NotConnected, 'Not connected');\n            }\n\n            const json = serializeMessage(message);\n            if (this._process.stdin.write(json)) {\n                resolve();\n            } else {\n                this._process.stdin.once('drain', resolve);\n            }\n        });\n    }\n}\n"],"mappings":"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"}