UNPKG

maplestorysea-mcp-server

Version:

NEXON MapleStory SEA API MCP Server for Claude Desktop - Complete character info, union details, guild data, rankings optimized for SEA servers

363 lines 13.5 kB
"use strict"; /** * Health check system for monitoring application status */ Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultHealthManager = exports.HealthCheckManager = exports.ProcessHealthChecker = exports.MemoryHealthChecker = exports.CacheHealthChecker = exports.ApiHealthChecker = exports.HealthChecker = void 0; exports.createDefaultHealthManager = createDefaultHealthManager; const logger_1 = require("./logger"); const cache_1 = require("./cache"); class HealthChecker { } exports.HealthChecker = HealthChecker; class ApiHealthChecker extends HealthChecker { apiClient; name = 'nexon-api'; constructor(apiClient) { super(); this.apiClient = apiClient; } async check(options = {}) { const startTime = Date.now(); const timeout = options.timeout || 5000; // Create a timeout promise with cleanup let timeoutId; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => reject(new Error('Health check timeout')), timeout); }); try { // Check API client basic configuration // This validates the client is properly initialized if (!this.apiClient) { throw new Error('NEXON API client is not initialized'); } // Check environment variable for API key presence const envApiKey = process.env.NEXON_API_KEY; if (!envApiKey) { throw new Error('NEXON_API_KEY environment variable is not set'); } if (envApiKey.length < 50) { throw new Error('NEXON API key appears to be invalid (too short)'); } // Simple timeout to simulate async check await new Promise(resolve => setTimeout(resolve, 50)); // Clear the timeout if the API call succeeded if (timeoutId) { clearTimeout(timeoutId); } const responseTime = Date.now() - startTime; return { status: 'healthy', lastCheck: new Date().toISOString(), responseTime, details: { endpoint: 'configuration_check', apiKeyPresent: true, timeout, }, }; } catch (error) { // Clear the timeout if it exists if (timeoutId) { clearTimeout(timeoutId); } const responseTime = Date.now() - startTime; let errorMessage; if (error instanceof Error) { errorMessage = error.message; // Include more context for NEXON API errors if ('code' in error || 'statusCode' in error) { const extra = []; if ('statusCode' in error) extra.push(`Status: ${error.statusCode}`); if ('code' in error) extra.push(`Code: ${error.code}`); if (extra.length > 0) { errorMessage += ` (${extra.join(', ')})`; } } } else if (error && typeof error === 'object') { // Handle structured error objects if ('error' in error) { const nestedError = error.error; if (nestedError && typeof nestedError === 'object') { errorMessage = nestedError.message || nestedError.name || 'API Error'; if (nestedError.name) { errorMessage = `${nestedError.name}: ${nestedError.message || 'Unknown error'}`; } } else { errorMessage = String(nestedError); } } else { errorMessage = JSON.stringify(error); } } else { errorMessage = String(error); } return { status: 'unhealthy', lastCheck: new Date().toISOString(), responseTime, error: errorMessage, details: { endpoint: 'configuration_check', apiKeyPresent: true, timeout, }, }; } } } exports.ApiHealthChecker = ApiHealthChecker; class CacheHealthChecker extends HealthChecker { cache; name = 'cache'; constructor(cache) { super(); this.cache = cache; } async check() { const startTime = Date.now(); try { // Test cache operations const testKey = 'health-check-test'; const testValue = { timestamp: Date.now() }; // Test set operation this.cache.set(testKey, testValue, 1000); // Test get operation const retrieved = this.cache.get(testKey); if (!retrieved || retrieved.timestamp !== testValue.timestamp) { throw new Error('Cache read/write verification failed'); } // Test delete operation this.cache.delete(testKey); const afterDelete = this.cache.get(testKey); if (afterDelete !== null) { throw new Error('Cache delete verification failed'); } const responseTime = Date.now() - startTime; return { status: 'healthy', lastCheck: new Date().toISOString(), responseTime, details: { operations: ['set', 'get', 'delete'], cacheSize: this.cache.size(), }, }; } catch (error) { const responseTime = Date.now() - startTime; return { status: 'unhealthy', lastCheck: new Date().toISOString(), responseTime, error: error instanceof Error ? error.message : String(error), }; } } } exports.CacheHealthChecker = CacheHealthChecker; class MemoryHealthChecker extends HealthChecker { name = 'memory'; async check() { const startTime = Date.now(); try { const memUsage = process.memoryUsage(); const responseTime = Date.now() - startTime; // Convert bytes to MB const memoryMB = { rss: Math.round(memUsage.rss / 1024 / 1024), heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), external: Math.round(memUsage.external / 1024 / 1024), }; // Determine status based on memory usage const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; let status = 'healthy'; if (heapUsagePercent > 90) { status = 'unhealthy'; } else if (heapUsagePercent > 80) { status = 'degraded'; } return { status, lastCheck: new Date().toISOString(), responseTime, details: { memory: memoryMB, heapUsagePercent: Math.round(heapUsagePercent), }, }; } catch (error) { const responseTime = Date.now() - startTime; return { status: 'unhealthy', lastCheck: new Date().toISOString(), responseTime, error: error instanceof Error ? error.message : String(error), }; } } } exports.MemoryHealthChecker = MemoryHealthChecker; class ProcessHealthChecker extends HealthChecker { name = 'process'; startTime = Date.now(); async check() { const checkStartTime = Date.now(); try { const uptime = process.uptime(); const responseTime = Date.now() - checkStartTime; return { status: 'healthy', lastCheck: new Date().toISOString(), responseTime, details: { uptime: Math.round(uptime), uptimeFormatted: this.formatUptime(uptime), pid: process.pid, nodeVersion: process.version, platform: process.platform, arch: process.arch, }, }; } catch (error) { const responseTime = Date.now() - checkStartTime; return { status: 'unhealthy', lastCheck: new Date().toISOString(), responseTime, error: error instanceof Error ? error.message : String(error), }; } } formatUptime(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); return `${days}d ${hours}h ${minutes}m ${secs}s`; } } exports.ProcessHealthChecker = ProcessHealthChecker; class HealthCheckManager { checkers = new Map(); logger; lastCheck; constructor(logger = logger_1.defaultLogger) { this.logger = logger; } registerChecker(checker) { this.checkers.set(checker.name, checker); this.logger.debug(`Registered health checker: ${checker.name}`); } async runHealthChecks(options = {}) { const startTime = Date.now(); const details = {}; // Run all health checks in parallel const checkPromises = Array.from(this.checkers.entries()).map(async ([name, checker]) => { try { const result = await checker.check(options); details[name] = result; this.logger.logHealthCheck(name, result.status, { responseTime: result.responseTime, error: result.error, }); return result; } catch (error) { const errorResult = { status: 'unhealthy', lastCheck: new Date().toISOString(), error: error instanceof Error ? error.message : String(error), }; details[name] = errorResult; this.logger.logHealthCheck(name, 'unhealthy', { error: errorResult.error }); return errorResult; } }); await Promise.all(checkPromises); // Determine overall status const statuses = Object.values(details).map((d) => d.status); let overallStatus = 'healthy'; if (statuses.includes('unhealthy')) { overallStatus = 'unhealthy'; } else if (statuses.includes('degraded')) { overallStatus = 'degraded'; } const healthStatus = { status: overallStatus, timestamp: new Date().toISOString(), uptime: process.uptime(), version: process.env.npm_package_version || '1.0.0', details, }; this.lastCheck = healthStatus; const checkDuration = Date.now() - startTime; this.logger.info(`Health check completed in ${checkDuration}ms`, { operation: 'health_check_summary', overallStatus, checkDuration, checkerCount: this.checkers.size, }); return healthStatus; } getLastCheck() { return this.lastCheck; } async getQuickStatus() { return { status: this.lastCheck?.status || 'unknown', uptime: process.uptime(), timestamp: new Date().toISOString(), }; } // Start periodic health checks startPeriodicChecks(interval = 60000) { // Default 1 minute this.logger.info(`Starting periodic health checks every ${interval}ms`); return setInterval(async () => { try { await this.runHealthChecks({ timeout: 5000 }); } catch (error) { this.logger.error('Periodic health check failed', { error: error instanceof Error ? error.message : String(error), }); } }, interval); } } exports.HealthCheckManager = HealthCheckManager; // Create default health check manager with all checkers function createDefaultHealthManager(apiClient, cache) { const manager = new HealthCheckManager(); // Always add process and memory checkers manager.registerChecker(new ProcessHealthChecker()); manager.registerChecker(new MemoryHealthChecker()); // Add cache checker if available if (cache) { manager.registerChecker(new CacheHealthChecker(cache)); } else { manager.registerChecker(new CacheHealthChecker(cache_1.defaultCache)); } // Add API checker if available if (apiClient) { manager.registerChecker(new ApiHealthChecker(apiClient)); } return manager; } // Export default health manager exports.defaultHealthManager = createDefaultHealthManager(); //# sourceMappingURL=health-check.js.map