UNPKG

@toolprint/hypertool-mcp

Version:

HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers

233 lines • 7.31 kB
/** * Health monitoring system for MCP server connections */ import { EventEmitter } from "events"; /** * Server health states */ export var HealthState; (function (HealthState) { HealthState["HEALTHY"] = "healthy"; HealthState["UNHEALTHY"] = "unhealthy"; HealthState["FAILED"] = "failed"; })(HealthState || (HealthState = {})); /** * Health monitor for tracking server availability */ export class HealthMonitor extends EventEmitter { config; connections = new Map(); healthResults = new Map(); checkTimer; isRunning = false; constructor(config = {}) { super(); this.config = { checkInterval: 30000, // 30 seconds ...config, }; } /** * Add a connection to monitor */ addConnection(connection) { this.connections.set(connection.serverName, connection); // Initialize health result const isConnected = connection.isConnected(); this.healthResults.set(connection.serverName, { serverId: connection.id, serverName: connection.serverName, state: isConnected ? HealthState.HEALTHY : HealthState.UNHEALTHY, timestamp: new Date(), lastHealthyAt: isConnected ? new Date() : undefined, }); // Listen for connection state changes connection.on("connected", () => { this.updateHealthStatus(connection.serverName, HealthState.HEALTHY); }); connection.on("disconnected", () => { this.updateHealthStatus(connection.serverName, HealthState.UNHEALTHY); }); connection.on("failed", (event) => { this.updateHealthStatus(connection.serverName, HealthState.FAILED, event.error); }); } /** * Remove a connection from monitoring */ removeConnection(serverName) { this.connections.delete(serverName); this.healthResults.delete(serverName); } /** * Start health monitoring */ start() { if (this.isRunning) { return; } this.isRunning = true; this.scheduleHealthChecks(); } /** * Stop health monitoring */ stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = undefined; } } /** * Get health status for a specific server */ getHealthStatus(serverName) { return this.healthResults.get(serverName); } /** * Get health status for all servers */ getAllHealthStatus() { return new Map(this.healthResults); } /** * Get list of healthy servers */ getHealthyServers() { return Array.from(this.healthResults.entries()) .filter(([, result]) => result.state === HealthState.HEALTHY) .map(([serverName]) => serverName); } /** * Get list of unhealthy servers */ getUnhealthyServers() { return Array.from(this.healthResults.entries()) .filter(([, result]) => result.state === HealthState.UNHEALTHY) .map(([serverName]) => serverName); } /** * Get list of failed servers */ getFailedServers() { return Array.from(this.healthResults.entries()) .filter(([, result]) => result.state === HealthState.FAILED) .map(([serverName]) => serverName); } /** * Perform health check on all connections */ async performHealthCheck() { const results = new Map(); const checkPromises = Array.from(this.connections.entries()).map(async ([serverName, connection]) => { const result = await this.checkConnectionHealth(connection); results.set(serverName, result); return result; }); await Promise.allSettled(checkPromises); return results; } /** * Check health of a specific connection */ async checkConnectionHealth(connection) { const previousResult = this.healthResults.get(connection.serverName); try { // Perform basic connectivity check const isConnected = connection.isConnected(); // Perform ping check if connected let pingResult = false; if (isConnected) { pingResult = await connection.ping(); } const healthy = isConnected && pingResult; let state; if (healthy) { state = HealthState.HEALTHY; } else if (isConnected) { state = HealthState.UNHEALTHY; } else { state = HealthState.FAILED; } const result = { serverId: connection.id, serverName: connection.serverName, state, timestamp: new Date(), lastHealthyAt: healthy ? new Date() : previousResult?.lastHealthyAt, }; this.processHealthResult(result, previousResult); return result; } catch (error) { const result = { serverId: connection.id, serverName: connection.serverName, state: HealthState.FAILED, timestamp: new Date(), error: error, lastHealthyAt: previousResult?.lastHealthyAt, }; this.processHealthResult(result, previousResult); return result; } } /** * Process health check result and emit appropriate events */ processHealthResult(result, previousResult) { // Update stored result this.healthResults.set(result.serverName, result); // Emit state change event only if state actually changed const previousState = previousResult?.state; if (previousState !== result.state) { this.emit("stateChange", result, previousState); } } /** * Update health status based on connection events */ updateHealthStatus(serverName, state, error) { const previousResult = this.healthResults.get(serverName); const result = { serverId: previousResult?.serverId || "", serverName, state, timestamp: new Date(), error, lastHealthyAt: state === HealthState.HEALTHY ? new Date() : previousResult?.lastHealthyAt, }; this.processHealthResult(result, previousResult); } /** * Schedule periodic health checks */ scheduleHealthChecks() { if (this.checkTimer) { clearInterval(this.checkTimer); } this.checkTimer = setInterval(async () => { if (this.isRunning) { await this.performHealthCheck(); } }, this.config.checkInterval); } on(event, listener) { return super.on(event, listener); } off(event, listener) { return super.off(event, listener); } emit(event, ...args) { return super.emit(event, ...args); } } //# sourceMappingURL=healthMonitor.js.map