close_tab
Close a specific browser tab using its ID to clean up tabs you no longer need.
Instructions
Close a specific browser tab. Use this to clean up tabs you no longer need. Be careful - closed tabs cannot be recovered through this tool!
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| tabId | Yes | ID of the tab to close (get this from list_tabs) | |
| apiKey | No | API key for authentication if enabled |
Implementation Reference
- src/tools/tab-management.ts:65-83 (handler)The 'close_tab' tool handler: defines the tool with name 'close_tab', accepts tabId (required number) and apiKey (optional string), sends a 'close_tab' command via WebSocket bridge to the browser extension, and returns a success message with the closed tab ID or an error.
server.tool( 'close_tab', 'Close a specific browser tab. Use this to clean up tabs you no longer need. Be careful - closed tabs cannot be recovered through this tool!', { tabId: z.number().describe('ID of the tab to close (get this from list_tabs)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'close_tab', params: { tabId }, apiKey, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: `Closed tab ${tabId}` }] }; } ); - src/tools/tab-management.ts:68-71 (schema)Input schema for close_tab: tabId (z.number()) required to identify which tab to close, and apiKey (z.string().optional()) for authentication.
{ tabId: z.number().describe('ID of the tab to close (get this from list_tabs)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, - src/tools/index.ts:29-31 (registration)Registration of all tools including close_tab via registerTabManagementTools(server, bridge) call at line 31.
export function registerAllTools(server: McpServer, bridge: WebSocketBridge) { registerNavigationTools(server, bridge); registerTabManagementTools(server, bridge); - src/tools/tab-management.ts:5-149 (registration)The registerTabManagementTools function that registers close_tab (among other tab tools) on the MCP server.
export function registerTabManagementTools(server: McpServer, bridge: WebSocketBridge) { server.tool( 'list_tabs', 'Get a list of ALL open browser tabs. Use this to find tab IDs, see what pages are open, or locate a specific website you need to interact with. Returns tab ID, URL, title, and active status.', { apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ apiKey }) => { const result = await bridge.sendCommand({ command: 'list_tabs', params: {}, apiKey, }); 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( 'select_tab', 'Switch to a specific tab. Use this when you need to interact with a different open tab. Makes the tab active and brings its window to front.', { tabId: z.number().describe('ID of the tab to activate (get this from list_tabs)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'select_tab', params: { tabId }, apiKey, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: `Activated tab ${tabId}` }] }; } ); server.tool( 'new_tab', 'Open a brand new browser tab. You can optionally provide a URL to navigate to immediately. Returns the new tab ID so you can interact with it.', { url: z.string().optional().describe('Optional URL to load in the new tab (e.g., "https://google.com")'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ url, apiKey }) => { const result = await bridge.sendCommand({ command: 'new_tab', params: { url }, apiKey, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data) }] }; } ); server.tool( 'close_tab', 'Close a specific browser tab. Use this to clean up tabs you no longer need. Be careful - closed tabs cannot be recovered through this tool!', { tabId: z.number().describe('ID of the tab to close (get this from list_tabs)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'close_tab', params: { tabId }, apiKey, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } return { content: [{ type: 'text', text: `Closed tab ${tabId}` }] }; } ); server.tool( 'get_tab_state', 'Capture URL, title, and a DOM hash for quick tab state comparison. Useful for verifying cross-tab state sync.', { 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 ({ tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'get_tab_state', params: {}, tabId, apiKey, }); 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( 'assert_tabs_match', 'Verify two tabs have the same state (URL, title, DOM hash). Useful for cross-tab state sync testing.', { tabIdA: z.number().describe('First tab ID'), tabIdB: z.number().describe('Second tab ID'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ tabIdA, tabIdB, apiKey }) => { const result = await bridge.sendCommand({ command: 'assert_tabs_match', params: { tabIdA, tabIdB }, apiKey, }); 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( 'test_storage_sync', 'Test cross-tab localStorage synchronization. Sets a value in tab A and checks if it appears in tab B.', { tabIdA: z.number().describe('Tab ID to set localStorage value in'), tabIdB: z.number().describe('Tab ID to verify localStorage value in'), key: z.string().describe('localStorage key to test'), value: z.string().describe('Value to set and expect'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ tabIdA, tabIdB, key, value, apiKey }) => { const result = await bridge.sendCommand({ command: 'test_storage_sync', params: { tabIdA, tabIdB, key, value }, apiKey, }); 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)The WebSocketBridge.sendCommand method that the close_tab handler uses to relay the 'close_tab' command to the Chrome extension over WebSocket.
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)); }); }