Skip to main content
Glama

firefox-devtools-mcp

index.ts6.86 kB
#!/usr/bin/env node /** * Firefox DevTools MCP Server * Model Context Protocol server for Firefox browser automation via WebDriver BiDi */ // Load .env file in development mode if (process.env.NODE_ENV !== 'production') { try { const { config } = await import('dotenv'); const result = config(); if (result.parsed) { console.error('📋 Loaded .env file for development'); } } catch (error) { // dotenv not required in production } } import { version } from 'node:process'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, CallToolRequest, } from '@modelcontextprotocol/sdk/types.js'; import { SERVER_NAME, SERVER_VERSION } from './config/constants.js'; import { log, logError, logDebug } from './utils/logger.js'; import { parseArguments } from './cli.js'; import { FirefoxDevTools } from './firefox/index.js'; import type { FirefoxLaunchOptions } from './firefox/types.js'; import * as tools from './tools/index.js'; // Export for direct usage in scripts export { FirefoxDevTools } from './firefox/index.js'; // Validate Node.js version const [major] = version.substring(1).split('.').map(Number); if (!major || major < 20) { console.error(`Node ${version} is not supported. Please use Node.js >=20.`); process.exit(1); } // Parse CLI arguments export const args = parseArguments(SERVER_VERSION); // Global context (lazy initialized on first tool call) let firefox: FirefoxDevTools | null = null; export async function getFirefox(): Promise<FirefoxDevTools> { if (!firefox) { log('Initializing Firefox DevTools connection...'); const options: FirefoxLaunchOptions = { firefoxPath: args.firefoxPath ?? undefined, headless: args.headless, profilePath: args.profilePath ?? undefined, viewport: args.viewport ?? undefined, args: (args.firefoxArg as string[] | undefined) ?? undefined, startUrl: args.startUrl ?? undefined, }; firefox = new FirefoxDevTools(options); await firefox.connect(); log('Firefox DevTools connection established'); } return firefox; } // Tool handler mapping const toolHandlers = new Map< string, (input: unknown) => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> >([ // Pages ['list_pages', tools.handleListPages], ['new_page', tools.handleNewPage], ['navigate_page', tools.handleNavigatePage], ['select_page', tools.handleSelectPage], ['close_page', tools.handleClosePage], // Script evaluation - DISABLED (see docs/future-features.md) // ['evaluate_script', tools.handleEvaluateScript], // Console ['list_console_messages', tools.handleListConsoleMessages], ['clear_console_messages', tools.handleClearConsoleMessages], // Network ['list_network_requests', tools.handleListNetworkRequests], ['get_network_request', tools.handleGetNetworkRequest], // Snapshot ['take_snapshot', tools.handleTakeSnapshot], ['resolve_uid_to_selector', tools.handleResolveUidToSelector], ['clear_snapshot', tools.handleClearSnapshot], // Input ['click_by_uid', tools.handleClickByUid], ['hover_by_uid', tools.handleHoverByUid], ['fill_by_uid', tools.handleFillByUid], ['drag_by_uid_to_uid', tools.handleDragByUidToUid], ['fill_form_by_uid', tools.handleFillFormByUid], ['upload_file_by_uid', tools.handleUploadFileByUid], // Screenshot ['screenshot_page', tools.handleScreenshotPage], ['screenshot_by_uid', tools.handleScreenshotByUid], // Utilities ['accept_dialog', tools.handleAcceptDialog], ['dismiss_dialog', tools.handleDismissDialog], ['navigate_history', tools.handleNavigateHistory], ['set_viewport_size', tools.handleSetViewportSize], ]); // All tool definitions const allTools = [ // Pages tools.listPagesTool, tools.newPageTool, tools.navigatePageTool, tools.selectPageTool, tools.closePageTool, // Script evaluation - DISABLED (see docs/future-features.md) // tools.evaluateScriptTool, // Console tools.listConsoleMessagesTool, tools.clearConsoleMessagesTool, // Network tools.listNetworkRequestsTool, tools.getNetworkRequestTool, // Snapshot tools.takeSnapshotTool, tools.resolveUidToSelectorTool, tools.clearSnapshotTool, // Input tools.clickByUidTool, tools.hoverByUidTool, tools.fillByUidTool, tools.dragByUidToUidTool, tools.fillFormByUidTool, tools.uploadFileByUidTool, // Screenshot tools.screenshotPageTool, tools.screenshotByUidTool, // Utilities tools.acceptDialogTool, tools.dismissDialogTool, tools.navigateHistoryTool, tools.setViewportSizeTool, ]; async function main() { log(`Starting ${SERVER_NAME} v${SERVER_VERSION}`); log(`Node.js ${version}`); // Log configuration logDebug(`Configuration:`); logDebug(` Headless: ${args.headless}`); if (args.firefoxPath) { logDebug(` Firefox Path: ${args.firefoxPath}`); } if (args.viewport) { logDebug(` Viewport: ${args.viewport.width}x${args.viewport.height}`); } const server = new Server( { name: SERVER_NAME, version: SERVER_VERSION, }, { capabilities: { resources: {}, tools: {}, }, } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { log('Listing available tools'); return { tools: allTools, }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { const { name, arguments: args } = request.params; log(`Executing tool: ${name}`); const handler = toolHandlers.get(name); if (!handler) { throw new Error(`Unknown tool: ${name}`); } try { return await handler(args); } catch (error) { logError(`Error executing tool ${name}`, error); throw error; } }); // List resources (not implemented for this server) server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [] }; }); // Read resource (not implemented for this server) server.setRequestHandler(ReadResourceRequestSchema, async () => { throw new Error('Resource reading not implemented'); }); const transport = new StdioServerTransport(); await server.connect(transport); log('Firefox DevTools MCP server running on stdio'); log('Ready to accept tool requests'); } // Only run main() if this file is executed directly (not imported) // In ES modules, check if import.meta.url matches the executed file if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error) => { logError('Fatal error in main', error); process.exit(1); }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freema/firefox-devtools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server