Skip to main content
Glama
stdio.js6.97 kB
"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' && isElectron(), 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; function isElectron() { return 'type' in node_process_1.default; } //# sourceMappingURL=stdio.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Valerio357/bet-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server