get_console_errors
Identify and retrieve browser console errors during web testing and automation on ARM64 devices using the Chromium-based MCP server, enabling efficient debugging and issue tracking.
Instructions
Get browser console errors
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- index.js:783-787 (handler)The main handler function for the 'get_console_errors' tool. It returns the global consoleErrors array as a formatted JSON string in the MCP response format.async getConsoleErrors() { return { content: [{ type: 'text', text: JSON.stringify(consoleErrors, null, 2) }], }; }
- index.js:239-246 (registration)Registration of the 'get_console_errors' tool in the ListTools response, including its name, description, and empty input schema.{ name: 'get_console_errors', description: 'Get browser console errors', inputSchema: { type: 'object', properties: {}, }, },
- index.js:369-370 (registration)Dispatch case in the CallToolRequestSchema handler that routes calls to the getConsoleErrors method.case 'get_console_errors': return await this.getConsoleErrors();
- index.js:242-245 (schema)Input schema definition for the tool, which requires no parameters.inputSchema: { type: 'object', properties: {}, },
- index.js:21-787 (helper)Global consoleErrors array (line 21) and the code in setupEventListeners (around line 521) that populates it when console.error or console.warning events are received via CDP.let consoleErrors = []; let networkLogs = []; let networkErrors = []; // Helper function to find Chromium executable function getChromiumPath() { const platform = os.platform(); if (platform === 'linux') { // Try common Linux paths const linuxPaths = [ '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable' ]; for (const chromePath of linuxPaths) { if (fs.existsSync(chromePath)) { return chromePath; } } } else if (platform === 'darwin') { // macOS paths const macPaths = [ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium', '/opt/homebrew/bin/chromium' ]; for (const chromePath of macPaths) { if (fs.existsSync(chromePath)) { return chromePath; } } } else if (platform === 'win32') { // Windows paths const winPaths = [ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Chromium\\Application\\chrome.exe' ]; for (const chromePath of winPaths) { if (fs.existsSync(chromePath)) { return chromePath; } } } // Try to find via which command try { const result = execSync('which chromium-browser || which chromium || which google-chrome', { encoding: 'utf8' }).trim(); if (result) { return result.split('\n')[0]; } } catch (error) { // Ignore error, will throw below } throw new Error(`Could not find Chromium browser. Please install it for your platform.`); } class DirectChromiumMCPServer { constructor() { this.server = new Server( { name: 'chromium-arm64-server', version: '1.3.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); this.setupErrorHandling(); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'navigate', description: 'Navigate to a URL', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to navigate to', }, }, required: ['url'], }, }, { name: 'screenshot', description: 'Take a screenshot of the current page', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name for the screenshot file', default: 'screenshot.png', }, fullPage: { type: 'boolean', description: 'Capture full page', default: false, }, }, }, }, { name: 'click', description: 'Click an element on the page', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector for the element to click', }, }, required: ['selector'], }, }, { name: 'fill', description: 'Fill an input field', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector for the input field', }, value: { type: 'string', description: 'Value to fill', }, }, required: ['selector', 'value'], }, }, { name: 'evaluate', description: 'Execute JavaScript in the browser', inputSchema: { type: 'object', properties: { script: { type: 'string', description: 'JavaScript code to execute', }, }, required: ['script'], }, }, { name: 'get_content', description: 'Get page content (HTML or text)', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['html', 'text'], description: 'Type of content to get', default: 'text', }, }, }, }, { name: 'hover', description: 'Hover over an element on the page', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector for the element to hover', }, }, required: ['selector'], }, }, { name: 'select', description: 'Select an option from a dropdown', inputSchema: { type: 'object', properties: { selector: { type: 'string', description: 'CSS selector for the select element', }, value: { type: 'string', description: 'Value to select', }, }, required: ['selector', 'value'], }, }, { name: 'get_console_logs', description: 'Get browser console logs', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_console_errors', description: 'Get browser console errors', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_network_logs', description: 'Get network activity logs', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_network_errors', description: 'Get network error logs', inputSchema: { type: 'object', properties: {}, }, }, { name: 'wipe_logs', description: 'Clear all stored logs from memory', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_selected_element', description: 'Get information about the currently selected element', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_accessibility_audit', description: 'Run an accessibility audit on the current page', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_performance_audit', description: 'Run a performance audit on the current page', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_seo_audit', description: 'Run an SEO audit on the current page', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_best_practices_audit', description: 'Run a best practices audit on the current page', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_nextjs_audit', description: 'Run a Next.js specific audit on the current page', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_debugger_mode', description: 'Run debugger mode to debug issues in the application', inputSchema: { type: 'object', properties: {}, }, }, { name: 'run_audit_mode', description: 'Run comprehensive audit mode for optimization', inputSchema: { type: 'object', properties: {}, }, }, { name: 'close_browser', description: 'Close the browser instance', inputSchema: { type: 'object', properties: {}, }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'navigate': return await this.navigate(args.url); case 'screenshot': return await this.screenshot(args.name || 'screenshot.png', args.fullPage || false); case 'click': return await this.click(args.selector); case 'fill': return await this.fill(args.selector, args.value); case 'evaluate': return await this.evaluate(args.script); case 'get_content': return await this.getContent(args.type || 'text'); case 'hover': return await this.hover(args.selector); case 'select': return await this.select(args.selector, args.value); case 'get_console_logs': return await this.getConsoleLogs(); case 'get_console_errors': return await this.getConsoleErrors(); case 'get_network_logs': return await this.getNetworkLogs(); case 'get_network_errors': return await this.getNetworkErrors(); case 'wipe_logs': return await this.wipeLogs(); case 'get_selected_element': return await this.getSelectedElement(); case 'run_accessibility_audit': return await this.runAccessibilityAudit(); case 'run_performance_audit': return await this.runPerformanceAudit(); case 'run_seo_audit': return await this.runSEOAudit(); case 'run_best_practices_audit': return await this.runBestPracticesAudit(); case 'run_nextjs_audit': return await this.runNextJSAudit(); case 'run_debugger_mode': return await this.runDebuggerMode(); case 'run_audit_mode': return await this.runAuditMode(); case 'close_browser': return await this.closeBrowser(); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } }); } async ensureChromium() { if (!chromiumProcess || chromiumProcess.exitCode !== null) { await this.startChromium(); } if (!wsConnection || wsConnection.readyState !== WebSocket.OPEN) { await this.connectToChromium(); } } async startChromium() { return new Promise((resolve, reject) => { const chromiumPath = getChromiumPath(); chromiumProcess = spawn(chromiumPath, [ '--headless', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-web-security', '--disable-features=VizDisplayCompositor', '--disable-extensions', '--disable-plugins', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', `--remote-debugging-port=${debuggingPort}`, '--no-first-run', '--no-zygote', '--disable-accelerated-2d-canvas', '--window-size=1280,720' ]); chromiumProcess.on('error', reject); // Wait for chromium to start setTimeout(resolve, 2000); }); } async connectToChromium() { // Wait a bit more for chromium to fully start await new Promise(resolve => setTimeout(resolve, 1000)); try { // Get available tabs const response = await this.httpRequest(`http://localhost:${debuggingPort}/json`); const tabs = JSON.parse(response); let wsUrl; // Find a page tab (not extension) const pageTab = tabs.find(tab => tab.type === 'page'); if (pageTab) { currentTabId = pageTab.id; wsUrl = pageTab.webSocketDebuggerUrl; } else { // Create a new tab const newTabResponse = await this.httpRequest(`http://localhost:${debuggingPort}/json/new`); const newTab = JSON.parse(newTabResponse); currentTabId = newTab.id; wsUrl = newTab.webSocketDebuggerUrl; } // Connect to WebSocket return new Promise((resolve, reject) => { wsConnection = new WebSocket(wsUrl); wsConnection.on('open', async () => { await this.setupEventListeners(); resolve(); }); wsConnection.on('error', reject); // Add timeout for connection setTimeout(() => { if (wsConnection.readyState !== WebSocket.OPEN) { reject(new Error('WebSocket connection timeout')); } }, 5000); }); } catch (error) { throw new Error(`Failed to connect to chromium: ${error.message}`); } } async setupEventListeners() { // Enable domains in sequence try { await this.sendCDPCommand('Runtime.enable'); await this.sendCDPCommand('Page.enable'); await this.sendCDPCommand('Network.enable'); await this.sendCDPCommand('DOM.enable'); } catch (error) { console.error('Failed to enable CDP domains:', error.message); } // Set up event listeners for logging (separate from command responses) wsConnection.on('message', (data) => { try { const message = JSON.parse(data.toString()); // Only handle events (methods), not command responses (ids) if (message.method && !message.id) { if (message.method === 'Runtime.consoleAPICalled') { const logEntry = { type: message.params.type, text: message.params.args.map(arg => arg.value || arg.description).join(' '), timestamp: new Date().toISOString() }; consoleLogs.push(logEntry); if (['error', 'warning'].includes(message.params.type)) { consoleErrors.push(logEntry); } // Keep only last 100 entries if (consoleLogs.length > 100) consoleLogs.shift(); if (consoleErrors.length > 100) consoleErrors.shift(); } if (message.method === 'Network.responseReceived') { const logEntry = { url: message.params.response.url, status: message.params.response.status, statusText: message.params.response.statusText, method: message.params.response.requestMethod || 'GET', timestamp: new Date().toISOString() }; networkLogs.push(logEntry); if (message.params.response.status >= 400) { networkErrors.push(logEntry); } // Keep only last 100 entries if (networkLogs.length > 100) networkLogs.shift(); if (networkErrors.length > 100) networkErrors.shift(); } } } catch (e) { // Ignore parse errors } }); } async sendCDPCommand(method, params = {}) { if (!wsConnection || wsConnection.readyState !== WebSocket.OPEN) { throw new Error('WebSocket not ready for CDP command'); } return new Promise((resolve, reject) => { const id = Math.floor(Math.random() * 1000000); const command = { id, method, params }; const timeout = setTimeout(() => { wsConnection.removeListener('message', messageHandler); reject(new Error(`CDP command timeout: ${method}`)); }, 10000); const messageHandler = (data) => { try { const response = JSON.parse(data.toString()); if (response.id === id) { clearTimeout(timeout); wsConnection.removeListener('message', messageHandler); if (response.error) { reject(new Error(`CDP Error: ${response.error.message}`)); } else { resolve(response.result || {}); } } } catch (e) { // Ignore parse errors for events } }; wsConnection.on('message', messageHandler); wsConnection.send(JSON.stringify(command)); }); } async httpRequest(url) { return new Promise((resolve, reject) => { http.get(url, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data)); }).on('error', reject); }); } async navigate(url) { await this.ensureChromium(); await this.sendCDPCommand('Page.navigate', { url }); return { content: [{ type: 'text', text: `Successfully navigated to ${url}` }], }; } async screenshot(name, fullPage) { await this.ensureChromium(); const screenshotParams = { format: 'png' }; if (fullPage) { const metrics = await this.sendCDPCommand('Page.getLayoutMetrics'); screenshotParams.clip = { x: 0, y: 0, width: metrics.contentSize.width, height: metrics.contentSize.height, scale: 1 }; } const result = await this.sendCDPCommand('Page.captureScreenshot', screenshotParams); const screenshotPath = `/tmp/${name}`; fs.writeFileSync(screenshotPath, result.data, 'base64'); return { content: [{ type: 'text', text: `Screenshot saved to ${screenshotPath}` }], }; } async click(selector) { await this.ensureChromium(); // Find element const doc = await this.sendCDPCommand('DOM.getDocument'); const element = await this.sendCDPCommand('DOM.querySelector', { nodeId: doc.root.nodeId, selector }); if (!element.nodeId) { throw new Error(`Element not found: ${selector}`); } // Get element box const box = await this.sendCDPCommand('DOM.getBoxModel', { nodeId: element.nodeId }); const quad = box.model.content; const x = (quad[0] + quad[4]) / 2; const y = (quad[1] + quad[5]) / 2; // Click await this.sendCDPCommand('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }); await this.sendCDPCommand('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 }); return { content: [{ type: 'text', text: `Clicked element: ${selector}` }], }; } async fill(selector, value) { await this.ensureChromium(); await this.click(selector); // Focus element first // Clear and type await this.sendCDPCommand('Input.insertText', { text: value }); return { content: [{ type: 'text', text: `Filled ${selector} with: ${value}` }], }; } async evaluate(script) { await this.ensureChromium(); const result = await this.sendCDPCommand('Runtime.evaluate', { expression: script, returnByValue: true }); return { content: [{ type: 'text', text: `Result: ${JSON.stringify(result.result?.value)}` }], }; } async getContent(type) { await this.ensureChromium(); let content; if (type === 'html') { const doc = await this.sendCDPCommand('DOM.getDocument'); const html = await this.sendCDPCommand('DOM.getOuterHTML', { nodeId: doc.root.nodeId }); content = html.outerHTML; } else { const result = await this.sendCDPCommand('Runtime.evaluate', { expression: 'document.body.innerText', returnByValue: true }); content = result.result?.value || ''; } return { content: [{ type: 'text', text: content }], }; } async hover(selector) { await this.ensureChromium(); const doc = await this.sendCDPCommand('DOM.getDocument'); const element = await this.sendCDPCommand('DOM.querySelector', { nodeId: doc.root.nodeId, selector }); if (!element.nodeId) { throw new Error(`Element not found: ${selector}`); } const box = await this.sendCDPCommand('DOM.getBoxModel', { nodeId: element.nodeId }); const quad = box.model.content; const x = (quad[0] + quad[4]) / 2; const y = (quad[1] + quad[5]) / 2; await this.sendCDPCommand('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }); return { content: [{ type: 'text', text: `Hovered over element: ${selector}` }], }; } async select(selector, value) { await this.ensureChromium(); const result = await this.sendCDPCommand('Runtime.evaluate', { expression: ` const select = document.querySelector('${selector}'); if (select) { select.value = '${value}'; select.dispatchEvent(new Event('change', { bubbles: true })); true; } else { false; } `, returnByValue: true }); if (!result.result?.value) { throw new Error(`Select element not found: ${selector}`); } return { content: [{ type: 'text', text: `Selected '${value}' in ${selector}` }], }; } // Logging methods (same as before) async getConsoleLogs() { return { content: [{ type: 'text', text: JSON.stringify(consoleLogs, null, 2) }], }; } async getConsoleErrors() { return { content: [{ type: 'text', text: JSON.stringify(consoleErrors, null, 2) }], }; }