/** * MCP Hypervisor * * Manages N running MCP server processes, pings them for liveness, and * dynamically exposes their tools at runtime. Inspired by the * MCPCompatibilityLayer/MCPHypervisor pattern from AnythingLLM. * * Architecture: * - Singleton: one hypervisor per process manages all MCP servers * - Each server is spawned with piped stdio for JSON-RPC communication * - Tool names are namespaced: @@mcp_{serverName}_{toolName} * - Health check loop: every 60s, process.exitCode check + tools/list probe * * Wire format: newline-delimited JSON-RPC 2.0 (stdin/stdout) */ import type { McpMeterSink } from './metering.js'; export interface MCPServerConfig { /** Unique name for this server (used in tool namespacing) */ name: string; /** Executable to run (e.g. 'node', 'pnpm') */ command: string; /** Arguments to the command */ args: string[]; /** Additional environment variables */ env?: Record; /** Required tier to access this server's tools (default: 'free') */ requiredTier?: 'free' | 'pro' | 'max' | 'enterprise'; } /** * Resolves credentials for a specific tenant/workspace at server spawn time. * Implementations should fetch from the database (tenantProviderConfigs, userApiKeys) * and return env vars to inject into the MCP server process. */ export interface MCPCredentialResolver { /** * Resolve credentials for a tenant+server combination. * Returns env vars to merge into the server process environment. * Return null if the tenant has no credentials for this server. */ resolve(tenantId: string, serverName: string): Promise | null>; } /** * Context for a tenant-scoped MCP operation. */ export interface MCPTenantContext { tenantId: string; userId?: string; tier: 'free' | 'pro' | 'max' | 'enterprise'; } export interface MCPTool { name: string; description: string; inputSchema: { type: 'object'; properties?: Record; required?: string[]; }; /** Minimum tier required to invoke this tool (default: 'free') */ requiredTier?: 'free' | 'pro' | 'max' | 'enterprise'; } export interface NamespacedTool { /** Namespaced name: @@mcp_{serverName}_{toolName} */ namespacedName: string; serverName: string; tool: MCPTool; /** Minimum tier required to invoke this tool (inherited from tool or server) */ requiredTier?: 'free' | 'pro' | 'max' | 'enterprise'; } /** * Singleton that manages MCP server processes and their tool registries. * * @example * ```typescript * const hypervisor = MCPHypervisor.getInstance() * * hypervisor.registerServer({ * name: 'stripe', * command: 'pnpm', * args: ['dlx', '@stripe/mcp', '--tools=all', '--api-key=sk_...'], * }) * * await hypervisor.startServer('stripe') * const tools = hypervisor.getAllTools() * // tools[0].namespacedName === '@@mcp_stripe_create_payment_intent' * ``` */ export declare class MCPHypervisor { private static instance; private servers; /** Tenant-scoped server instances: key = `${tenantId}:${serverName}` */ private tenantServers; private credentialResolver; private meterSink; private requestCounter; private pendingRequests; private healthCheckTimer; private constructor(); static getInstance(): MCPHypervisor; /** * Register an MCP server configuration without starting it. */ registerServer(config: MCPServerConfig): void; /** * Unregister a server (stops it first if running). */ unregisterServer(name: string): Promise; /** * Spawn the MCP server process with piped stdio. * Tools are discovered via `listServerTools()` after startup. */ startServer(name: string): Promise; /** * Stop a running MCP server process. */ stopServer(name: string): Promise; /** * Stop all running servers. */ stopAll(): Promise; /** * Send a JSON-RPC request to a running server and await the response. */ private sendRequest; private handleResponse; /** * Ping a server - checks process liveness and sends a JSON-RPC `ping`. * Updates `entry.healthy`. */ pingServer(name: string): Promise; /** * Discover tools from a running server via JSON-RPC `tools/list`. * Caches the result in the server entry. */ listServerTools(name: string): Promise; /** * Return all tools from all healthy servers, namespaced as * `@@mcp_{serverName}_{toolName}` to avoid collisions. */ getAllTools(): NamespacedTool[]; /** * Call a tool on a running MCP server via JSON-RPC `tools/call`. * * @param serverName - The registered server name * @param toolName - The tool name (without namespace prefix) * @param args - Arguments to pass to the tool */ callTool(serverName: string, toolName: string, args: unknown): Promise; /** * Return the health status of all registered servers. */ getStatus(): Record; /** * Set the credential resolver for tenant-scoped server operations. * Must be set before calling any tenant-scoped methods. */ setCredentialResolver(resolver: MCPCredentialResolver): void; /** * Install a consumer-wired metering sink. Fires exactly once per * tool-call boundary (success or failure) with an `McpMeterEvent`. * Sinks may be sync or async — invocation is fire-and-forget; sink * errors are logged at warn and never propagate into the call path. * * See `./metering.ts` for the event shape. Pass `null` to disable. */ setUsageMeterSink(sink: McpMeterSink | null): void; /** * Fire the consumer-wired metering sink. Swallows errors so * observability can never corrupt a successful tool call. * * @internal */ private emitMeter; /** * Start an MCP server with tenant-specific credentials. * Each tenant gets its own isolated server process with credentials * resolved from the database (stored keys or platform defaults). * * Server instances are keyed by `${tenantId}:${serverName}`. */ startServerForTenant(serverName: string, ctx: MCPTenantContext): Promise; /** * Stop a tenant-scoped server instance. */ stopServerForTenant(serverName: string, tenantId: string): Promise; /** * Call a tool on a tenant-scoped server instance. */ callToolForTenant(serverName: string, tenantId: string, toolName: string, args: unknown): Promise; /** * Get all tools available to a tenant, filtered by tier. * Checks both server-level and tool-level requiredTier - the effective * requirement is the higher of the two. */ getToolsForTenant(ctx: MCPTenantContext): NamespacedTool[]; /** * Stop all tenant-scoped servers (called during shutdown). */ private stopAllTenantServers; private tierSatisfied; private higherTier; private startHealthCheckLoop; private stopHealthCheckLoop; /** * Reset the singleton (for testing only). * @internal */ static _resetForTests(): void; } //# sourceMappingURL=hypervisor.d.ts.map