find_element
Locate any element on a webpage using visible text, ARIA role, label, CSS selector, or XPath. Returns a unique CSS selector and element details for interaction.
Instructions
Find an element by visible text, ARIA role, ARIA label, CSS selector, or XPath. Returns a unique CSS selector and element details. Use this when you need to locate an element but don't know its exact selector. Role matching supports both explicit role="..." attributes AND semantic HTML (e.g., matches role "button", matches role "link"). When multiple filters are provided (e.g., role + text), they are combined with AND logic.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| text | No | Find by visible text content (substring match). Can be combined with role for precise targeting. | |
| role | No | Find by ARIA role (e.g., "button", "link", "navigation", "main"). Falls back to semantic HTML roles if no explicit role attribute is present. | |
| ariaLabel | No | Find by aria-label attribute (substring match) | |
| css | No | CSS selector (most direct — bypasses all other filters) | |
| xpath | No | XPath expression (bypasses all other filters) | |
| nth | No | Select the Nth match (0-indexed), default: 0 | |
| tabId | No | Target tab ID (defaults to currently active tab) | |
| apiKey | No | API key for authentication if enabled |
Implementation Reference
- src/tools/elements.ts:20-33 (handler)Handler function for the find_element tool. It sends a 'find_element' command via the WebSocket bridge with params (text, role, ariaLabel, css, xpath, nth) and returns the result.
async ({ text, role, ariaLabel, css, xpath, nth, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'find_element', params: { text, role, ariaLabel, css, xpath, nth }, tabId, apiKey, timeout: LONG_TIMEOUT, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } ); - src/tools/elements.ts:10-19 (schema)Zod schema defining the input parameters for the find_element tool: text, role, ariaLabel, css, xpath (all optional strings), nth (optional number), tabId (optional number), apiKey (optional string).
{ text: z.string().optional().describe('Find by visible text content (substring match). Can be combined with role for precise targeting.'), role: z.string().optional().describe('Find by ARIA role (e.g., "button", "link", "navigation", "main"). Falls back to semantic HTML roles if no explicit role attribute is present.'), ariaLabel: z.string().optional().describe('Find by aria-label attribute (substring match)'), css: z.string().optional().describe('CSS selector (most direct — bypasses all other filters)'), xpath: z.string().optional().describe('XPath expression (bypasses all other filters)'), nth: z.number().optional().describe('Select the Nth match (0-indexed), default: 0'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, - src/tools/elements.ts:6-33 (registration)Registration of find_element tool on the MCP server via server.tool() inside registerElementTools(). Also registered via registerElementTools() call in src/tools/index.ts line 47.
export function registerElementTools(server: McpServer, bridge: WebSocketBridge) { server.tool( 'find_element', 'Find an element by visible text, ARIA role, ARIA label, CSS selector, or XPath. Returns a unique CSS selector and element details. Use this when you need to locate an element but don\'t know its exact selector. Role matching supports both explicit role="..." attributes AND semantic HTML (e.g., <button> matches role "button", <a> matches role "link"). When multiple filters are provided (e.g., role + text), they are combined with AND logic.', { text: z.string().optional().describe('Find by visible text content (substring match). Can be combined with role for precise targeting.'), role: z.string().optional().describe('Find by ARIA role (e.g., "button", "link", "navigation", "main"). Falls back to semantic HTML roles if no explicit role attribute is present.'), ariaLabel: z.string().optional().describe('Find by aria-label attribute (substring match)'), css: z.string().optional().describe('CSS selector (most direct — bypasses all other filters)'), xpath: z.string().optional().describe('XPath expression (bypasses all other filters)'), nth: z.number().optional().describe('Select the Nth match (0-indexed), default: 0'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ text, role, ariaLabel, css, xpath, nth, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'find_element', params: { text, role, ariaLabel, css, xpath, nth }, tabId, apiKey, timeout: LONG_TIMEOUT, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } );