Skip to main content
Glama

firefox-devtools-mcp

script.ts5.68 kB
/** * JavaScript evaluation tool (currently disabled - see docs/future-features.md) */ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import type { McpToolResponse } from '../types/common.js'; export const evaluateScriptTool = { name: 'evaluate_script', description: 'Execute a JavaScript function in the selected tab and return its JSON-serializable result. Use with caution; prefer snapshot+UID tools for interactions. This tool may be disabled by default.', inputSchema: { type: 'object', properties: { function: { type: 'string', description: `A JavaScript function to run in the currently selected page. Example without arguments: \`() => { return document.title }\` or \`async () => { return await fetch("example.com") }\`. Example with arguments: \`(el) => { return el.innerText; }\``, }, args: { type: 'array', description: 'An optional list of arguments to pass to the function (UIDs from snapshot).', items: { type: 'object', properties: { uid: { type: 'string', description: 'The uid of an element on the page from the page content snapshot', }, }, required: ['uid'], }, }, timeout: { type: 'number', description: 'Optional timeout in milliseconds for script execution (default: 5000). Protection against infinite loops.', }, }, required: ['function'], }, }; // Constants const MAX_FUNCTION_SIZE = 16 * 1024; // 16 KB const DEFAULT_TIMEOUT = 5000; // 5 seconds /** * Validate function string format */ function validateFunction(fnString: string): void { if (!fnString || typeof fnString !== 'string') { throw new Error('function parameter is required and must be a string'); } if (fnString.length > MAX_FUNCTION_SIZE) { throw new Error( `Function too large (${fnString.length} bytes, max ${MAX_FUNCTION_SIZE} bytes). ` + 'This tool is not designed for massive scripts.' ); } // Check if it looks like a function or arrow function const trimmed = fnString.trim(); const isFunctionLike = trimmed.startsWith('function') || trimmed.startsWith('async function') || trimmed.startsWith('(') || trimmed.startsWith('async ('); if (!isFunctionLike) { throw new Error( `Invalid function format. Expected a function or arrow function, got: "${trimmed.substring(0, 50)}...".\n\n` + 'Valid examples:\n' + ' () => document.title\n' + ' async () => { return await fetch("/api") }\n' + ' (el) => el.innerText\n' + ' function() { return window.location.href }' ); } } export async function handleEvaluateScript(args: unknown): Promise<McpToolResponse> { try { const { function: fnString, args: fnArgs, timeout, } = args as { function: string; args?: Array<{ uid: string }>; timeout?: number; }; // Validate function validateFunction(fnString); const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); const driver = firefox.getDriver(); if (!driver) { throw new Error('WebDriver not available'); } const scriptTimeout = timeout ?? DEFAULT_TIMEOUT; // Prepare arguments: resolve UIDs to WebElements if provided const resolvedArgs: unknown[] = []; if (fnArgs && fnArgs.length > 0) { for (const arg of fnArgs) { try { const element = await firefox.resolveUidToElement(arg.uid); resolvedArgs.push(element); } catch (error) { const errorMsg = (error as Error).message; // Provide friendly error for stale UIDs if ( errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID') ) { throw new Error( `UID "${arg.uid}" is invalid or from an old snapshot.\n\n` + 'The page may have changed since the snapshot was taken.\n' + 'Please call take_snapshot to get fresh UIDs and try again.' ); } throw new Error(`Failed to resolve UID "${arg.uid}": ${errorMsg}`); } } } // Unified execution path: use executeScript with optional args const evalCode = ` const fn = ${fnString}; const args = Array.from(arguments); const result = fn(...args); return result instanceof Promise ? result : Promise.resolve(result); `; // Set script timeout await driver.manage().setTimeouts({ script: scriptTimeout }); // Execute with resolved args (empty array if no args) const result = await driver.executeScript(evalCode, ...resolvedArgs); // Format output let output = 'Script ran on page and returned:\n'; output += '```json\n'; output += JSON.stringify(result, null, 2); output += '\n```'; return successResponse(output); } catch (error) { const errorMsg = (error as Error).message; // Enhance timeout errors if (errorMsg.includes('timeout') || errorMsg.includes('Timeout')) { const timeoutValue = (args as { timeout?: number })?.timeout ?? DEFAULT_TIMEOUT; return errorResponse( new Error( `Script execution timed out (exceeded ${timeoutValue}ms).\n\n` + 'The function may contain an infinite loop or be waiting for a slow operation.\n' + 'Try simplifying the script or increasing the timeout parameter.' ) ); } return errorResponse(error as Error); } }

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