UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

564 lines • 22.1 kB
/** * Handler for granular debug data access tools * Provides explicit tools for console logs, network activity, etc. * that AI agents commonly expect */ export class DebugDataHandler { localEngine; sessions; name = 'debug-data'; // Handler methods mapping handlers = { get_console_logs: this.getConsoleLogs.bind(this), get_network_activity: this.getNetworkActivity.bind(this), get_errors: this.getErrors.bind(this), get_performance_metrics: this.getPerformanceMetrics.bind(this), get_dom_snapshot: this.getDomSnapshot.bind(this), get_local_storage: this.getLocalStorage.bind(this), get_cookies: this.getCookies.bind(this), search_logs: this.searchLogs.bind(this), list_available_debug_data: this.listAvailableDebugData.bind(this) }; // Implement the required handle method from ToolHandler interface async handle(toolName, args, sessions) { const handler = this.handlers[toolName]; if (!handler) { throw new Error(`Unknown debug data tool: ${toolName}`); } return handler(args); } tools = [ { name: 'get_console_logs', description: 'Get console messages from the debugging session. Returns an array of console log entries with timestamp, level (log/warn/error/info), and message text.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, level: { type: 'string', enum: ['all', 'log', 'warn', 'error', 'info'], default: 'all', description: 'Filter by log level' }, limit: { type: 'number', default: 100, description: 'Maximum number of logs to return' } }, required: ['sessionId'] } }, { name: 'get_network_activity', description: 'Get network requests from the debugging session. Returns an array of network requests with URL, method, status, response time, and headers.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, filter: { type: 'string', enum: ['all', 'xhr', 'fetch', 'document', 'script', 'stylesheet', 'image', 'font', 'websocket'], default: 'all', description: 'Filter by request type' }, statusFilter: { type: 'string', enum: ['all', 'success', 'error', 'pending'], default: 'all', description: 'Filter by status code range' } }, required: ['sessionId'] } }, { name: 'get_errors', description: 'Get JavaScript errors and exceptions from the debugging session. Returns an array of error objects with message, stack trace, and source location.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, includeWarnings: { type: 'boolean', default: false, description: 'Include warnings in addition to errors' } }, required: ['sessionId'] } }, { name: 'get_performance_metrics', description: 'Get performance metrics from the debugging session. Returns Core Web Vitals (LCP, FCP, CLS, FID), resource timings, and memory usage.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, detailed: { type: 'boolean', default: false, description: 'Include detailed resource timings' } }, required: ['sessionId'] } }, { name: 'get_dom_snapshot', description: 'Get current DOM tree snapshot from the debugging session. Returns HTML structure, computed styles, and element properties.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, selector: { type: 'string', description: 'CSS selector to get specific element (optional, defaults to full page)' }, includeStyles: { type: 'boolean', default: false, description: 'Include computed styles for elements' } }, required: ['sessionId'] } }, { name: 'get_local_storage', description: 'Get localStorage data from the debugging session. Returns key-value pairs stored in browser localStorage.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, keys: { type: 'array', items: { type: 'string' }, description: 'Specific keys to retrieve (optional, defaults to all)' } }, required: ['sessionId'] } }, { name: 'get_cookies', description: 'Get cookies from the debugging session. Returns array of cookie objects with name, value, domain, path, and expiry.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, domain: { type: 'string', description: 'Filter cookies by domain (optional)' } }, required: ['sessionId'] } }, { name: 'search_logs', description: 'Search through console logs, network activity, and errors for specific patterns. Returns matching entries across all debug data.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, pattern: { type: 'string', description: 'Search pattern (supports regex)' }, searchIn: { type: 'array', items: { type: 'string', enum: ['console', 'network', 'errors'] }, default: ['console', 'network', 'errors'], description: 'Where to search' } }, required: ['sessionId', 'pattern'] } }, { name: 'list_available_debug_data', description: 'List all types of debug data available for the current session. Returns summary of console logs, network requests, errors, and other captured data.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } } ]; constructor(localEngine, sessions) { this.localEngine = localEngine; this.sessions = sessions; } async getConsoleLogs(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const allLogs = await this.localEngine.getConsoleMessages(); let filtered = allLogs; // Filter by level if specified if (args.level && args.level !== 'all') { filtered = allLogs.filter((log) => log.level === args.level); } // Apply limit if (args.limit) { filtered = filtered.slice(-args.limit); } return { content: [ { type: 'text', text: `Retrieved ${filtered.length} console messages${args.level !== 'all' ? ` (${args.level} level)` : ''}:` }, { type: 'text', text: JSON.stringify(filtered, null, 2) } ] }; } async getNetworkActivity(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const allRequests = await this.localEngine.getNetworkRequests(); let filtered = allRequests; // Filter by type if specified if (args.filter && args.filter !== 'all') { filtered = allRequests.filter((req) => { const resourceType = req.resourceType?.toLowerCase() || ''; return resourceType === args.filter; }); } // Filter by status if specified if (args.statusFilter && args.statusFilter !== 'all') { filtered = filtered.filter((req) => { const status = req.status || 0; switch (args.statusFilter) { case 'success': return status >= 200 && status < 300; case 'error': return status >= 400; case 'pending': return status === 0; default: return true; } }); } return { content: [ { type: 'text', text: `Retrieved ${filtered.length} network requests:` }, { type: 'text', text: JSON.stringify(filtered, null, 2) } ] }; } async getErrors(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const consoleLogs = await this.localEngine.getConsoleMessages(); const errors = consoleLogs.filter((log) => log.level === 'error' || (args.includeWarnings && log.level === 'warning')); // Also get failed network requests const failedRequests = await this.localEngine.getFailedRequests(); return { content: [ { type: 'text', text: `Found ${errors.length} console errors${args.includeWarnings ? '/warnings' : ''} and ${failedRequests.length} failed network requests:` }, { type: 'text', text: JSON.stringify({ consoleErrors: errors, networkErrors: failedRequests }, null, 2) } ] }; } async getPerformanceMetrics(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } // Get performance metrics from the page const metrics = await session.page.evaluate(() => { const navigation = performance.getEntriesByType('navigation')[0]; const paint = performance.getEntriesByType('paint'); return { navigation: { domContentLoaded: navigation?.domContentLoadedEventEnd - navigation?.domContentLoadedEventStart, loadComplete: navigation?.loadEventEnd - navigation?.loadEventStart, responseTime: navigation?.responseEnd - navigation?.requestStart }, paint: paint.map(entry => ({ name: entry.name, startTime: entry.startTime })), memory: performance.memory ? { usedJSHeapSize: performance.memory.usedJSHeapSize, totalJSHeapSize: performance.memory.totalJSHeapSize, jsHeapSizeLimit: performance.memory.jsHeapSizeLimit } : null }; }); return { content: [ { type: 'text', text: 'Performance metrics:' }, { type: 'text', text: JSON.stringify(metrics, null, 2) } ] }; } async getDomSnapshot(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const selector = args.selector || 'body'; const snapshot = await session.page.evaluate((sel, includeStyles) => { const element = document.querySelector(sel); if (!element) return null; const result = { tagName: element.tagName, attributes: {}, innerHTML: element.innerHTML.substring(0, 1000) + (element.innerHTML.length > 1000 ? '...' : ''), textContent: element.textContent?.substring(0, 500) }; // Get attributes for (let i = 0; i < element.attributes.length; i++) { const attr = element.attributes[i]; result.attributes[attr.name] = attr.value; } // Get computed styles if requested if (includeStyles) { const styles = window.getComputedStyle(element); result.computedStyles = { display: styles.display, position: styles.position, width: styles.width, height: styles.height, color: styles.color, backgroundColor: styles.backgroundColor, fontSize: styles.fontSize, fontFamily: styles.fontFamily }; } return result; }, selector, args.includeStyles || false); return { content: [ { type: 'text', text: `DOM snapshot for "${selector}":` }, { type: 'text', text: JSON.stringify(snapshot, null, 2) } ] }; } async getLocalStorage(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const storage = await session.page.evaluate((keys) => { const result = {}; if (keys && keys.length > 0) { // Get specific keys for (const key of keys) { result[key] = localStorage.getItem(key); } } else { // Get all keys for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key) { result[key] = localStorage.getItem(key); } } } return result; }, args.keys); return { content: [ { type: 'text', text: `localStorage data (${Object.keys(storage).length} items):` }, { type: 'text', text: JSON.stringify(storage, null, 2) } ] }; } async getCookies(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } let cookies = await session.page.context().cookies(); // Filter by domain if specified if (args.domain) { cookies = cookies.filter((cookie) => cookie.domain === args.domain || cookie.domain === `.${args.domain}`); } return { content: [ { type: 'text', text: `Found ${cookies.length} cookies${args.domain ? ` for domain ${args.domain}` : ''}:` }, { type: 'text', text: JSON.stringify(cookies, null, 2) } ] }; } async searchLogs(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const results = { console: [], network: [], errors: [] }; const regex = new RegExp(args.pattern, 'i'); // Search in console logs if (args.searchIn.includes('console')) { const logs = await this.localEngine.getConsoleMessages(); results.console = logs.filter((log) => regex.test(log.text) || regex.test(JSON.stringify(log))); } // Search in network activity if (args.searchIn.includes('network')) { const requests = await this.localEngine.getNetworkRequests(); results.network = requests.filter((req) => regex.test(req.url) || regex.test(JSON.stringify(req))); } // Search in errors if (args.searchIn.includes('errors')) { const logs = await this.localEngine.getConsoleMessages(); const errors = logs.filter((log) => log.level === 'error'); results.errors = errors.filter((err) => regex.test(err.text) || regex.test(JSON.stringify(err))); } const totalMatches = results.console.length + results.network.length + results.errors.length; return { content: [ { type: 'text', text: `Found ${totalMatches} matches for "${args.pattern}":` }, { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } async listAvailableDebugData(args) { const session = this.sessions.get(args.sessionId); if (!session || !session.page) { throw new Error('Invalid session ID or session expired'); } const consoleLogs = await this.localEngine.getConsoleMessages(); const networkRequests = await this.localEngine.getNetworkRequests(); const errors = consoleLogs.filter((log) => log.level === 'error'); const summary = { consoleLogs: { total: consoleLogs.length, byLevel: { log: consoleLogs.filter((l) => l.level === 'log').length, warn: consoleLogs.filter((l) => l.level === 'warning').length, error: consoleLogs.filter((l) => l.level === 'error').length, info: consoleLogs.filter((l) => l.level === 'info').length } }, networkActivity: { total: networkRequests.length, successful: networkRequests.filter((r) => r.status >= 200 && r.status < 300).length, failed: networkRequests.filter((r) => r.status >= 400).length, pending: networkRequests.filter((r) => !r.status).length }, errors: { total: errors.length, unique: new Set(errors.map((e) => e.text)).size }, availableDataTypes: [ 'console_logs', 'network_activity', 'errors', 'performance_metrics', 'dom_snapshot', 'local_storage', 'cookies' ] }; return { content: [ { type: 'text', text: 'Available debug data summary:' }, { type: 'text', text: JSON.stringify(summary, null, 2) } ] }; } } //# sourceMappingURL=debug-data-handler.js.map