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
JavaScript
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;