Skip to main content
Glama
health-monitor.cjs•9.04 kB
#!/usr/bin/env node // Health Monitor for EGW Writings MCP Server // Monitors coldstart, heartbeat, and system health const { SimpleEGWDatabase } = require('./database-utils.js'); class HealthMonitor { constructor() { this.isRunning = false; this.monitorInterval = 30000; // 30 seconds this.healthHistory = []; } async startMonitoring() { if (this.isRunning) { console.log('šŸ„ Health monitoring already running'); return; } console.log('šŸ„ Starting EGW Writings Health Monitor...'); this.isRunning = true; // Initial health check await this.performHealthCheck(); // Set up periodic monitoring this.monitorTimer = setInterval(async () => { await this.performHealthCheck(); }, this.monitorInterval); console.log(`āœ… Health monitor started (checking every ${this.monitorInterval / 1000}s)`); // Handle graceful shutdown process.on('SIGINT', () => this.stopMonitoring()); process.on('SIGTERM', () => this.stopMonitoring()); } async performHealthCheck() { try { const health = await SimpleEGWDatabase.performHealthCheck(); const timestamp = new Date().toISOString(); // Analyze health status const analysis = this.analyzeHealth(health); console.log(`\nšŸ„ Health Check - ${timestamp}`); console.log('=' .repeat(60)); // Database status console.log(`šŸ“Š Database: ${health.database.isWarm ? 'āœ… WARM' : 'āŒ COLD'}`); if (health.database.lastWarmup) { const warmupAge = Date.now() - new Date(health.database.lastWarmup).getTime(); console.log(` Last warmup: ${Math.round(warmupAge / 1000)}s ago`); } // Heartbeat status console.log(`šŸ’“ Heartbeat: ${health.heartbeat.isActive ? 'āœ… ACTIVE' : 'āŒ INACTIVE'}`); if (health.heartbeat.lastHeartbeat) { const heartbeatAge = Date.now() - new Date(health.heartbeat.lastHeartbeat).getTime(); const status = heartbeatAge < 60000 ? 'āœ… FRESH' : 'āš ļø STALE'; console.log(` Last heartbeat: ${Math.round(heartbeatAge / 1000)}s ago (${status})`); } // Persistent heartbeat status if (health.heartbeat.persistent) { if (health.heartbeat.persistent.isValid) { console.log(` Persistent: āœ… VALID (${Math.round(health.heartbeat.persistent.age / 1000)}s old)`); } else { console.log(` Persistent: āŒ INVALID/EXPIRED`); } } // Connection pool status if (health.connectionPool) { console.log(`šŸ”— Connections: ${health.connectionPool.size}/${health.connectionPool.size + health.connectionPool.waiting} total`); if (health.connectionPool.waiting > 0) { console.log(` āš ļø ${health.connectionPool.waiting} requests waiting`); } } // Overall system status console.log(`šŸŽÆ Overall: ${analysis.overall}`); if (analysis.issues.length > 0) { console.log('āš ļø Issues detected:'); analysis.issues.forEach(issue => console.log(` • ${issue}`)); } if (analysis.recommendations.length > 0) { console.log('šŸ’” Recommendations:'); analysis.recommendations.forEach(rec => console.log(` • ${rec}`)); } console.log('=' .repeat(60)); // Store in history this.healthHistory.push({ timestamp, health, analysis }); // Keep only last 10 checks if (this.healthHistory.length > 10) { this.healthHistory.shift(); } } catch (error) { console.error('āŒ Health check failed:', error.message); } } analyzeHealth(health) { const analysis = { overall: 'āœ… HEALTHY', issues: [], recommendations: [] }; // Check database warm status if (!health.database.isWarm) { analysis.issues.push('Database is cold - warming may be needed'); analysis.recommendations.push('Run database warmup manually'); analysis.overall = 'āš ļø DEGRADED'; } // Check heartbeat status if (!health.heartbeat.isActive) { analysis.issues.push('No active heartbeat detected'); analysis.recommendations.push('Start heartbeat process'); analysis.overall = 'āŒ UNHEALTHY'; } else if (health.heartbeat.lastHeartbeat) { const heartbeatAge = Date.now() - new Date(health.heartbeat.lastHeartbeat).getTime(); if (heartbeatAge > 60000) { // More than 1 minute analysis.issues.push('Heartbeat is stale (>60s)'); analysis.recommendations.push('Restart heartbeat process'); analysis.overall = 'āš ļø DEGRADED'; } } // Check persistent heartbeat if (health.heartbeat.persistent && !health.heartbeat.persistent.isValid) { analysis.issues.push('Persistent heartbeat is invalid'); analysis.recommendations.push('Clear persistent heartbeat file'); } // Check connection pool if (health.connectionPool && health.connectionPool.waiting > 3) { analysis.issues.push('Connection pool backlog detected'); analysis.recommendations.push('Monitor database load'); analysis.overall = 'āš ļø DEGRADED'; } return analysis; } async stopMonitoring() { if (!this.isRunning) { console.log('šŸ„ Health monitor not running'); return; } console.log('\nšŸ›‘ Stopping health monitor...'); if (this.monitorTimer) { clearInterval(this.monitorTimer); this.monitorTimer = null; } this.isRunning = false; // Generate summary report this.generateSummaryReport(); console.log('āœ… Health monitor stopped'); process.exit(0); } generateSummaryReport() { if (this.healthHistory.length === 0) { console.log('šŸ“Š No health data collected'); return; } console.log('\nšŸ“Š Health Monitor Summary Report'); console.log('=' .repeat(50)); const healthyCount = this.healthHistory.filter(h => h.analysis.overall.includes('HEALTHY')).length; const degradedCount = this.healthHistory.filter(h => h.analysis.overall.includes('DEGRADED')).length; const unhealthyCount = this.healthHistory.filter(h => h.analysis.overall.includes('UNHEALTHY')).length; console.log(`Total checks: ${this.healthHistory.length}`); console.log(`āœ… Healthy: ${healthyCount} (${(healthyCount / this.healthHistory.length * 100).toFixed(1)}%)`); console.log(`āš ļø Degraded: ${degradedCount} (${(degradedCount / this.healthHistory.length * 100).toFixed(1)}%)`); console.log(`āŒ Unhealthy: ${unhealthyCount} (${(unhealthyCount / this.healthHistory.length * 100).toFixed(1)}%)`); // Most common issues const allIssues = this.healthHistory.flatMap(h => h.analysis.issues); const issueCounts = {}; allIssues.forEach(issue => { issueCounts[issue] = (issueCounts[issue] || 0) + 1; }); const sortedIssues = Object.entries(issueCounts) .sort(([,a], [,b]) => b - a) .slice(0, 5); if (sortedIssues.length > 0) { console.log('\nšŸ” Most Common Issues:'); sortedIssues.forEach(([issue, count]) => { console.log(` • ${issue} (${count} times)`); }); } } async showStatus() { const health = await SimpleEGWDatabase.performHealthCheck(); const analysis = this.analyzeHealth(health); console.log('\nšŸ„ Current System Status:'); console.log(`Overall: ${analysis.overall}`); if (analysis.issues.length > 0) { console.log('\nIssues:'); analysis.issues.forEach(issue => console.log(` • ${issue}`)); } if (analysis.recommendations.length > 0) { console.log('\nRecommendations:'); analysis.recommendations.forEach(rec => console.log(` • ${rec}`)); } } } // CLI interface async function main() { const command = process.argv[2]; const monitor = new HealthMonitor(); switch (command) { case 'start': await monitor.startMonitoring(); break; case 'check': await monitor.showStatus(); break; case 'once': await monitor.performHealthCheck(); break; default: console.log(` EGW Writings Health Monitor Usage: node health-monitor.cjs <command> Commands: start Start continuous health monitoring check Show current system status once Perform single health check Examples: node health-monitor.cjs start node health-monitor.cjs check node health-monitor.cjs once The health monitor tracks: • Database warm status and warmup times • Heartbeat activity and persistence • Connection pool utilization • Overall system health and recommendations • Historical trends and issue patterns `); } } if (require.main === module) { main().catch(error => { console.error('āŒ Health monitor error:', error.message); process.exit(1); }); } module.exports = { HealthMonitor };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pythondev-pro/egw_writings_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server