UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

235 lines (234 loc) • 9.39 kB
/** * External MCP Server Manager * Handles lifecycle management of external MCP servers including: * - Process spawning and management * - Health monitoring and automatic restart * - Connection management and cleanup * - Tool discovery and registration */ import { EventEmitter } from "events"; import { ToolDiscoveryService } from "./toolDiscoveryService.js"; import type { HITLManager, JsonObject, ServerLoadResult, ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPConfigValidation, ExternalMCPOperationResult, ExternalMCPManagerConfig, ExternalMCPToolInfo, MCPServerInfo } from "../types/index.js"; export declare class ExternalServerManager extends EventEmitter { private servers; private config; private isShuttingDown; /** Servers whose previous ping has not returned; the interval must not stack. */ private healthChecksInFlight; /** Tool calls currently executing per server; a busy server is not a hung one. */ private inFlightToolCalls; /** Consecutive pings missed while idle, per server. */ private unresponsiveChecks; private toolDiscovery; private enableMainRegistryIntegration; private hitlManager?; constructor(config?: ExternalMCPManagerConfig, options?: { enableMainRegistryIntegration?: boolean; }); /** * Attach a McpOutputNormalizer to the underlying ToolDiscoveryService. * All tool outputs will be measured and (if oversized) replaced with compact * surrogates before being returned to callers. */ setOutputNormalizer(normalizer: import("./mcpOutputNormalizer.js").McpOutputNormalizer): void; /** * Set HITL manager for human-in-the-loop safety mechanisms * @param hitlManager - HITL manager instance (optional, can be undefined to disable) */ setHITLManager(hitlManager?: HITLManager): void; /** * Get current HITL manager */ getHITLManager(): HITLManager | undefined; /** * Resolve the human-readable server name for an event payload. * Falls back to serverId if the instance or config.name isn't available. */ getServerName(serverId: string): string; /** * Load MCP server configurations from .mcp-config.json file with parallel loading support * Automatically registers servers found in the configuration * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd) * @param options Loading options including parallel support * @returns Promise resolving to { serversLoaded, errors } */ loadMCPConfiguration(configPath?: string, options?: { parallel?: boolean; }): Promise<ServerLoadResult>; /** * Load MCP servers in parallel for improved performance * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd) * @returns Promise resolving to batch operation result */ loadMCPConfigurationParallel(configPath?: string | null): Promise<ServerLoadResult>; /** * Load MCP servers sequentially (original implementation for backward compatibility) * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd) * @returns Promise resolving to batch operation result */ loadMCPConfigurationSequential(configPath?: string): Promise<ServerLoadResult>; /** * Validate external MCP server configuration */ validateConfig(config: MCPServerInfo): ExternalMCPConfigValidation; /** * Convert MCPServerInfo format (keeping for backward compatibility) * Helper function for transitioning to zero-conversion architecture */ private convertConfigToMCPServerInfo; /** * Add a new external MCP server - Backward compatibility overload */ addServer(serverId: string, config: MCPServerInfo): Promise<ExternalMCPOperationResult<ExternalMCPServerInstance>>; /** * Add a new external MCP server - Updated to accept MCPServerInfo */ addServer(serverId: string, serverInfo: MCPServerInfo): Promise<ExternalMCPOperationResult<ExternalMCPServerInstance>>; /** * Remove an external MCP server */ removeServer(serverId: string): Promise<ExternalMCPOperationResult<void>>; /** * Start an external MCP server */ private startServer; /** * Close a live client/transport pair, clearing the lifecycle hooks first * so the `onclose` they would otherwise trigger cannot schedule a restart * of a connection being torn down deliberately. Shared by `stopServer` and * the minTools readiness gate in `startServer`, which rejects a server * before it is ever registered as connected. Leaves status untouched — * callers set whatever status fits their case. */ private closeInstanceConnection; /** * Stop an external MCP server */ private stopServer; /** * Update server status and emit events */ private updateServerStatus; /** * Hook the SDK client's lifecycle so the manager learns about the server * dying from the object that actually owns the process. * * `client.onclose` fires when the stdio child closes or a network * transport drops. It also fires during our own `stopServer()`, which is * why every path into `handleConnectionLost` checks that the client is * still the instance's current one and the status is still "connected": * a close that arrives while stopping or restarting is intentional. */ private attachClientLifecycle; /** * Transition a server whose connection is gone, exactly once. * * Reached from three places that can all observe the same death — the * client's onclose, a failed health-check ping, and a tool call rejected * with the SDK's connection-closed error — so it is idempotent on the * (client, status) pair rather than trusting any one caller to be first. */ private handleConnectionLost; private trackToolCall; /** * Handle server errors */ private handleServerError; /** * Handle server disconnection */ private handleServerDisconnection; /** * Schedule server restart with exponential backoff */ private scheduleRestart; /** * Start health monitoring for a server */ private startHealthMonitoring; /** * Perform a health check by pinging the server over its live transport. * * The previous check read `process.killed` — a flag Node sets only when we * call `kill()` ourselves, on a process handle that was not even the * server's — so it could never observe a server that died on its own. * * A ping that comes back with the SDK's connection-closed error is a * definitive death and is handled immediately. A ping that merely times * out is ambiguous: single-threaded servers (a Python MCP server running a * synchronous tool) cannot answer while a tool call is executing, and * restarting one mid-call would destroy the work in flight. So a timeout * counts toward a restart only while the server has no tool call in * flight, and only after UNRESPONSIVE_CHECKS_BEFORE_RESTART consecutive * misses. */ private performHealthCheck; /** * Get server instance - converted to ExternalMCPServerInstance for compatibility */ getServer(serverId: string): ExternalMCPServerInstance | undefined; /** * Get all servers - converted to ExternalMCPServerInstance for compatibility */ getAllServers(): Map<string, ExternalMCPServerInstance>; /** * List servers as MCPServerInfo - ZERO conversion needed */ listServers(): MCPServerInfo[]; /** * Get server statuses */ getServerStatuses(): ExternalMCPServerHealth[]; /** * Shutdown all servers and clean up resources * This method should be called during application shutdown to prevent memory leaks */ shutdown(): Promise<void>; /** * Destroy the manager and all associated resources * Alias for shutdown() to match the pattern used by other components */ destroy(): Promise<void>; /** * Get manager statistics */ getStatistics(): { totalServers: number; connectedServers: number; failedServers: number; totalTools: number; totalConnections: number; totalErrors: number; }; /** * Discover tools from a server */ private discoverServerTools; /** * Register server tools with main tool registry for unified access * This enables external MCP tools to be accessed via the main toolRegistry.executeTool() */ private registerServerToolsWithMainRegistry; /** * Unregister server tools from main tool registry */ private unregisterServerToolsFromMainRegistry; /** * Execute a tool on a specific server */ executeTool(serverId: string, toolName: string, parameters: JsonObject, options?: { timeout?: number; }): Promise<unknown>; /** * Get all tools from all servers */ getAllTools(): ExternalMCPToolInfo[]; /** * Get tools for a specific server */ getServerTools(serverId: string): ExternalMCPToolInfo[]; /** * Get tool discovery service */ getToolDiscovery(): ToolDiscoveryService; }