tab_to_next
Simulate Tab key navigation by moving focus to the next focusable element. Returns details of the previous and current focused elements, and indicates if focus wrapped around.
Instructions
Simulate Tab key navigation and track focus movement. Returns previous and current focused element info, and whether focus wrapped around.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| direction | No | Tab direction | next |
| shift | No | Hold Shift for reverse tab order | |
| tabId | No | Target tab ID (defaults to currently active tab) | |
| apiKey | No | API key for authentication if enabled |
Implementation Reference
- src/tools/qa.ts:64-86 (handler)The tool handler for 'tab_to_next'. It sends a 'tab_to_next' command via WebSocket bridge to the Chrome extension, passing optional direction and shift params. The extension then simulates Tab key navigation and returns focus movement info including previous/current focused elements and whether focus wrapped around.
server.tool( 'tab_to_next', 'Simulate Tab key navigation and track focus movement. Returns previous and current focused element info, and whether focus wrapped around.', { direction: z.enum(['next', 'previous']).optional().default('next').describe('Tab direction'), shift: z.boolean().optional().describe('Hold Shift for reverse tab order'), 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 ({ direction, shift, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'tab_to_next', params: { direction, shift }, 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/qa.ts:64-86 (registration)The tool is registered via server.tool('tab_to_next', ...) inside the registerQaTools function. This is called from registerAllTools in src/tools/index.ts which is invoked from createServer in src/server.ts.
server.tool( 'tab_to_next', 'Simulate Tab key navigation and track focus movement. Returns previous and current focused element info, and whether focus wrapped around.', { direction: z.enum(['next', 'previous']).optional().default('next').describe('Tab direction'), shift: z.boolean().optional().describe('Hold Shift for reverse tab order'), 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 ({ direction, shift, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'tab_to_next', params: { direction, shift }, 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-129 (helper)The WebSocketBridge.sendCommand() method is the helper that sends the 'tab_to_next' command over WebSocket to the Chrome extension and awaits 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)); }); } private rejectAllPending(reason: string): void { for (const [id, pending] of this.pending) { clearTimeout(pending.timer); pending.resolve({ success: false, error: { code: 'CONNECTION_LOST', message: reason, }, }); } this.pending.clear(); } async close(): Promise<void> { this.rejectAllPending('Server shutting down'); if (this.client) { this.client.close(); this.client = null; } return new Promise((resolve) => { this.wss.close(() => resolve()); }); } } - src/tools/qa.ts:67-72 (schema)Input schema for 'tab_to_next': optional direction (enum 'next'/'previous', default 'next'), optional shift (boolean), optional tabId (number), optional apiKey (string).
{ direction: z.enum(['next', 'previous']).optional().default('next').describe('Tab direction'), shift: z.boolean().optional().describe('Hold Shift for reverse tab order'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), },