| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- "use strict";
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.StdioClientTransport = exports.DEFAULT_INHERITED_ENV_VARS = void 0;
- exports.getDefaultEnvironment = getDefaultEnvironment;
- const cross_spawn_1 = __importDefault(require("cross-spawn"));
- const node_process_1 = __importDefault(require("node:process"));
- const node_stream_1 = require("node:stream");
- const stdio_js_1 = require("../shared/stdio.js");
- /**
- * Environment variables to inherit by default, if an environment is not explicitly given.
- */
- exports.DEFAULT_INHERITED_ENV_VARS = node_process_1.default.platform === 'win32'
- ? [
- 'APPDATA',
- 'HOMEDRIVE',
- 'HOMEPATH',
- 'LOCALAPPDATA',
- 'PATH',
- 'PROCESSOR_ARCHITECTURE',
- 'SYSTEMDRIVE',
- 'SYSTEMROOT',
- 'TEMP',
- 'USERNAME',
- 'USERPROFILE',
- 'PROGRAMFILES'
- ]
- : /* list inspired by the default env inheritance of sudo */
- ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
- /**
- * Returns a default environment object including only environment variables deemed safe to inherit.
- */
- function getDefaultEnvironment() {
- const env = {};
- for (const key of exports.DEFAULT_INHERITED_ENV_VARS) {
- const value = node_process_1.default.env[key];
- if (value === undefined) {
- continue;
- }
- if (value.startsWith('()')) {
- // Skip functions, which are a security risk.
- continue;
- }
- env[key] = value;
- }
- return env;
- }
- /**
- * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
- *
- * This transport is only available in Node.js environments.
- */
- class StdioClientTransport {
- constructor(server) {
- this._readBuffer = new stdio_js_1.ReadBuffer();
- this._stderrStream = null;
- this._serverParams = server;
- if (server.stderr === 'pipe' || server.stderr === 'overlapped') {
- this._stderrStream = new node_stream_1.PassThrough();
- }
- }
- /**
- * Starts the server process and prepares to communicate with it.
- */
- async start() {
- if (this._process) {
- throw new Error('StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.');
- }
- return new Promise((resolve, reject) => {
- this._process = (0, cross_spawn_1.default)(this._serverParams.command, this._serverParams.args ?? [], {
- // merge default env with server env because mcp server needs some env vars
- env: {
- ...getDefaultEnvironment(),
- ...this._serverParams.env
- },
- stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'],
- shell: false,
- windowsHide: node_process_1.default.platform === 'win32',
- cwd: this._serverParams.cwd
- });
- this._process.on('error', error => {
- reject(error);
- this.onerror?.(error);
- });
- this._process.on('spawn', () => {
- resolve();
- });
- this._process.on('close', _code => {
- this._process = undefined;
- this.onclose?.();
- });
- this._process.stdin?.on('error', error => {
- this.onerror?.(error);
- });
- this._process.stdout?.on('data', chunk => {
- this._readBuffer.append(chunk);
- this.processReadBuffer();
- });
- this._process.stdout?.on('error', error => {
- this.onerror?.(error);
- });
- if (this._stderrStream && this._process.stderr) {
- this._process.stderr.pipe(this._stderrStream);
- }
- });
- }
- /**
- * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
- *
- * If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
- * attach listeners before the start method is invoked. This prevents loss of any early
- * error output emitted by the child process.
- */
- get stderr() {
- if (this._stderrStream) {
- return this._stderrStream;
- }
- return this._process?.stderr ?? null;
- }
- /**
- * The child process pid spawned by this transport.
- *
- * This is only available after the transport has been started.
- */
- get pid() {
- return this._process?.pid ?? null;
- }
- processReadBuffer() {
- while (true) {
- try {
- const message = this._readBuffer.readMessage();
- if (message === null) {
- break;
- }
- this.onmessage?.(message);
- }
- catch (error) {
- this.onerror?.(error);
- }
- }
- }
- async close() {
- if (this._process) {
- const processToClose = this._process;
- this._process = undefined;
- const closePromise = new Promise(resolve => {
- processToClose.once('close', () => {
- resolve();
- });
- });
- try {
- processToClose.stdin?.end();
- }
- catch {
- // ignore
- }
- await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);
- if (processToClose.exitCode === null) {
- try {
- processToClose.kill('SIGTERM');
- }
- catch {
- // ignore
- }
- await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);
- }
- if (processToClose.exitCode === null) {
- try {
- processToClose.kill('SIGKILL');
- }
- catch {
- // ignore
- }
- }
- }
- this._readBuffer.clear();
- }
- send(message) {
- return new Promise(resolve => {
- if (!this._process?.stdin) {
- throw new Error('Not connected');
- }
- const json = (0, stdio_js_1.serializeMessage)(message);
- if (this._process.stdin.write(json)) {
- resolve();
- }
- else {
- this._process.stdin.once('drain', resolve);
- }
- });
- }
- }
- exports.StdioClientTransport = StdioClientTransport;
- //# sourceMappingURL=stdio.js.map
|