haloapi-mcp-tools
Version:
Model Context Protocol (MCP) server for interacting with the HaloPSA API
319 lines (274 loc) • 9.41 kB
JavaScript
/**
* 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();