@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
TypeScript
/**
* 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;
}