UNPKG

mcp-use

Version:

Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents, Clients and Servers with support for ChatGPT Apps, Code Mode, OAuth, Notifications, Sampling, Observability and more.

1,089 lines (1,069 loc) • 33.4 kB
import { CodeModeConnector } from "./chunk-YTP55WBX.js"; import { BaseMCPClient, ConnectionManager, HttpConnector, MCPSession } from "./chunk-N545LZAQ.js"; import { BaseConnector } from "./chunk-YHC2FTSG.js"; import { Tel, getPackageVersion } from "./chunk-B2CGDIF6.js"; import { logger } from "./chunk-U7F22OTV.js"; import { __name, __require } from "./chunk-3GQAWCBQ.js"; // src/client.ts import fs from "fs"; import path from "path"; // src/client/executors/base.ts var BaseCodeExecutor = class { static { __name(this, "BaseCodeExecutor"); } client; _connecting = false; constructor(client) { this.client = client; } /** * Ensure all configured MCP servers are connected before execution. * Prevents race conditions with a connection lock. */ async ensureServersConnected() { const configuredServers = this.client.getServerNames(); const activeSessions = Object.keys(this.client.getAllActiveSessions()); const missingServers = configuredServers.filter( (s) => !activeSessions.includes(s) ); if (missingServers.length > 0 && !this._connecting) { this._connecting = true; try { logger.debug( `Connecting to configured servers for code execution: ${missingServers.join(", ")}` ); await this.client.createAllSessions(); } finally { this._connecting = false; } } else if (missingServers.length > 0 && this._connecting) { logger.debug("Waiting for ongoing server connection..."); const startWait = Date.now(); while (this._connecting && Date.now() - startWait < 5e3) { await new Promise((resolve) => setTimeout(resolve, 100)); } } } /** * Get tool namespace information from all active MCP sessions. * Filters out the internal code_mode server. */ getToolNamespaces() { const namespaces = []; const activeSessions = this.client.getAllActiveSessions(); for (const [serverName, session] of Object.entries(activeSessions)) { if (serverName === "code_mode") continue; try { const connector = session.connector; let tools; try { tools = connector.tools; } catch (e) { logger.warn(`Tools not available for server ${serverName}: ${e}`); continue; } if (!tools || tools.length === 0) continue; namespaces.push({ serverName, tools, session }); } catch (e) { logger.warn(`Failed to load tools for server ${serverName}: ${e}`); } } return namespaces; } /** * Create a search function for discovering available MCP tools. * Used by code execution environments to find tools at runtime. */ createSearchToolsFunction() { return async (query = "", detailLevel = "full") => { const allTools = []; const allNamespaces = /* @__PURE__ */ new Set(); const queryLower = query.toLowerCase(); const activeSessions = this.client.getAllActiveSessions(); for (const [serverName, session] of Object.entries(activeSessions)) { if (serverName === "code_mode") continue; try { const tools = session.connector.tools; if (tools && tools.length > 0) { allNamespaces.add(serverName); } for (const tool of tools) { if (detailLevel === "names") { allTools.push({ name: tool.name, server: serverName }); } else if (detailLevel === "descriptions") { allTools.push({ name: tool.name, server: serverName, description: tool.description }); } else { allTools.push({ name: tool.name, server: serverName, description: tool.description, input_schema: tool.inputSchema }); } } } catch (e) { logger.warn(`Failed to search tools in server ${serverName}: ${e}`); } } let filteredTools = allTools; if (query) { filteredTools = allTools.filter((tool) => { const nameMatch = tool.name.toLowerCase().includes(queryLower); const descMatch = tool.description?.toLowerCase().includes(queryLower); const serverMatch = tool.server.toLowerCase().includes(queryLower); return nameMatch || descMatch || serverMatch; }); } return { meta: { total_tools: allTools.length, namespaces: Array.from(allNamespaces).sort(), result_count: filteredTools.length }, results: filteredTools }; }; } }; // src/client/executors/e2b.ts var E2BCodeExecutor = class extends BaseCodeExecutor { static { __name(this, "E2BCodeExecutor"); } e2bApiKey; codeExecSandbox = null; SandboxClass = null; timeoutMs; constructor(client, options) { super(client); this.e2bApiKey = options.apiKey; this.timeoutMs = options.timeoutMs ?? 3e5; } /** * Lazy load E2B Sandbox class. * This allows the library to work without E2B installed. */ async ensureSandboxClass() { if (this.SandboxClass) return; try { const e2b = await import("@e2b/code-interpreter"); this.SandboxClass = e2b.Sandbox; } catch (error) { throw new Error( "@e2b/code-interpreter is not installed. The E2B code executor requires this optional dependency. Install it with: yarn add @e2b/code-interpreter" ); } } /** * Get or create a dedicated sandbox for code execution. */ async getOrCreateCodeExecSandbox() { if (this.codeExecSandbox) return this.codeExecSandbox; await this.ensureSandboxClass(); logger.debug("Starting E2B sandbox for code execution..."); this.codeExecSandbox = await this.SandboxClass.create("base", { apiKey: this.e2bApiKey, timeoutMs: this.timeoutMs }); return this.codeExecSandbox; } /** * Generate the shim code that exposes tools to the sandbox environment. * Creates a bridge that intercepts tool calls and sends them back to host. */ generateShim(tools) { let shim = ` // MCP Bridge Shim global.__callMcpTool = async (server, tool, args) => { const id = Math.random().toString(36).substring(7); console.log(JSON.stringify({ type: '__MCP_TOOL_CALL__', id, server, tool, args })); const resultPath = \`/tmp/mcp_result_\${id}.json\`; const fs = require('fs'); // Poll for result file let attempts = 0; while (attempts < 300) { // 30 seconds timeout if (fs.existsSync(resultPath)) { const content = fs.readFileSync(resultPath, 'utf8'); const result = JSON.parse(content); fs.unlinkSync(resultPath); // Clean up if (result.error) { throw new Error(result.error); } return result.data; } await new Promise(resolve => setTimeout(resolve, 100)); attempts++; } throw new Error('Tool execution timed out'); }; // Global search_tools helper global.search_tools = async (query, detailLevel = 'full') => { const allTools = ${JSON.stringify( Object.entries(tools).flatMap( ([server, serverTools]) => serverTools.map((tool) => ({ name: tool.name, description: tool.description, server, input_schema: tool.inputSchema })) ) )}; const filtered = allTools.filter(tool => { if (!query) return true; const q = query.toLowerCase(); return tool.name.toLowerCase().includes(q) || (tool.description && tool.description.toLowerCase().includes(q)); }); if (detailLevel === 'names') { return filtered.map(t => ({ name: t.name, server: t.server })); } else if (detailLevel === 'descriptions') { return filtered.map(t => ({ name: t.name, server: t.server, description: t.description })); } return filtered; }; `; for (const [serverName, serverTools] of Object.entries(tools)) { if (!serverTools || serverTools.length === 0) continue; const safeServerName = serverName.replace(/[^a-zA-Z0-9_]/g, "_"); shim += ` global['${serverName}'] = {`; for (const tool of serverTools) { shim += ` '${tool.name}': async (args) => await global.__callMcpTool('${serverName}', '${tool.name}', args),`; } shim += ` }; // Also expose as safe name if different if ('${safeServerName}' !== '${serverName}') { global['${safeServerName}'] = global['${serverName}']; } `; } return shim; } /** * Build the tool catalog for the shim. * Returns a map of server names to their available tools. */ buildToolCatalog() { const catalog = {}; const namespaces = this.getToolNamespaces(); for (const { serverName, tools } of namespaces) { catalog[serverName] = tools; } return catalog; } /** * Execute JavaScript/TypeScript code in an E2B sandbox with MCP tool access. * Tool calls are proxied back to the host via the bridge pattern. * * @param code - Code to execute * @param timeout - Execution timeout in milliseconds (default: 30000) */ async execute(code, timeout = 3e4) { const startTime = Date.now(); let result = null; let error = null; let logs = []; try { await this.ensureServersConnected(); const sandbox = await this.getOrCreateCodeExecSandbox(); const toolCatalog = this.buildToolCatalog(); const shim = this.generateShim(toolCatalog); const wrappedCode = ` ${shim} (async () => { try { const func = async () => { ${code} }; const result = await func(); console.log('__MCP_RESULT_START__'); console.log(JSON.stringify(result)); console.log('__MCP_RESULT_END__'); } catch (e) { console.error(e); process.exit(1); } })(); `; const filename = `exec_${Date.now()}.js`; await sandbox.files.write(filename, wrappedCode); const execution = await sandbox.commands.run(`node ${filename}`, { timeoutMs: timeout, onStdout: /* @__PURE__ */ __name(async (data) => { try { const lines = data.split("\n"); for (const line of lines) { if (line.trim().startsWith('{"type":"__MCP_TOOL_CALL__"')) { const call = JSON.parse(line); if (call.type === "__MCP_TOOL_CALL__") { try { logger.debug( `[E2B Bridge] Calling tool ${call.server}.${call.tool}` ); const activeSessions = this.client.getAllActiveSessions(); const session = activeSessions[call.server]; if (!session) { throw new Error(`Server ${call.server} not found`); } const toolResult = await session.connector.callTool( call.tool, call.args ); let extractedResult = toolResult; if (toolResult.content && toolResult.content.length > 0) { const item = toolResult.content[0]; if (item.type === "text") { try { extractedResult = JSON.parse(item.text); } catch { extractedResult = item.text; } } else { extractedResult = item; } } const resultPath = `/tmp/mcp_result_${call.id}.json`; await sandbox.files.write( resultPath, JSON.stringify({ data: extractedResult }) ); } catch (err) { logger.error( `[E2B Bridge] Tool execution failed: ${err.message}` ); const resultPath = `/tmp/mcp_result_${call.id}.json`; await sandbox.files.write( resultPath, JSON.stringify({ error: err.message || String(err) }) ); } } } } } catch (e) { } }, "onStdout") }); logs = [execution.stdout, execution.stderr].filter(Boolean); if (execution.exitCode !== 0) { error = execution.stderr || "Execution failed"; } else { const stdout = execution.stdout; const startMarker = "__MCP_RESULT_START__"; const endMarker = "__MCP_RESULT_END__"; const startIndex = stdout.indexOf(startMarker); const endIndex = stdout.indexOf(endMarker); if (startIndex !== -1 && endIndex !== -1) { const jsonStr = stdout.substring(startIndex + startMarker.length, endIndex).trim(); try { result = JSON.parse(jsonStr); } catch (e) { result = jsonStr; } logs = logs.map((log) => { let cleaned = log.replace( new RegExp(startMarker + "[\\s\\S]*?" + endMarker), "[Result captured]" ); cleaned = cleaned.split("\n").filter((l) => !l.includes("__MCP_TOOL_CALL__")).join("\n"); return cleaned; }); } } } catch (e) { error = e.message || String(e); if (error && (error.includes("timeout") || error.includes("timed out"))) { error = "Script execution timed out"; } } return { result, logs, error, execution_time: (Date.now() - startTime) / 1e3 }; } /** * Clean up the E2B sandbox. * Should be called when the executor is no longer needed. */ async cleanup() { if (this.codeExecSandbox) { try { await this.codeExecSandbox.kill(); this.codeExecSandbox = null; logger.debug("E2B code execution sandbox stopped"); } catch (error) { logger.error("Failed to stop E2B code execution sandbox:", error); } } } }; // src/client/executors/vm.ts var vm = null; var vmCheckAttempted = false; function getVMModuleName() { return ["node", "vm"].join(":"); } __name(getVMModuleName, "getVMModuleName"); function tryLoadVM() { if (vmCheckAttempted) { return vm !== null; } vmCheckAttempted = true; try { const nodeRequire = typeof __require !== "undefined" ? __require : null; if (nodeRequire) { vm = nodeRequire(getVMModuleName()); return true; } } catch (error) { logger.debug("node:vm module not available via require"); } return false; } __name(tryLoadVM, "tryLoadVM"); async function tryLoadVMAsync() { if (vm !== null) { return true; } if (!vmCheckAttempted) { if (tryLoadVM()) { return true; } } try { vm = await import( /* @vite-ignore */ getVMModuleName() ); return true; } catch (error) { logger.debug( "node:vm module not available in this environment (e.g., Deno)" ); return false; } } __name(tryLoadVMAsync, "tryLoadVMAsync"); function isVMAvailable() { tryLoadVM(); return vm !== null; } __name(isVMAvailable, "isVMAvailable"); var VMCodeExecutor = class extends BaseCodeExecutor { static { __name(this, "VMCodeExecutor"); } defaultTimeout; memoryLimitMb; constructor(client, options) { super(client); this.defaultTimeout = options?.timeoutMs ?? 3e4; this.memoryLimitMb = options?.memoryLimitMb; tryLoadVM(); } /** * Ensure VM module is loaded before execution */ async ensureVMLoaded() { if (vm !== null) { return; } const loaded = await tryLoadVMAsync(); if (!loaded) { throw new Error( "node:vm module is not available in this environment. Please use E2B executor instead or run in a Node.js environment." ); } } /** * Execute JavaScript/TypeScript code with access to MCP tools. * * @param code - Code to execute * @param timeout - Execution timeout in milliseconds (default: configured timeout or 30000) */ async execute(code, timeout) { const effectiveTimeout = timeout ?? this.defaultTimeout; await this.ensureVMLoaded(); await this.ensureServersConnected(); const logs = []; const startTime = Date.now(); let result = null; let error = null; try { const context = await this._buildContext(logs); const wrappedCode = ` (async () => { try { ${code} } catch (e) { throw e; } })() `; const script = new vm.Script(wrappedCode, { filename: "agent_code.js" }); const promise = script.runInNewContext(context, { timeout: effectiveTimeout, displayErrors: true }); result = await promise; } catch (e) { error = e.message || String(e); if (e.code === "ERR_SCRIPT_EXECUTION_TIMEOUT" || e.message === "Script execution timed out." || typeof error === "string" && (error.includes("timed out") || error.includes("timeout"))) { error = "Script execution timed out"; } if (e.stack) { logger.debug(`Code execution error stack: ${e.stack}`); } } const executionTime = (Date.now() - startTime) / 1e3; return { result, logs, error, execution_time: executionTime }; } /** * Build the VM execution context with MCP tools and standard globals. * * @param logs - Array to capture console output */ async _buildContext(logs) { const logHandler = /* @__PURE__ */ __name((...args) => { logs.push( args.map( (arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg) ).join(" ") ); }, "logHandler"); const sandbox = { console: { log: logHandler, error: /* @__PURE__ */ __name((...args) => { logHandler("[ERROR]", ...args); }, "error"), warn: /* @__PURE__ */ __name((...args) => { logHandler("[WARN]", ...args); }, "warn"), info: logHandler, debug: logHandler }, // Standard globals Object, Array, String, Number, Boolean, Date, Math, JSON, RegExp, Map, Set, Promise, parseInt, parseFloat, isNaN, isFinite, encodeURI, decodeURI, encodeURIComponent, decodeURIComponent, setTimeout, clearTimeout, // Helper for tools search_tools: this.createSearchToolsFunction(), __tool_namespaces: [] }; const toolNamespaces = {}; const namespaceInfos = this.getToolNamespaces(); for (const { serverName, tools, session } of namespaceInfos) { const serverNamespace = {}; for (const tool of tools) { const toolName = tool.name; serverNamespace[toolName] = async (args) => { const result = await session.connector.callTool(toolName, args || {}); if (result.content && result.content.length > 0) { const item = result.content[0]; if (item.type === "text") { try { return JSON.parse(item.text); } catch { return item.text; } } return item; } return result; }; } sandbox[serverName] = serverNamespace; toolNamespaces[serverName] = true; } sandbox.__tool_namespaces = Object.keys(toolNamespaces); return vm.createContext(sandbox); } /** * Clean up resources. * VM executor doesn't need cleanup, but method kept for interface consistency. */ async cleanup() { } }; // src/config.ts import { readFileSync } from "fs"; // src/connectors/stdio.ts import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import process2 from "process"; // src/task_managers/stdio.ts import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; var StdioConnectionManager = class extends ConnectionManager { static { __name(this, "StdioConnectionManager"); } serverParams; errlog; _transport = null; /** * Create a new stdio connection manager. * * @param serverParams Parameters for the stdio server process. * @param errlog Stream to which the server's stderr should be piped. * Defaults to `process.stderr`. */ constructor(serverParams, errlog = process.stderr) { super(); this.serverParams = serverParams; this.errlog = errlog; } /** * Establish the stdio connection by spawning the server process and starting * the SDK's transport. Returns the live `StdioClientTransport` instance. */ async establishConnection() { this._transport = new StdioClientTransport(this.serverParams); if (this._transport.stderr && typeof this._transport.stderr.pipe === "function") { this._transport.stderr.pipe( this.errlog ); } logger.debug(`${this.constructor.name} connected successfully`); return this._transport; } /** * Close the stdio connection, making sure the transport cleans up the child * process and associated resources. */ async closeConnection(_connection) { if (this._transport) { try { await this._transport.close(); } catch (e) { logger.warn(`Error closing stdio transport: ${e}`); } finally { this._transport = null; } } } }; // src/connectors/stdio.ts var StdioConnector = class extends BaseConnector { static { __name(this, "StdioConnector"); } command; args; env; errlog; clientInfo; constructor({ command = "npx", args = [], env, errlog = process2.stderr, ...rest } = {}) { super(rest); this.command = command; this.args = args; this.env = env; this.errlog = errlog; this.clientInfo = rest.clientInfo ?? { name: "stdio-connector", version: "1.0.0" }; } /** Establish connection to the MCP implementation. */ async connect() { if (this.connected) { logger.debug("Already connected to MCP implementation"); return; } logger.debug(`Connecting to MCP implementation via stdio: ${this.command}`); try { let mergedEnv; if (this.env) { mergedEnv = {}; for (const [key, value] of Object.entries(process2.env)) { if (value !== void 0) { mergedEnv[key] = value; } } Object.assign(mergedEnv, this.env); } const serverParams = { command: this.command, args: this.args, env: mergedEnv }; this.connectionManager = new StdioConnectionManager( serverParams, this.errlog ); const transport = await this.connectionManager.start(); const clientOptions = { ...this.opts.clientOptions || {}, capabilities: { ...this.opts.clientOptions?.capabilities || {}, roots: { listChanged: true }, // Always advertise roots capability // Add sampling capability if callback is provided ...this.opts.onSampling ?? this.opts.samplingCallback ? { sampling: {} } : {}, // Add elicitation capability if callback is provided ...this.opts.elicitationCallback ? { elicitation: { form: {}, url: {} } } : {} } }; this.client = new Client(this.clientInfo, clientOptions); await this.client.connect(transport); this.connected = true; this.setupNotificationHandler(); this.setupRootsHandler(); this.setupSamplingHandler(); this.setupElicitationHandler(); logger.debug( `Successfully connected to MCP implementation: ${this.command}` ); this.trackConnectorInit({ serverCommand: this.command, serverArgs: this.args, publicIdentifier: `${this.command} ${this.args.join(" ")}` }); } catch (err) { logger.error(`Failed to connect to MCP implementation: ${err}`); await this.cleanupResources(); throw err; } } get publicIdentifier() { return { type: "stdio", "command&args": `${this.command} ${this.args.join(" ")}` }; } }; // src/config.ts function getDefaultClientInfo() { return { name: "mcp-use", title: "mcp-use", version: getPackageVersion(), description: "mcp-use is a complete TypeScript framework for building and using MCP", icons: [ { src: "https://mcp-use.com/logo.png" } ], websiteUrl: "https://mcp-use.com" }; } __name(getDefaultClientInfo, "getDefaultClientInfo"); function normalizeClientInfo(input) { const fallback = getDefaultClientInfo(); if (!input || typeof input !== "object") return fallback; const ci = input; if (!ci.name || !ci.version) return fallback; return { ...fallback, ...ci }; } __name(normalizeClientInfo, "normalizeClientInfo"); function loadConfigFile(filepath) { const raw = readFileSync(filepath, "utf-8"); return JSON.parse(raw); } __name(loadConfigFile, "loadConfigFile"); function createConnectorFromConfig(serverConfig, connectorOptions) { const clientInfo = normalizeClientInfo(serverConfig.clientInfo); if ("command" in serverConfig && "args" in serverConfig) { return new StdioConnector({ command: serverConfig.command, args: serverConfig.args, env: serverConfig.env, clientInfo, ...connectorOptions }); } if ("url" in serverConfig) { const transport = serverConfig.transport || "http"; return new HttpConnector(serverConfig.url, { headers: serverConfig.headers, authToken: serverConfig.auth_token || serverConfig.authToken, // Only force SSE if explicitly requested preferSse: serverConfig.preferSse || transport === "sse", // Disable SSE fallback if explicitly disabled in config disableSseFallback: serverConfig.disableSseFallback, clientInfo, ...connectorOptions }); } throw new Error("Cannot determine connector type from config"); } __name(createConnectorFromConfig, "createConnectorFromConfig"); // src/client.ts var MCPClient = class _MCPClient extends BaseMCPClient { static { __name(this, "MCPClient"); } /** * Get the mcp-use package version. * Works in all environments (Node.js, browser, Cloudflare Workers, Deno, etc.) */ static getPackageVersion() { return getPackageVersion(); } codeMode = false; _codeExecutor = null; _customCodeExecutor = null; _codeExecutorConfig = "vm"; _executorOptions; _samplingCallback; _elicitationCallback; constructor(config, options) { if (config) { if (typeof config === "string") { super(loadConfigFile(config)); } else { super(config); } } else { super(); } let codeModeEnabled = false; let executorConfig = "vm"; let executorOptions; if (options?.codeMode) { if (typeof options.codeMode === "boolean") { codeModeEnabled = options.codeMode; } else { codeModeEnabled = options.codeMode.enabled; executorConfig = options.codeMode.executor ?? "vm"; executorOptions = options.codeMode.executorOptions; } } this.codeMode = codeModeEnabled; this._codeExecutorConfig = executorConfig; this._executorOptions = executorOptions; this._samplingCallback = options?.onSampling ?? options?.samplingCallback; if (options?.samplingCallback && !options?.onSampling) { console.warn( '[MCPClient] The "samplingCallback" option is deprecated. Use "onSampling" instead.' ); } this._elicitationCallback = options?.elicitationCallback; if (this.codeMode) { this._setupCodeModeConnector(); } this._trackClientInit(); } _trackClientInit() { const servers = Object.keys(this.config.mcpServers ?? {}); const hasSamplingCallback = !!this._samplingCallback; const hasElicitationCallback = !!this._elicitationCallback; Tel.getInstance().trackMCPClientInit({ codeMode: this.codeMode, sandbox: false, // Sandbox not supported in TS yet allCallbacks: hasSamplingCallback && hasElicitationCallback, verify: false, // No verify option in TS client servers, numServers: servers.length, isBrowser: false // Node.js MCPClient }).catch((e) => logger.debug(`Failed to track MCPClient init: ${e}`)); } static fromDict(cfg, options) { return new _MCPClient(cfg, options); } static fromConfigFile(path2, options) { return new _MCPClient(loadConfigFile(path2), options); } /** * Save configuration to a file (Node.js only) */ saveConfig(filepath) { const dir = path.dirname(filepath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filepath, JSON.stringify(this.config, null, 2), "utf-8"); } /** * Create a connector from server configuration (Node.js version) * Supports all connector types including StdioConnector */ createConnectorFromConfig(serverConfig) { return createConnectorFromConfig(serverConfig, { samplingCallback: this._samplingCallback, elicitationCallback: this._elicitationCallback }); } _setupCodeModeConnector() { logger.debug("Code mode connector initialized as internal meta server"); const connector = new CodeModeConnector(this); const session = new MCPSession(connector); this.sessions["code_mode"] = session; this.activeSessions.push("code_mode"); } _ensureCodeExecutor() { if (!this._codeExecutor) { const config = this._codeExecutorConfig; if (config instanceof BaseCodeExecutor) { this._codeExecutor = config; } else if (typeof config === "function") { this._customCodeExecutor = config; throw new Error( "Custom executor function should be handled in executeCode" ); } else if (config === "e2b") { const opts = this._executorOptions; if (!opts?.apiKey) { logger.warn("E2B executor requires apiKey. Falling back to VM."); try { this._codeExecutor = new VMCodeExecutor( this, this._executorOptions ); } catch (error) { throw new Error( "VM executor is not available in this environment and E2B API key is not provided. Please provide an E2B API key or run in a Node.js environment." ); } } else { this._codeExecutor = new E2BCodeExecutor(this, opts); } } else { try { this._codeExecutor = new VMCodeExecutor( this, this._executorOptions ); } catch (error) { const e2bOpts = this._executorOptions; const e2bApiKey = e2bOpts?.apiKey || process.env.E2B_API_KEY; if (e2bApiKey) { logger.info( "VM executor not available in this environment. Falling back to E2B." ); this._codeExecutor = new E2BCodeExecutor(this, { ...e2bOpts, apiKey: e2bApiKey }); } else { throw new Error( "VM executor is not available in this environment. Please provide an E2B API key via executorOptions or E2B_API_KEY environment variable, or run in a Node.js environment." ); } } } } return this._codeExecutor; } /** * Execute code in code mode */ async executeCode(code, timeout) { if (!this.codeMode) { throw new Error("Code execution mode is not enabled"); } if (this._customCodeExecutor) { return this._customCodeExecutor(code, timeout); } return this._ensureCodeExecutor().execute(code, timeout); } /** * Search available tools (used by code mode) */ async searchTools(query = "", detailLevel = "full") { if (!this.codeMode) { throw new Error("Code execution mode is not enabled"); } return this._ensureCodeExecutor().createSearchToolsFunction()( query, detailLevel ); } /** * Override getServerNames to exclude internal code_mode server */ getServerNames() { const isCodeModeEnabled = this.codeMode; return super.getServerNames().filter((name) => { return !isCodeModeEnabled || name !== "code_mode"; }); } /** * Close the client and clean up resources including code executors. * This ensures E2B sandboxes and other resources are properly released. */ async close() { if (this._codeExecutor) { await this._codeExecutor.cleanup(); this._codeExecutor = null; } await this.closeAllSessions(); } }; export { BaseCodeExecutor, E2BCodeExecutor, isVMAvailable, VMCodeExecutor, StdioConnector, loadConfigFile, MCPClient };