browser-console-mcp-server
Version:
MCP server for browser console access and tab management
193 lines (192 loc) • 7.05 kB
JavaScript
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;
}