2025-08-10 22:40:50 -05:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-10 23:47:18 -05:00
|
|
|
* WebFetch.MCP - Log Monitor Utility v0.1.8
|
2025-08-10 22:40:50 -05:00
|
|
|
* Real-time log monitoring for WebFetch.MCP server
|
|
|
|
|
*
|
|
|
|
|
* This utility monitors the detailed log file generated by the WebFetch.MCP server
|
|
|
|
|
* and displays formatted, real-time log entries for debugging and monitoring.
|
|
|
|
|
*
|
|
|
|
|
* Usage: node monitor-logs.mjs
|
|
|
|
|
*
|
|
|
|
|
* @author Jay Leon (@manull)
|
|
|
|
|
* @license MIT
|
|
|
|
|
* @version 0.1.7
|
|
|
|
|
* @repository https://github.com/manull/webfetch-mcp
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2025 Jay Leon (@manull)
|
|
|
|
|
* Licensed under the MIT License - see LICENSE file for details
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { watchFile, existsSync, readFileSync, statSync } from 'fs';
|
|
|
|
|
import { join, dirname } from 'path';
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
|
const __dirname = dirname(__filename);
|
|
|
|
|
const LOG_FILE = join(__dirname, 'mcp-server-detailed.log');
|
|
|
|
|
|
|
|
|
|
console.log('🔍 MCP Server Log Monitor');
|
|
|
|
|
console.log('========================');
|
|
|
|
|
console.log(`Monitoring: ${LOG_FILE}`);
|
|
|
|
|
console.log('Press Ctrl+C to stop\n');
|
|
|
|
|
|
|
|
|
|
// Check if log file exists
|
|
|
|
|
if (!existsSync(LOG_FILE)) {
|
|
|
|
|
console.log('⏳ Waiting for log file to be created...');
|
|
|
|
|
console.log(' (Start your MCP server to begin logging)\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let lastSize = 0;
|
|
|
|
|
|
|
|
|
|
// Function to read new content from log file
|
|
|
|
|
function readNewContent() {
|
|
|
|
|
if (!existsSync(LOG_FILE)) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const stats = statSync(LOG_FILE);
|
|
|
|
|
const currentSize = stats.size;
|
|
|
|
|
|
|
|
|
|
if (currentSize > lastSize) {
|
|
|
|
|
const content = readFileSync(LOG_FILE, 'utf8');
|
|
|
|
|
const newContent = content.slice(lastSize);
|
|
|
|
|
|
|
|
|
|
// Parse and format the new content
|
|
|
|
|
const lines = newContent.split('\n').filter(line => line.trim());
|
|
|
|
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
try {
|
|
|
|
|
// Try to parse as JSON log entry
|
|
|
|
|
if (line.includes(']: ')) {
|
|
|
|
|
const [timestamp, rest] = line.split(']: ', 2);
|
|
|
|
|
const category = timestamp.split('] ')[1];
|
|
|
|
|
|
|
|
|
|
console.log(`\n🔸 ${category} ${timestamp.split('] ')[0]}]`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const data = JSON.parse(rest);
|
|
|
|
|
console.log(formatLogData(category, data));
|
|
|
|
|
} catch {
|
|
|
|
|
console.log(rest);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.log(line);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastSize = currentSize;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error reading log file:', error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Format log data for better readability
|
|
|
|
|
function formatLogData(category, data) {
|
|
|
|
|
switch (category) {
|
|
|
|
|
case 'SERVER_STARTUP':
|
|
|
|
|
return ` Server: ${data.server_info?.name} v${data.server_info?.version}
|
|
|
|
|
SearxNG: ${data.server_info?.searxng_base}
|
|
|
|
|
Debug: ${data.server_info?.debug_enabled}
|
|
|
|
|
Node: ${data.environment?.node_version}`;
|
|
|
|
|
|
|
|
|
|
case 'INCOMING_REQUEST':
|
|
|
|
|
if (data.method === 'initialize') {
|
|
|
|
|
return ` Initialize request from client: ${data.request?.params?.clientInfo?.name}`;
|
|
|
|
|
} else if (data.method === 'tools/call') {
|
|
|
|
|
return ` Tool call: ${data.tool_name}
|
|
|
|
|
Arguments: ${JSON.stringify(data.arguments, null, 4)}`;
|
|
|
|
|
} else if (data.method === 'tools/list') {
|
|
|
|
|
return ` Tools list requested`;
|
|
|
|
|
}
|
|
|
|
|
return ` Method: ${data.method}`;
|
|
|
|
|
|
|
|
|
|
case 'OUTGOING_RESPONSE':
|
|
|
|
|
if (data.method === 'tools/call') {
|
|
|
|
|
const responseText = data.response?.content?.[0]?.text;
|
|
|
|
|
const preview = responseText ? responseText.substring(0, 200) + (responseText.length > 200 ? '...' : '') : 'No text content';
|
|
|
|
|
return ` Tool response (${data.tool_name}):
|
|
|
|
|
${preview}`;
|
|
|
|
|
} else if (data.method === 'tools/list') {
|
|
|
|
|
const toolNames = data.response?.tools?.map(t => t.name).join(', ') || 'none';
|
|
|
|
|
return ` Available tools: ${toolNames}`;
|
|
|
|
|
}
|
|
|
|
|
return ` Response for: ${data.method}`;
|
|
|
|
|
|
|
|
|
|
case 'ERROR':
|
|
|
|
|
return ` ❌ Tool: ${data.tool_name}
|
|
|
|
|
Error: ${data.error_details?.message}
|
|
|
|
|
Args: ${JSON.stringify(data.error_details?.arguments)}`;
|
|
|
|
|
|
|
|
|
|
case 'SERVER_ERROR':
|
|
|
|
|
return ` ❌ ${data.error?.message}`;
|
|
|
|
|
|
|
|
|
|
case 'SERVER_SHUTDOWN':
|
|
|
|
|
return ` Reason: ${data.reason}`;
|
|
|
|
|
|
|
|
|
|
case 'UNCAUGHT_EXCEPTION':
|
|
|
|
|
case 'UNHANDLED_REJECTION':
|
|
|
|
|
return ` ❌ ${data.error?.message || data.reason}`;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return ` ${JSON.stringify(data, null, 2)}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initial read
|
|
|
|
|
readNewContent();
|
|
|
|
|
|
|
|
|
|
// Watch for file changes
|
|
|
|
|
watchFile(LOG_FILE, { interval: 500 }, () => {
|
|
|
|
|
readNewContent();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle graceful shutdown
|
|
|
|
|
process.on('SIGINT', () => {
|
|
|
|
|
console.log('\n\n👋 Log monitoring stopped');
|
|
|
|
|
process.exit(0);
|
|
|
|
|
});
|