force_pseudo_state
Force CSS pseudo-states (hover, focus, active, visited, focus-within, focus-visible) on an element and retrieve computed styles for testing state styles without actual interaction.
Instructions
Force a CSS pseudo-state (hover, focus, active, visited, focus-within, focus-visible) on an element and read the resulting computed styles. Useful for testing CSS state styles without actual interaction.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| selector | Yes | CSS selector for the element | |
| pseudoState | Yes | Pseudo-state to force | |
| tabId | No | Target tab ID (defaults to currently active tab) | |
| apiKey | No | API key for authentication if enabled |
Implementation Reference
- src/tools/interaction.ts:42-64 (handler)The handler for the force_pseudo_state tool. It sends a 'force_pseudo_state' command via WebSocket bridge with CSS selector and pseudo-state parameters, then returns the resulting computed styles.
server.tool( 'force_pseudo_state', 'Force a CSS pseudo-state (hover, focus, active, visited, focus-within, focus-visible) on an element and read the resulting computed styles. Useful for testing CSS state styles without actual interaction.', { selector: z.string().describe('CSS selector for the element'), pseudoState: z.enum(['hover', 'focus', 'active', 'visited', 'focus-within', 'focus-visible']).describe('Pseudo-state to force'), 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 ({ selector, pseudoState, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'force_pseudo_state', params: { selector, pseudoState }, 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/interaction.ts:45-50 (schema)Zod schema defining input validation: selector (string), pseudoState (enum of hover/focus/active/visited/focus-within/focus-visible), optional tabId (number), optional apiKey (string).
{ selector: z.string().describe('CSS selector for the element'), pseudoState: z.enum(['hover', 'focus', 'active', 'visited', 'focus-within', 'focus-visible']).describe('Pseudo-state to force'), 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/index.ts:49-49 (registration)Registration of the interaction tools (including force_pseudo_state) via registerInteractionTools called in registerAllTools.
registerInteractionTools(server, bridge); - src/tools/interaction.ts:17-89 (registration)The registerInteractionTools function that registers force_pseudo_state (and hover_and_inspect, get_tooltip_text) as an MCP tool on the server.
export function registerInteractionTools(server: McpServer, bridge: WebSocketBridge) { server.tool( 'hover_and_inspect', 'Hover over an element and capture any resulting DOM or style changes. Useful for testing dropdown menus, tooltips, and hover effects.', { target: targetSchema.describe('Element or position to hover over'), captureChanges: z.boolean().optional().default(true).describe('Capture and return DOM/style changes after hover'), 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 ({ target, captureChanges, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'hover_and_inspect', params: { target, captureChanges }, 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) }] }; } ); server.tool( 'force_pseudo_state', 'Force a CSS pseudo-state (hover, focus, active, visited, focus-within, focus-visible) on an element and read the resulting computed styles. Useful for testing CSS state styles without actual interaction.', { selector: z.string().describe('CSS selector for the element'), pseudoState: z.enum(['hover', 'focus', 'active', 'visited', 'focus-within', 'focus-visible']).describe('Pseudo-state to force'), 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 ({ selector, pseudoState, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'force_pseudo_state', params: { selector, pseudoState }, 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) }] }; } ); server.tool( 'get_tooltip_text', 'Hover over an element and extract any tooltip text. Checks title attribute, aria-describedby, aria-labelledby, CSS tooltips, and custom tooltip components.', { target: targetSchema.describe('Element or position to hover over'), waitForTooltip: z.number().optional().default(200).describe('Time to wait after hover for tooltip animations (ms)'), 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 ({ target, waitForTooltip, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'get_tooltip_text', params: { target, waitForTooltip }, 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/websocket-bridge.ts:63-103 (helper)WebSocketBridge.sendCommand helper used by the handler to dispatch the force_pseudo_state command and await the response.
async sendCommand(cmd: BridgeCommand): Promise<BridgeResponse> { if (!this.isConnected()) { return { success: false, error: { code: 'NOT_CONNECTED', message: 'Chrome extension is not connected. Ensure the extension is installed, enabled, and the browser is running.', }, }; } const id = crypto.randomUUID(); const timeout = cmd.timeout ?? DEFAULT_TIMEOUT; return new Promise<BridgeResponse>((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(id); resolve({ success: false, error: { code: 'TIMEOUT', message: `Command '${cmd.command}' timed out after ${timeout}ms`, }, }); }, timeout); this.pending.set(id, { resolve, reject, timer }); const message = { id, type: 'request', command: cmd.command, params: cmd.params, tabId: cmd.tabId, apiKey: cmd.apiKey, timestamp: Date.now(), }; this.client!.send(JSON.stringify(message)); }); }