@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
233 lines • 7.31 kB
JavaScript
/**
* 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