UNPKG

leshan-mcp-server

Version:

A standards-compliant MCP server for Leshan LwM2M, exposing Leshan as Model Context Protocol tools.

164 lines (140 loc) 4.58 kB
import { get } from "./leshanClient.js"; import logger from "./loggerConfig.js"; import { validateEnvironment } from "../config/env.js"; // Get environment configuration const env = validateEnvironment(); class HealthChecker { constructor() { this.lastHealthCheck = null; this.isHealthy = false; this.healthHistory = []; this.maxHistorySize = 100; } /** * Perform comprehensive health check * @returns {Promise<Object>} Health status */ async checkHealth() { const checkId = `health-${Date.now()}`; const startTime = Date.now(); try { logger.debug("Starting health check", { checkId }); // Test Leshan server connectivity const clients = await get("/clients"); const responseTime = Date.now() - startTime; const healthStatus = { healthy: true, timestamp: new Date().toISOString(), checkId, responseTime, leshanServer: { accessible: true, url: env.LESHAN_URL, responseTime, clientCount: Array.isArray(clients) ? clients.length : 0 }, system: { uptime: process.uptime(), memoryUsage: process.memoryUsage(), nodeVersion: process.version, pid: process.pid } }; this.isHealthy = true; this.lastHealthCheck = new Date(); this.addToHistory(healthStatus); logger.info("Health check passed", { checkId, responseTime, clientCount: healthStatus.leshanServer.clientCount }); return healthStatus; } catch (error) { const responseTime = Date.now() - startTime; const healthStatus = { healthy: false, timestamp: new Date().toISOString(), checkId, responseTime, error: { message: error.message, type: error.constructor.name, ...(error.statusCode && { statusCode: error.statusCode }) }, leshanServer: { accessible: false, url: env.LESHAN_URL, responseTime }, system: { uptime: process.uptime(), memoryUsage: process.memoryUsage(), nodeVersion: process.version, pid: process.pid } }; this.isHealthy = false; this.lastHealthCheck = new Date(); this.addToHistory(healthStatus); logger.error("Health check failed", { checkId, error: error.message, responseTime }); return healthStatus; } } /** * Get current health status without performing new check * @returns {Object} Current status */ getStatus() { return { healthy: this.isHealthy, lastCheck: this.lastHealthCheck, uptime: process.uptime(), memoryUsage: process.memoryUsage(), healthHistory: this.healthHistory.slice(-10) // Last 10 checks }; } /** * Add health check result to history * @param {Object} healthStatus - Health check result */ addToHistory(healthStatus) { this.healthHistory.push({ timestamp: healthStatus.timestamp, healthy: healthStatus.healthy, responseTime: healthStatus.responseTime, ...(healthStatus.error && { error: healthStatus.error.message }) }); // Keep only recent history if (this.healthHistory.length > this.maxHistorySize) { this.healthHistory = this.healthHistory.slice(-this.maxHistorySize); } } /** * Get health statistics * @returns {Object} Health statistics */ getHealthStats() { if (this.healthHistory.length === 0) { return { noData: true }; } const recentChecks = this.healthHistory.slice(-20); const successfulChecks = recentChecks.filter(check => check.healthy); const failedChecks = recentChecks.filter(check => !check.healthy); const avgResponseTime = recentChecks.reduce((sum, check) => sum + check.responseTime, 0) / recentChecks.length; return { totalChecks: this.healthHistory.length, recentChecks: recentChecks.length, successRate: (successfulChecks.length / recentChecks.length) * 100, failureRate: (failedChecks.length / recentChecks.length) * 100, averageResponseTime: Math.round(avgResponseTime), lastSuccessful: successfulChecks.length > 0 ? successfulChecks[successfulChecks.length - 1].timestamp : null, lastFailure: failedChecks.length > 0 ? failedChecks[failedChecks.length - 1].timestamp : null }; } } const healthChecker = new HealthChecker(); export default healthChecker;