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
JavaScript
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
};