UNPKG

haloapi-mcp-tools

Version:

Model Context Protocol (MCP) server for interacting with the HaloPSA API

319 lines (274 loc) 9.41 kB
#!/usr/bin/env node /** * MCP Server Log Monitor * * This script monitors the MCP server logs for JSON parsing errors and other issues. * It provides real-time alerts and statistics on log events. * * Usage: * node monitor-logs.js [options] * * Options: * --log-file <path> Path to log file (default: ~/Library/Logs/Claude/mcp-server-halopsa.log) * --error-only Only show error messages * --watch Watch file for changes (default: true) * --interval <ms> Check interval in milliseconds (default: 1000) * --stats Show statistics (default: true) * --help Show this help message * * MIT License Copyright (c) 2025 sulemanji.com MCP Team */ const fs = require('fs'); const path = require('path'); const os = require('os'); const readline = require('readline'); // ANSI color codes for formatting const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m', bgBlue: '\x1b[44m' }; // Default options const defaultOptions = { logFile: path.join(os.homedir(), 'Library', 'Logs', 'Claude', 'mcp-server-halopsa.log'), errorOnly: false, watch: true, interval: 1000, stats: true }; // Parse command line arguments function parseArgs() { const args = process.argv.slice(2); const options = { ...defaultOptions }; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--log-file' && i + 1 < args.length) { options.logFile = args[++i]; } else if (arg === '--error-only') { options.errorOnly = true; } else if (arg === '--no-watch') { options.watch = false; } else if (arg === '--interval' && i + 1 < args.length) { options.interval = parseInt(args[++i], 10); } else if (arg === '--no-stats') { options.stats = false; } else if (arg === '--help') { showHelp(); process.exit(0); } } return options; } // Show help message function showHelp() { console.log(`${colors.bright}MCP Server Log Monitor${colors.reset} This script monitors the MCP server logs for JSON parsing errors and other issues. It provides real-time alerts and statistics on log events. ${colors.bright}Usage:${colors.reset} node monitor-logs.js [options] ${colors.bright}Options:${colors.reset} --log-file <path> Path to log file (default: ~/Library/Logs/Claude/mcp-server-halopsa.log) --error-only Only show error messages --no-watch Don't watch file for changes --interval <ms> Check interval in milliseconds (default: 1000) --no-stats Don't show statistics --help Show this help message `); } // Statistics tracking const stats = { total: 0, info: 0, error: 0, jsonParseErrors: 0, zodErrors: 0, otherErrors: 0, startTime: new Date(), lastReset: new Date() }; // Get elapsed time as string function getElapsedTime() { const now = new Date(); const elapsed = Math.floor((now - stats.startTime) / 1000); const hours = Math.floor(elapsed / 3600); const minutes = Math.floor((elapsed % 3600) / 60); const seconds = elapsed % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } // Print statistics function printStats() { console.clear(); console.log(`${colors.bright}${colors.blue}===== MCP Server Log Monitor =====\n${colors.reset}`); console.log(`${colors.cyan}Monitoring:${colors.reset} ${options.logFile}`); console.log(`${colors.cyan}Runtime:${colors.reset} ${getElapsedTime()} (since ${stats.startTime.toLocaleTimeString()})\n`); console.log(`${colors.bright}Statistics:${colors.reset}`); console.log(`${colors.cyan}Total messages:${colors.reset} ${stats.total}`); console.log(`${colors.cyan}Info messages:${colors.reset} ${stats.info}`); console.log(`${colors.cyan}Error messages:${colors.reset} ${stats.error} (${stats.error ? (stats.error / stats.total * 100).toFixed(2) : 0}%)`); console.log(`${colors.cyan}JSON parse errors:${colors.reset} ${stats.jsonParseErrors}`); console.log(`${colors.cyan}Zod validation errors:${colors.reset} ${stats.zodErrors}`); console.log(`${colors.cyan}Other errors:${colors.reset} ${stats.otherErrors}\n`); // Show alert if JSON parsing errors present if (stats.jsonParseErrors > 0 || stats.zodErrors > 0) { console.log(`${colors.bgRed}${colors.white} ALERT: ${stats.jsonParseErrors + stats.zodErrors} JSON/Zod errors detected! ${colors.reset}\n`); } else { console.log(`${colors.bgGreen}${colors.white} STATUS: No JSON/Zod errors detected. Looking good! ${colors.reset}\n`); } console.log(`${colors.dim}Press Ctrl+C to exit. Statistics update every ${options.interval}ms.${colors.reset}`); } // Process a log line function processLogLine(line) { stats.total++; // Extract log level and message let isError = false; let isJsonError = false; let isZodError = false; if (line.includes('[error]')) { isError = true; stats.error++; if (line.includes('JSON') && line.includes('parse')) { isJsonError = true; stats.jsonParseErrors++; } else if (line.includes('ZodError') || line.includes('invalid_union')) { isZodError = true; stats.zodErrors++; } else { stats.otherErrors++; } } else if (line.includes('[info]')) { stats.info++; } // Print the line if it's an error or if we're not in error-only mode if ((!options.errorOnly || isError) && !options.stats) { if (isJsonError) { console.log(`${colors.red}${colors.bright}[JSON ERROR] ${colors.reset}${line}`); } else if (isZodError) { console.log(`${colors.magenta}${colors.bright}[ZOD ERROR] ${colors.reset}${line}`); } else if (isError) { console.log(`${colors.red}[ERROR] ${colors.reset}${line}`); } else { console.log(line); } } return { isError, isJsonError, isZodError }; } // Process log file function processLogFile() { if (!fs.existsSync(options.logFile)) { console.error(`${colors.red}Error: Log file not found at ${options.logFile}${colors.reset}`); process.exit(1); } const fileStream = fs.createReadStream(options.logFile); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); rl.on('line', (line) => { processLogLine(line); }); rl.on('close', () => { if (!options.watch) { if (options.stats) { printStats(); } process.exit(0); } }); } // Watch log file for changes function watchLogFile() { let lastSize = 0; try { const stats = fs.statSync(options.logFile); lastSize = stats.size; } catch (error) { console.error(`${colors.red}Error: Could not stat log file: ${error.message}${colors.reset}`); return; } // Initial processing processLogFile(); // Set up interval for checking file changes setInterval(() => { try { const stats = fs.statSync(options.logFile); if (stats.size > lastSize) { // File has grown, read the new lines const fileStream = fs.createReadStream(options.logFile, { start: lastSize, end: stats.size }); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); rl.on('line', (line) => { const result = processLogLine(line); // Print important errors immediately even in stats mode if (options.stats && (result.isJsonError || result.isZodError)) { console.log(`\n${colors.red}${colors.bright}[NEW ERROR] ${colors.reset}${line}`); setTimeout(printStats, 100); // Refresh stats after showing error } }); rl.on('close', () => { lastSize = stats.size; if (options.stats) { printStats(); } }); } else if (stats.size < lastSize) { // File was truncated, reset lastSize = stats.size; stats.total = 0; stats.info = 0; stats.error = 0; stats.jsonParseErrors = 0; stats.zodErrors = 0; stats.otherErrors = 0; stats.lastReset = new Date(); if (options.stats) { printStats(); } // Re-process from the beginning processLogFile(); } else if (options.stats) { // File didn't change, but refresh stats if needed printStats(); } } catch (error) { console.error(`${colors.red}Error reading log file: ${error.message}${colors.reset}`); } }, options.interval); } // Main function function main() { try { if (options.stats) { printStats(); } else { console.log(`${colors.blue}Monitoring MCP server log: ${options.logFile}${colors.reset}`); } if (options.watch) { watchLogFile(); } else { processLogFile(); } } catch (error) { console.error(`${colors.red}Error: ${error.message}${colors.reset}`); process.exit(1); } } // Parse command line options const options = parseArgs(); // Run the main function main();