Skip to main content
Glama

firefox-devtools-mcp

input.ts10.9 kB
/** * UID-based input interaction tools * Require valid UIDs from take_snapshot */ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import type { McpToolResponse } from '../types/common.js'; /** * Transform UID resolution errors into friendly messages */ function handleUidError(error: Error, uid: string): Error { const errorMsg = error.message; if ( errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID') || errorMsg.includes('not found') ) { return new Error( `UID "${uid}" is stale or invalid.\n\n` + 'The page may have changed since the snapshot was taken.\n' + 'Please call take_snapshot to get fresh UIDs and try again.' ); } return error; } // Tool definitions export const clickByUidTool = { name: 'click_by_uid', description: 'Click an element identified by its UID. Supports double-click via dblClick=true. TIP: Take a fresh snapshot if the UID becomes stale.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'The UID of the element to click', }, dblClick: { type: 'boolean', description: 'If true, performs a double-click (default: false)', }, }, required: ['uid'], }, }; export const hoverByUidTool = { name: 'hover_by_uid', description: 'Hover over an element identified by its UID. TIP: Take a fresh snapshot if the UID becomes stale.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'The UID of the element to hover over', }, }, required: ['uid'], }, }; export const fillByUidTool = { name: 'fill_by_uid', description: 'Fill a text input or textarea identified by its UID. Keep values short and safe. TIP: Take a fresh snapshot if the UID becomes stale.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'The UID of the input element', }, value: { type: 'string', description: 'The text value to fill into the input', }, }, required: ['uid', 'value'], }, }; export const dragByUidToUidTool = { name: 'drag_by_uid_to_uid', description: 'Simulate HTML5 drag-and-drop from one UID to another using JS drag events. May not work with all custom libraries.', inputSchema: { type: 'object', properties: { fromUid: { type: 'string', description: 'The UID of the element to drag', }, toUid: { type: 'string', description: 'The UID of the target element to drop onto', }, }, required: ['fromUid', 'toUid'], }, }; export const fillFormByUidTool = { name: 'fill_form_by_uid', description: 'Fill multiple form fields in one call using an array of {uid, value} pairs.', inputSchema: { type: 'object', properties: { elements: { type: 'array', description: 'Array of form field UIDs with their values', items: { type: 'object', properties: { uid: { type: 'string', description: 'The UID of the form field', }, value: { type: 'string', description: 'The value to fill', }, }, required: ['uid', 'value'], }, }, }, required: ['elements'], }, }; export const uploadFileByUidTool = { name: 'upload_file_by_uid', description: 'Upload a file into an <input type="file"> element identified by its UID. The file path must be accessible to the server.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'The UID of the file input element', }, filePath: { type: 'string', description: 'Local filesystem path to the file to upload', }, }, required: ['uid', 'filePath'], }, }; // Handlers export async function handleClickByUid(args: unknown): Promise<McpToolResponse> { try { const { uid, dblClick } = args as { uid: string; dblClick?: boolean }; if (!uid || typeof uid !== 'string') { throw new Error('uid parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.clickByUid(uid, dblClick); return successResponse( `✅ ${dblClick ? 'Double-clicked' : 'Clicked'} element with UID "${uid}"` ); } catch (error) { throw handleUidError(error as Error, uid); } } catch (error) { return errorResponse(error as Error); } } export async function handleHoverByUid(args: unknown): Promise<McpToolResponse> { try { const { uid } = args as { uid: string }; if (!uid || typeof uid !== 'string') { throw new Error('uid parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.hoverByUid(uid); return successResponse(`✅ Hovered over element with UID "${uid}"`); } catch (error) { throw handleUidError(error as Error, uid); } } catch (error) { return errorResponse(error as Error); } } export async function handleFillByUid(args: unknown): Promise<McpToolResponse> { try { const { uid, value } = args as { uid: string; value: string }; if (!uid || typeof uid !== 'string') { throw new Error('uid parameter is required and must be a string'); } if (value === undefined || typeof value !== 'string') { throw new Error('value parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.fillByUid(uid, value); return successResponse( `✅ Filled element with UID "${uid}"\nValue: ${value.substring(0, 50)}${value.length > 50 ? '...' : ''}` ); } catch (error) { throw handleUidError(error as Error, uid); } } catch (error) { return errorResponse(error as Error); } } export async function handleDragByUidToUid(args: unknown): Promise<McpToolResponse> { try { const { fromUid, toUid } = args as { fromUid: string; toUid: string }; if (!fromUid || typeof fromUid !== 'string') { throw new Error('fromUid parameter is required and must be a string'); } if (!toUid || typeof toUid !== 'string') { throw new Error('toUid parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.dragByUidToUid(fromUid, toUid); return successResponse(`✅ Dragged element "${fromUid}" to "${toUid}"`); } catch (error) { // Check both UIDs for staleness const errorMsg = (error as Error).message; if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) { throw new Error( `One or both UIDs (from: "${fromUid}", to: "${toUid}") are stale or invalid.\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 error; } } catch (error) { return errorResponse(error as Error); } } export async function handleFillFormByUid(args: unknown): Promise<McpToolResponse> { try { const { elements } = args as { elements: Array<{ uid: string; value: string }> }; if (!elements || !Array.isArray(elements) || elements.length === 0) { throw new Error('elements parameter is required and must be a non-empty array'); } // Validate all elements for (const el of elements) { if (!el.uid || typeof el.uid !== 'string') { throw new Error(`Invalid element: uid is required and must be a string`); } if (el.value === undefined || typeof el.value !== 'string') { throw new Error(`Invalid element for uid "${el.uid}": value must be a string`); } } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.fillFormByUid(elements); return successResponse( `✅ Filled ${elements.length} form field(s):\n` + elements .map( (el) => ` - ${el.uid}: ${el.value.substring(0, 30)}${el.value.length > 30 ? '...' : ''}` ) .join('\n') ); } catch (error) { const errorMsg = (error as Error).message; if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) { throw new Error( `One or more UIDs are stale or invalid.\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 error; } } catch (error) { return errorResponse(error as Error); } } export async function handleUploadFileByUid(args: unknown): Promise<McpToolResponse> { try { const { uid, filePath } = args as { uid: string; filePath: string }; if (!uid || typeof uid !== 'string') { throw new Error('uid parameter is required and must be a string'); } if (!filePath || typeof filePath !== 'string') { throw new Error('filePath parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); try { await firefox.uploadFileByUid(uid, filePath); return successResponse(`✅ Uploaded file to element with UID "${uid}"\nFile: ${filePath}`); } catch (error) { const errorMsg = (error as Error).message; // Check for UID staleness if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) { throw handleUidError(error as Error, uid); } // Check for file input specific errors if (errorMsg.includes('not a file input') || errorMsg.includes('type="file"')) { throw new Error( `Element with UID "${uid}" is not an <input type="file"> element.\n\n` + 'Please ensure the UID points to a file input element.' ); } if (errorMsg.includes('hidden') || errorMsg.includes('not visible')) { throw new Error( `File input element with UID "${uid}" is hidden or not interactable.\n\n` + 'Some file inputs are hidden and cannot be directly interacted with.' ); } throw error; } } catch (error) { 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