UNPKG

browser-console-mcp-server

Version:
193 lines (192 loc) 7.05 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { getVersion } from './utils/version.js'; import { z } from 'zod'; import WebSocket from 'ws'; // WebSocket client configuration for connecting to existing server const WS_PORT = parseInt(process.env.WS_PORT || '3001', 10); const WS_URL = `ws://localhost:${WS_PORT}`; /** * Connect to existing WebSocket server and send request with response handling */ async function connectAndSendRequest(request) { return new Promise((resolve, reject) => { const ws = new WebSocket(WS_URL); const timeout = setTimeout(() => { ws.close(); reject(new Error('Request timeout - server did not respond within 10 seconds')); }, 10000); ws.on('open', () => { console.error(`🔌 Connected to server at ${WS_URL}`); // Send the request immediately upon connection ws.send(JSON.stringify(request)); }); ws.on('message', (data) => { try { const response = JSON.parse(data.toString()); clearTimeout(timeout); ws.close(); console.error('🔌 Received response, connection closed'); resolve(response); } catch (error) { clearTimeout(timeout); ws.close(); reject(new Error(`Failed to parse server response: ${error}`)); } }); ws.on('error', (error) => { clearTimeout(timeout); reject(new Error(`WebSocket connection error: ${error.message}`)); }); ws.on('close', (code, reason) => { clearTimeout(timeout); if (code !== 1000) { // 1000 is normal closure reject(new Error(`WebSocket closed unexpectedly: ${code} ${reason.toString()}`)); } }); }); } /** * Send request to extension via WebSocket client connection */ async function sendToExtension(request) { return connectAndSendRequest(request); } /** * Handle get console logs tool */ async function handleGetConsoleLogs(args) { const { tabId, limit = 100 } = args || {}; const response = await sendToExtension({ command: 'getConsoleLogs', tabId, }); if (!response.success) { throw new Error(response.error || 'Failed to get console logs'); } const logs = response.data?.logs || []; const limitedLogs = logs.slice(-limit); // Get the most recent logs const formattedLogs = limitedLogs.map((log) => { const timestamp = new Date(log.timestamp).toISOString(); const args = Array.isArray(log.args) ? log.args.join(' ') : String(log.args); return `[${timestamp}] ${log.level.toUpperCase()}: ${args}`; }).join('\n'); return { content: [ { type: 'text', text: `Console logs from tab ${response.data?.tabId} (${limitedLogs.length} entries):\n\n${formattedLogs || 'No console logs found'}`, }, ], }; } /** * Handle refresh tab tool */ async function handleRefreshTab(args) { const { tabId } = args || {}; const response = await sendToExtension({ command: 'refreshTab', tabId, }); if (!response.success) { throw new Error(response.error || 'Failed to refresh tab'); } return { content: [ { type: 'text', text: `Successfully refreshed tab ${response.data?.tabId}`, }, ], }; } /** * Handle navigate tab tool */ async function handleNavigateTab(args) { const { url, tabId } = args || {}; if (!url) { throw new Error('URL is required for navigation'); } const response = await sendToExtension({ command: 'navigateTab', tabId, url, }); if (!response.success) { throw new Error(response.error || 'Failed to navigate tab'); } return { content: [ { type: 'text', text: `Successfully navigated tab ${response.data?.tabId} to ${response.data?.url}`, }, ], }; } /** * Create and configure the MCP server for browser console access */ export async function createServer() { // Create MCP server with dynamic version from package.json const server = new McpServer({ name: 'browser-console-mcp-server', version: getVersion() }); // Register get_console_logs tool server.tool('get_console_logs', 'Retrieve console logs from the active browser tab or a specific tab. Perfect for debugging web applications, monitoring JavaScript errors, and tracking console output in real-time during development.', { tabId: z.number().optional().describe('Optional tab ID. If not provided, uses the active tab.'), limit: z.number().default(100).describe('Maximum number of logs to return (default: 100)') }, async ({ tabId, limit }) => { try { return await handleGetConsoleLogs({ tabId, limit }); } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }); // Register refresh_tab tool server.tool('refresh_tab', 'Refresh the active browser tab or a specific tab to reload the page content. Useful for testing changes, clearing cached content, or resetting the page state during development.', { tabId: z.number().optional().describe('Optional tab ID. If not provided, refreshes the active tab.') }, async ({ tabId }) => { try { return await handleRefreshTab({ tabId }); } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }); // Register navigate_tab tool server.tool('navigate_tab', 'Navigate the active browser tab or a specific tab to a new URL. Essential for automated testing, switching between development environments, or directing the browser to specific pages during development workflow.', { url: z.string().describe('The URL to navigate to'), tabId: z.number().optional().describe('Optional tab ID. If not provided, navigates the active tab.') }, async ({ url, tabId }) => { try { return await handleNavigateTab({ url, tabId }); } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }); return server; }