Skip to main content
Glama
input.ts8.98 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 concise 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} stale/invalid. Call take_snapshot first.`); } return error; } // Tool definitions export const clickByUidTool = { name: 'click_by_uid', description: 'Click element by UID. Set dblClick for double-click.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'Element UID from snapshot', }, dblClick: { type: 'boolean', description: 'Double-click (default: false)', }, }, required: ['uid'], }, }; export const hoverByUidTool = { name: 'hover_by_uid', description: 'Hover over element by UID.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'Element UID from snapshot', }, }, required: ['uid'], }, }; export const fillByUidTool = { name: 'fill_by_uid', description: 'Fill text input/textarea by UID.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'Input element UID from snapshot', }, value: { type: 'string', description: 'Text to fill', }, }, required: ['uid', 'value'], }, }; export const dragByUidToUidTool = { name: 'drag_by_uid_to_uid', description: 'Drag element to another (HTML5 drag events).', inputSchema: { type: 'object', properties: { fromUid: { type: 'string', description: 'Source element UID', }, toUid: { type: 'string', description: 'Target element UID', }, }, required: ['fromUid', 'toUid'], }, }; export const fillFormByUidTool = { name: 'fill_form_by_uid', description: 'Fill multiple form fields at once.', inputSchema: { type: 'object', properties: { elements: { type: 'array', description: 'Array of {uid, value} pairs', items: { type: 'object', properties: { uid: { type: 'string', description: 'Field UID', }, value: { type: 'string', description: 'Field value', }, }, required: ['uid', 'value'], }, }, }, required: ['elements'], }, }; export const uploadFileByUidTool = { name: 'upload_file_by_uid', description: 'Upload file to file input by UID.', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'File input UID from snapshot', }, filePath: { type: 'string', description: 'Local file path', }, }, 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 ? 'dblclick' : 'click'} ${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(`✅ hover ${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(`✅ fill ${uid}`); } 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(`✅ drag ${fromUid}→${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(`UIDs stale/invalid. Call take_snapshot first.`); } 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} fields`); } catch (error) { const errorMsg = (error as Error).message; if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) { throw new Error(`UIDs stale/invalid. Call take_snapshot first.`); } 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(`✅ upload ${uid}`); } 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(`${uid} is not a file input`); } if (errorMsg.includes('hidden') || errorMsg.includes('not visible')) { throw new Error(`${uid} is hidden/not interactable`); } throw error; } } catch (error) { return errorResponse(error as Error); } }

Latest Blog Posts

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