diff --git a/README.md b/README.md index fbb05fa..fcd88ec 100644 --- a/README.md +++ b/README.md @@ -627,39 +627,14 @@ http.createServer(async (req, res) => { -- **browser_tab_close** - - Title: Close a tab - - Description: Close a tab +- **browser_tabs** + - Title: Manage tabs + - Description: List, create, close, or select a browser tab. - Parameters: - - `index` (number, optional): The index of the tab to close. Closes current tab if not provided. + - `action` (string): Operation to perform + - `index` (number, optional): Tab index, used for close/select. If omitted for close, current tab is closed. - Read-only: **false** - - -- **browser_tab_list** - - Title: List tabs - - Description: List browser tabs - - Parameters: None - - Read-only: **true** - - - -- **browser_tab_new** - - Title: Open a new tab - - Description: Open a new tab - - Parameters: - - `url` (string, optional): The URL to navigate to in the new tab. If not provided, the new tab will be blank. - - Read-only: **true** - - - -- **browser_tab_select** - - Title: Select a tab - - Description: Select a tab by index - - Parameters: - - `index` (number): The index of the tab to select - - Read-only: **true** -
diff --git a/src/tools/tabs.ts b/src/tools/tabs.ts index 048cb28..f92177d 100644 --- a/src/tools/tabs.ts +++ b/src/tools/tabs.ts @@ -17,85 +17,48 @@ import { z } from 'zod'; import { defineTool } from './tool.js'; -const listTabs = defineTool({ +const browserTabs = defineTool({ capability: 'core-tabs', schema: { - name: 'browser_tab_list', - title: 'List tabs', - description: 'List browser tabs', - inputSchema: z.object({}), - type: 'readOnly', - }, - - handle: async (context, params, response) => { - await context.ensureTab(); - response.setIncludeTabs(); - }, -}); - -const selectTab = defineTool({ - capability: 'core-tabs', - - schema: { - name: 'browser_tab_select', - title: 'Select a tab', - description: 'Select a tab by index', + name: 'browser_tabs', + title: 'Manage tabs', + description: 'List, create, close, or select a browser tab.', inputSchema: z.object({ - index: z.number().describe('The index of the tab to select'), - }), - type: 'readOnly', - }, - - handle: async (context, params, response) => { - await context.selectTab(params.index); - response.setIncludeSnapshot(); - }, -}); - -const newTab = defineTool({ - capability: 'core-tabs', - - schema: { - name: 'browser_tab_new', - title: 'Open a new tab', - description: 'Open a new tab', - inputSchema: z.object({ - url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'), - }), - type: 'readOnly', - }, - - handle: async (context, params, response) => { - const tab = await context.newTab(); - if (params.url) - await tab.navigate(params.url); - response.setIncludeSnapshot(); - }, -}); - -const closeTab = defineTool({ - capability: 'core-tabs', - - schema: { - name: 'browser_tab_close', - title: 'Close a tab', - description: 'Close a tab', - inputSchema: z.object({ - index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'), + action: z.enum(['list', 'new', 'close', 'select']).describe('Operation to perform'), + index: z.number().optional().describe('Tab index, used for close/select. If omitted for close, current tab is closed.'), }), type: 'destructive', }, handle: async (context, params, response) => { - await context.closeTab(params.index); - response.setIncludeSnapshot(); + switch (params.action) { + case 'list': { + await context.ensureTab(); + response.setIncludeTabs(); + return; + } + case 'new': { + await context.newTab(); + response.setIncludeTabs(); + return; + } + case 'close': { + await context.closeTab(params.index); + response.setIncludeSnapshot(); + return; + } + case 'select': { + if (!params.index) + throw new Error('Tab index is required'); + await context.selectTab(params.index); + response.setIncludeSnapshot(); + return; + } + } }, }); export default [ - listTabs, - newTab, - selectTab, - closeTab, + browserTabs, ]; diff --git a/tests/tabs.spec.ts b/tests/tabs.spec.ts index 58b7bc0..a194c3b 100644 --- a/tests/tabs.spec.ts +++ b/tests/tabs.spec.ts @@ -19,8 +19,14 @@ import { test, expect } from './fixtures.js'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; async function createTab(client: Client, title: string, body: string) { + await client.callTool({ + name: 'browser_tabs', + arguments: { + action: 'new', + }, + }); return await client.callTool({ - name: 'browser_tab_new', + name: 'browser_navigate', arguments: { url: `data:text/html,${title}${body}`, }, @@ -29,7 +35,10 @@ async function createTab(client: Client, title: string, body: string) { test('list initial tabs', async ({ client }) => { expect(await client.callTool({ - name: 'browser_tab_list', + name: 'browser_tabs', + arguments: { + action: 'list', + }, })).toHaveResponse({ tabs: `- 0: (current) [] (about:blank)`, }); @@ -38,7 +47,10 @@ test('list initial tabs', async ({ client }) => { test('list first tab', async ({ client }) => { await createTab(client, 'Tab one', 'Body one'); expect(await client.callTool({ - name: 'browser_tab_list', + name: 'browser_tabs', + arguments: { + action: 'list', + }, })).toHaveResponse({ tabs: `- 0: [] (about:blank) - 1: (current) [Tab one] (data:text/html,Tab oneBody one)`, @@ -75,8 +87,9 @@ test('select tab', async ({ client }) => { await createTab(client, 'Tab two', 'Body two'); expect(await client.callTool({ - name: 'browser_tab_select', + name: 'browser_tabs', arguments: { + action: 'select', index: 1, }, })).toHaveResponse({ @@ -97,8 +110,9 @@ test('close tab', async ({ client }) => { await createTab(client, 'Tab two', 'Body two'); expect(await client.callTool({ - name: 'browser_tab_close', + name: 'browser_tabs', arguments: { + action: 'close', index: 2, }, })).toHaveResponse({