UNPKG

browser-connect-mcp

Version:

MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance

399 lines 15.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BackendLogsStreamV2ArgsSchema = void 0; exports.backendLogsStreamV2 = backendLogsStreamV2; const zod_1 = require("zod"); const child_process_1 = require("child_process"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const logger_js_1 = __importDefault(require("../../utils/logger.js")); const backend_manager_js_1 = require("../../utils/backend-manager.js"); // Enhanced schema with better defaults exports.BackendLogsStreamV2ArgsSchema = zod_1.z.object({ source: zod_1.z.enum(['auto', 'file', 'process', 'docker', 'start']).default('auto'), target: zod_1.z.string().optional(), // Optional - we'll auto-detect command: zod_1.z.string().optional(), // For 'start' source cwd: zod_1.z.string().optional(), filters: zod_1.z.object({ level: zod_1.z.array(zod_1.z.enum(['error', 'warn', 'info', 'debug', 'trace'])).optional(), pattern: zod_1.z.string().optional(), regex: zod_1.z.boolean().default(false), since: zod_1.z.string().optional(), limit: zod_1.z.number().default(1000) }).optional(), follow: zod_1.z.boolean().default(true), // Default to following format: zod_1.z.enum(['json', 'text', 'auto']).default('auto') }); // Helper to find processes async function findBackendProcesses() { return new Promise((resolve) => { const processes = []; // Use ps to find Node.js processes const ps = (0, child_process_1.spawn)('ps', ['aux']); let output = ''; ps.stdout.on('data', (data) => { output += data.toString(); }); ps.on('close', () => { const lines = output.split('\n'); const nodeProcesses = lines.filter(line => line.includes('node') && !line.includes('node_modules') && !line.includes('browser-connect-mcp')); for (const line of nodeProcesses) { const parts = line.split(/\s+/); if (parts.length > 10) { processes.push({ pid: parseInt(parts[1]), name: parts[10], cmd: parts.slice(10).join(' ') }); } } resolve(processes); }); }); } // Helper to find process by port async function findProcessByPort(port) { return new Promise((resolve) => { const lsof = (0, child_process_1.spawn)('lsof', ['-i', `:${port}`, '-n', '-P']); let output = ''; lsof.stdout.on('data', (data) => { output += data.toString(); }); lsof.on('close', () => { const lines = output.split('\n'); for (const line of lines) { if (line.includes('LISTEN') && line.includes('node')) { const parts = line.split(/\s+/); resolve({ pid: parseInt(parts[1]), name: parts[0], port: port, cmd: `node process on port ${port}` }); return; } } resolve(null); }); }); } // Auto-detect log sources async function detectLogSources(targetHint) { const sources = []; const cwd = process.cwd(); // 1. Check for log files const commonLogPaths = [ 'server.log', 'app.log', 'error.log', 'debug.log', 'logs/app.log', 'logs/error.log', 'var/log/app.log', '.logs/app.log' ]; // If target hint looks like a path, check it first if (targetHint && (targetHint.includes('/') || targetHint.includes('.log'))) { if (fs.existsSync(targetHint)) { sources.push({ type: 'file', target: targetHint, description: `Log file: ${targetHint}` }); } } // Check common log locations for (const logPath of commonLogPaths) { const fullPath = path.join(cwd, logPath); if (fs.existsSync(fullPath)) { sources.push({ type: 'file', target: fullPath, description: `Log file: ${logPath}` }); } } // 2. Check for running processes if (targetHint && /^\d{4,5}$/.test(targetHint)) { // Port number provided const process = await findProcessByPort(parseInt(targetHint)); if (process) { sources.push({ type: 'process', target: process.pid.toString(), description: `Process on port ${targetHint} (PID: ${process.pid})` }); } } // Check for Node.js processes const processes = await findBackendProcesses(); for (const proc of processes) { sources.push({ type: 'process', target: proc.pid.toString(), description: `${proc.name} (PID: ${proc.pid})` }); } // 3. Check for Docker containers try { const docker = (0, child_process_1.spawn)('docker', ['ps', '--format', '{{.Names}}']); let containers = ''; docker.stdout.on('data', (data) => { containers += data.toString(); }); await new Promise(resolve => docker.on('close', resolve)); if (containers) { const containerList = containers.trim().split('\n'); for (const container of containerList) { if (container) { sources.push({ type: 'docker', target: container, description: `Docker container: ${container}` }); } } } } catch (error) { // Docker not available } return sources; } // Start a new process with debugging async function startProcessWithDebugging(command, cwd) { const logs = []; // Parse command to add debugging flags let [cmd, ...args] = command.split(' '); // Add debugging flag for Node.js if (cmd === 'node' && !args.includes('--inspect')) { args.unshift('--inspect'); } // Handle npm/yarn commands if (cmd === 'npm' && args[0] === 'start') { // Try to get the actual command from package.json try { const packageJson = JSON.parse(fs.readFileSync(path.join(cwd || process.cwd(), 'package.json'), 'utf-8')); if (packageJson.scripts?.start) { // Recursively handle the start script return startProcessWithDebugging(packageJson.scripts.start, cwd); } } catch (error) { // Continue with npm start } } const proc = (0, child_process_1.spawn)(cmd, args, { cwd: cwd || process.cwd(), env: { ...process.env, NODE_ENV: process.env.NODE_ENV || 'development' } }); // Capture stdout proc.stdout.on('data', (data) => { const lines = data.toString().split('\n').filter(line => line.trim()); for (const line of lines) { logs.push({ timestamp: new Date().toISOString(), level: 'info', message: line, source: 'stdout', raw: line }); } }); // Capture stderr proc.stderr.on('data', (data) => { const lines = data.toString().split('\n').filter(line => line.trim()); for (const line of lines) { // Check if it's actually an error or just debug output const isError = line.toLowerCase().includes('error') || line.toLowerCase().includes('exception'); logs.push({ timestamp: new Date().toISOString(), level: isError ? 'error' : 'debug', message: line, source: 'stderr', raw: line }); } }); return { process: proc, logs }; } // Main function with enhanced capabilities async function backendLogsStreamV2(args) { const backendManager = backend_manager_js_1.BackendManager.getInstance(); try { // Auto-detect source if needed if (args.source === 'auto') { const sources = await detectLogSources(args.target); if (sources.length === 0) { // No sources found, provide guidance return { success: false, error: 'No log sources found', suggestions: [ 'Start your backend with: node --inspect server.js', 'Or ensure your app writes logs to a file', 'Or run your app in a Docker container' ], help: 'Would you like me to start your backend with debugging enabled?' }; } // Use the first source (or best match based on target hint) const source = sources[0]; args.source = source.type; args.target = source.target; logger_js_1.default.info('Auto-detected log source', { type: source.type, target: source.target, description: source.description }); } // Handle the start source if (args.source === 'start') { if (!args.command) { // Try to detect start command const packageJsonPath = path.join(process.cwd(), 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); if (packageJson.scripts?.start) { args.command = 'npm start'; } else if (packageJson.main) { args.command = `node ${packageJson.main}`; } } if (!args.command) { // Look for common entry points const entryPoints = ['server.js', 'app.js', 'index.js', 'src/index.js']; for (const entry of entryPoints) { if (fs.existsSync(entry)) { args.command = `node ${entry}`; break; } } } if (!args.command) { return { success: false, error: 'Could not determine start command', suggestions: [ 'Specify the command: { "command": "node server.js" }', 'Or add a start script to package.json' ] }; } } // Start the process const { process, logs } = await startProcessWithDebugging(args.command, args.cwd); return { success: true, source: 'start', command: args.command, pid: process.pid, message: `Started process with PID ${process.pid}`, logs: logs.slice(0, args.filters?.limit || 1000), streaming: args.follow, tip: 'Process is running with debugging enabled. Logs will be captured in real-time.' }; } // Handle other sources (file, process, docker) let logs = []; switch (args.source) { case 'file': if (!args.target) { return { success: false, error: 'File path required for file source' }; } logs = await streamFileLog(args.target, args.filters, args.format, args.follow); break; case 'process': if (!args.target) { return { success: false, error: 'Process ID or port required for process source' }; } logs = await streamProcessLog(args.target, args.filters, args.follow); break; case 'docker': if (!args.target) { return { success: false, error: 'Container name required for docker source' }; } logs = await streamDockerLog(args.target, args.filters, args.follow); break; } // Store and filter logs if (args.target) { backendManager.addLogs(args.target, logs); } if (args.filters) { logs = filterLogs(logs, args.filters); } return { success: true, source: args.source, target: args.target, logCount: logs.length, logs: logs.slice(0, args.filters?.limit || 1000), streaming: args.follow }; } catch (error) { logger_js_1.default.error('Failed to stream logs', { error }); return { isError: true, error: error instanceof Error ? error.message : 'Failed to stream logs', suggestions: [ 'Check if the target file/process exists', 'Ensure you have necessary permissions', 'Try using the "start" source to launch with debugging' ] }; } } // ... (Include the existing helper functions from the original file) // streamFileLog, streamProcessLog, streamDockerLog, parseLogLine, filterLogs, etc. //# sourceMappingURL=logs-stream-v2.js.map