proxy.ts•4.94 kB
import type { UpstreamServerManager } from "../mcp/upstream-server-manager.js";
import type { MacroManager } from "../mcp/macros.js";
import type { EvalRuntime } from "./runtime.js";
/**
* Converts dash-case or snake_case to camelCase
* Example: agent-debugger -> agentDebugger, list_targets -> listTargets
*/
function toCamelCase(str: string): string {
return str
.replace(/[-_]([a-z])/g, (_, letter) => letter.toUpperCase())
.replace(/^[a-z]/, (letter) => letter.toLowerCase());
}
export interface ToolProxy {
[toolName: string]: (args?: unknown) => Promise<unknown>;
}
export interface ServerProxies {
[serverName: string]: ToolProxy;
}
export async function createServerProxies(
upstreamServerManager: UpstreamServerManager
): Promise<ServerProxies> {
const proxies: ServerProxies = {};
// Create proxies for ALL connected servers (both 'connected' and 'enabled' states)
// The proxy will check state when tools are called
for (const managedServer of upstreamServerManager.getAllServers()) {
if (managedServer.state !== "not-connected") {
try {
proxies[managedServer.name] = await managedServer.createProxy();
} catch (error) {
console.error(`Failed to create proxy for server ${managedServer.name}:`, error);
}
}
}
return proxies;
}
function createMacrosProxy(macroManager: MacroManager, evalRuntime: EvalRuntime): ToolProxy {
// Create a lazy proxy that loads macros on-demand
return new Proxy(
{},
{
get(_target, prop, _receiver) {
if (typeof prop === "string") {
// Return a function that will execute the macro
return async (args?: unknown) => {
return await macroManager.executeMacro(prop, args || {}, evalRuntime);
};
}
return undefined;
},
}
);
}
export function createGlobalContext(
proxies: ServerProxies,
upstreamServerManager: UpstreamServerManager,
macroManager: MacroManager | null,
evalRuntime: EvalRuntime | null
): Record<string, unknown> {
// Create the global context that will be available in eval'd code
const context: Record<string, unknown> = {};
// Add each server as a global variable (with both original and camelCase names)
for (const [serverName, toolProxy] of Object.entries(proxies)) {
context[serverName] = toolProxy;
// Also add camelCase version (e.g., agent-debugger -> agentDebugger)
const camelCaseName = toCamelCase(serverName);
if (camelCaseName !== serverName) {
context[camelCaseName] = toolProxy;
}
}
// Add macros proxy if MacroManager is available
if (macroManager && evalRuntime) {
context.macros = createMacrosProxy(macroManager, evalRuntime);
}
// Add utility functions
context.listServers = () => Object.keys(proxies);
context.listTools = (serverName?: string) => {
if (serverName) {
const server = proxies[serverName];
return server ? Object.keys(server) : [];
}
const allTools: Record<string, string[]> = {};
for (const [name, proxy] of Object.entries(proxies)) {
allTools[name] = Object.keys(proxy);
}
return allTools;
};
context.help = async (serverName: string, toolName?: string) => {
// Get managed server
const managedServer = upstreamServerManager.getManagedServer(serverName);
if (!managedServer || managedServer.state !== "enabled") {
throw new Error(
`Server '${serverName}' not found or not enabled. Available servers: ${Object.keys(proxies).join(", ")}`
);
}
try {
// Get all tools for the server
const result = await managedServer.listTools();
const tools = result.tools || [];
if (toolName) {
// Show help for specific tool
const tool = tools.find((t: { name: string }) => t.name === toolName);
if (!tool) {
throw new Error(
`Tool '${toolName}' not found in server '${serverName}'. Available tools: ${tools.map((t: { name: string }) => t.name).join(", ")}`
);
}
return {
server: serverName,
tool: {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema || {},
},
};
} else {
// Show help for all tools in server
return {
server: serverName,
tools: tools.map(
(tool: { name: string; description?: string; inputSchema?: unknown }) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema || {},
})
),
};
}
} catch (error) {
throw new Error(
`Error getting tools from server '${serverName}': ${error instanceof Error ? error.message : String(error)}`
);
}
};
// Add vars object for persisting state across eval calls
context.vars = {};
return context;
}