UNPKG

reactbits-mcp-tools

Version:

Model Context Protocol server for ReactBits component library with comprehensive TypeScript build system and real data integration

415 lines (411 loc) 14.3 kB
#!/usr/bin/env node /** * Health Check and Monitoring System for ReactBits MCP Server * * Provides comprehensive health monitoring, metrics collection, * and observability endpoints for production deployments. */ import * as fs from 'fs/promises'; import * as path from 'path'; import { performance } from 'perf_hooks'; // ============================================================================ // Health Check System // ============================================================================ export class HealthCheckSystem { checks = new Map(); lastResults = new Map(); healthStatus = 'healthy'; constructor() { this.registerDefaultChecks(); } /** * Register a health check function */ registerCheck(name, checkFn) { this.checks.set(name, checkFn); } /** * Run all health checks */ async runAllChecks() { const results = []; for (const [name, checkFn] of this.checks) { try { const startTime = performance.now(); const result = await checkFn(); const duration = performance.now() - startTime; const healthResult = { name, status: result.healthy ? 'pass' : 'fail', message: result.message, timestamp: Date.now(), duration: Math.round(duration), ...(result.details !== undefined && { details: result.details }) }; results.push(healthResult); this.lastResults.set(name, healthResult); } catch (error) { const errorResult = { name, status: 'fail', message: `Health check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, timestamp: Date.now(), duration: 0, details: { error: String(error) } }; results.push(errorResult); this.lastResults.set(name, errorResult); } } // Update overall health status this.updateOverallHealthStatus(results); return results; } /** * Get current health status */ getHealthStatus() { return this.healthStatus; } /** * Get last health check results */ getLastResults() { return Array.from(this.lastResults.values()); } /** * Register default health checks */ registerDefaultChecks() { // Memory usage check this.registerCheck('memory_usage', async () => { const usage = process.memoryUsage(); const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024); const heapTotalMB = Math.round(usage.heapTotal / 1024 / 1024); const rssUsedMB = Math.round(usage.rss / 1024 / 1024); const isHealthy = heapUsedMB < 400; // Alert if using more than 400MB return { healthy: isHealthy, message: isHealthy ? `Memory usage normal: ${heapUsedMB}MB heap, ${rssUsedMB}MB RSS` : `High memory usage: ${heapUsedMB}MB heap, ${rssUsedMB}MB RSS`, details: { heapUsed: heapUsedMB, heapTotal: heapTotalMB, rss: rssUsedMB, external: Math.round(usage.external / 1024 / 1024) } }; }); // File system check this.registerCheck('filesystem', async () => { try { const testPath = path.join(process.cwd(), 'tmp-health-check'); await fs.writeFile(testPath, 'health-check'); await fs.unlink(testPath); return { healthy: true, message: 'File system access working normally', details: { testPath } }; } catch (error) { return { healthy: false, message: `File system access failed: ${error instanceof Error ? error.message : 'Unknown error'}`, details: { error: String(error) } }; } }); // Data directory check this.registerCheck('data_directory', async () => { try { const extractionPath = path.resolve(process.cwd(), 'production-react-bits-extraction'); const indexPath = path.join(extractionPath, 'component-index.json'); await fs.access(extractionPath); const indexData = await fs.readFile(indexPath, 'utf-8'); const components = JSON.parse(indexData); const isHealthy = Array.isArray(components) && components.length > 0; return { healthy: isHealthy, message: isHealthy ? `Data directory accessible with ${components.length} components` : 'Data directory exists but no components found', details: { path: extractionPath, componentCount: components.length } }; } catch (error) { return { healthy: false, message: `Data directory check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, details: { error: String(error) } }; } }); // Process uptime check this.registerCheck('uptime', async () => { const uptimeSeconds = process.uptime(); const uptimeMinutes = Math.round(uptimeSeconds / 60); return { healthy: true, message: `Server running for ${uptimeMinutes} minutes`, details: { uptimeSeconds: Math.round(uptimeSeconds), uptimeMinutes, uptimeHours: Math.round(uptimeSeconds / 3600) } }; }); // Node.js version check this.registerCheck('nodejs_version', async () => { const version = process.version; const majorVersion = parseInt(version.substring(1).split('.')[0]); const isSupported = majorVersion >= 18; return { healthy: isSupported, message: isSupported ? `Node.js version ${version} is supported` : `Node.js version ${version} is not supported (requires >=18)`, details: { version, majorVersion, platform: process.platform, arch: process.arch } }; }); } /** * Update overall health status based on check results */ updateOverallHealthStatus(results) { const failedChecks = results.filter(r => r.status === 'fail'); const totalChecks = results.length; if (failedChecks.length === 0) { this.healthStatus = 'healthy'; } else if (failedChecks.length / totalChecks < 0.5) { this.healthStatus = 'degraded'; } else { this.healthStatus = 'unhealthy'; } } } // ============================================================================ // Metrics Collection System // ============================================================================ export class MetricsCollector { metrics = new Map(); maxMetricAge; cleanupInterval; constructor(maxMetricAge = 300000) { this.maxMetricAge = maxMetricAge; // Clean up old metrics every minute this.cleanupInterval = setInterval(() => { this.cleanupOldMetrics(); }, 60000); } /** * Record a metric value */ record(metric, value, labels = {}) { const metricValue = { value, timestamp: Date.now(), labels }; if (!this.metrics.has(metric)) { this.metrics.set(metric, []); } this.metrics.get(metric).push(metricValue); } /** * Record operation duration */ recordDuration(operation, startTime, labels = {}) { const duration = performance.now() - startTime; this.record(`${operation}_duration`, duration, labels); } /** * Increment a counter */ increment(counter, labels = {}) { this.record(counter, 1, labels); } /** * Get metrics summary */ getSummary() { const summary = { timestamp: Date.now(), metrics: {} }; for (const [name, values] of this.metrics) { if (values.length === 0) continue; const numericValues = values.map(v => v.value); const sum = numericValues.reduce((a, b) => a + b, 0); const avg = sum / numericValues.length; const min = Math.min(...numericValues); const max = Math.max(...numericValues); summary.metrics[name] = { count: values.length, sum, avg: Math.round(avg * 100) / 100, min, max, latest: values[values.length - 1].value }; } return summary; } /** * Get raw metrics data */ getMetrics(metric) { if (metric) { return { [metric]: this.metrics.get(metric) || [] }; } return Object.fromEntries(this.metrics); } /** * Clean up old metrics */ cleanupOldMetrics() { const cutoffTime = Date.now() - this.maxMetricAge; for (const [name, values] of this.metrics) { const filteredValues = values.filter(v => v.timestamp >= cutoffTime); this.metrics.set(name, filteredValues); } } /** * Clean up resources */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } } } // ============================================================================ // Monitoring Configuration // ============================================================================ export class MonitoringConfig { /** * Create Prometheus configuration */ static generatePrometheusConfig() { return ` global: scrape_interval: 15s evaluation_interval: 15s rule_files: # - "first_rules.yml" # - "second_rules.yml" scrape_configs: - job_name: 'reactbits-mcp-server' static_configs: - targets: ['localhost:3000'] scrape_interval: 30s metrics_path: '/metrics' - job_name: 'node-exporter' static_configs: - targets: ['localhost:9100'] scrape_interval: 30s alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 `.trim(); } /** * Create Grafana dashboard configuration */ static generateGrafanaDashboard() { return { dashboard: { id: null, title: "ReactBits MCP Server", tags: ["mcp", "reactbits", "monitoring"], timezone: "browser", panels: [ { id: 1, title: "Health Status", type: "stat", targets: [ { expr: "reactbits_health_status", refId: "A" } ], fieldConfig: { defaults: { color: { mode: "thresholds" }, thresholds: { steps: [ { color: "green", value: null }, { color: "yellow", value: 1 }, { color: "red", value: 2 } ] } } } }, { id: 2, title: "Request Rate", type: "graph", targets: [ { expr: "rate(reactbits_requests_total[5m])", refId: "A" } ] }, { id: 3, title: "Response Time", type: "graph", targets: [ { expr: "reactbits_request_duration_seconds", refId: "A" } ] }, { id: 4, title: "Memory Usage", type: "graph", targets: [ { expr: "reactbits_memory_usage_bytes", refId: "A" } ] } ], time: { from: "now-1h", to: "now" }, refresh: "30s" } }; } } // ============================================================================ // Default Export // ============================================================================ export const defaultHealthCheck = new HealthCheckSystem(); export const defaultMetrics = new MetricsCollector(); //# sourceMappingURL=health.js.map