diff --git a/README.md b/README.md index ba982e9..ce1c72d 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,10 @@ Playwright MCP server supports following arguments. They can be provided in the --no-sandbox disable the sandbox for all process types that are normally sandboxed. --output-dir path to the directory for output files. + --output-mode whether to save snapshots, console + messages, network logs to a file or to + the standard output. Can be "file" or + "stdout". Default is "stdout". --port port to listen on for SSE transport. --proxy-bypass comma-separated domains to bypass proxy, for example @@ -436,6 +440,10 @@ Playwright MCP server supports following arguments. They can be provided in the created. --viewport-size specify browser viewport size in pixels, for example "1280x720" + --codegen specify the language to use for code + generation, possible values: + "typescript", "none". Default is + "typescript". ``` @@ -659,6 +667,11 @@ npx @playwright/mcp@latest --config path/to/config.json */ outputDir?: string; + /** + * Whether to save snapshots, console messages, network logs and other session logs to a file or to the standard output. Defaults to "stdout". + */ + outputMode?: 'file' | 'stdout'; + console?: { /** * The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info". @@ -705,13 +718,18 @@ npx @playwright/mcp@latest --config path/to/config.json * When taking snapshots for responses, specifies the mode to use. */ mode?: 'incremental' | 'full' | 'none'; - } + }; /** * Whether to allow file uploads from anywhere on the file system. * By default (false), file uploads are restricted to paths within the MCP roots only. */ allowUnrestrictedFileAccess?: boolean; + + /** + * Specify the language to use for code generation. + */ + codegen?: 'typescript' | 'none'; } ``` @@ -811,7 +829,7 @@ http.createServer(async (req, res) => { - Title: Click - Description: Perform click on a web page - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click - `button` (string, optional): Button to click, defaults to left @@ -832,7 +850,8 @@ http.createServer(async (req, res) => { - Title: Get console messages - Description: Returns all console messages - Parameters: - - `level` (string, optional): Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info". + - `level` (string): Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info". + - `filename` (string, optional): Filename to save the console messages to. If not provided, messages are returned as text. - Read-only: **true** @@ -892,7 +911,7 @@ http.createServer(async (req, res) => { - Title: Hover mouse - Description: Hover over element on page - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - Read-only: **false** @@ -909,7 +928,7 @@ http.createServer(async (req, res) => { - **browser_navigate_back** - Title: Go back - - Description: Go back to the previous page + - Description: Go back to the previous page in the history - Parameters: None - Read-only: **false** @@ -919,7 +938,8 @@ http.createServer(async (req, res) => { - Title: List network requests - Description: Returns all network requests since loading the page - Parameters: - - `includeStatic` (boolean, optional): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false. + - `includeStatic` (boolean): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false. + - `filename` (string, optional): Filename to save the network requests to. If not provided, requests are returned as text. - Read-only: **true** @@ -956,7 +976,7 @@ http.createServer(async (req, res) => { - Title: Select option - Description: Select an option in a dropdown - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values. - Read-only: **false** @@ -976,7 +996,7 @@ http.createServer(async (req, res) => { - Title: Take a screenshot - Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions. - Parameters: - - `type` (string, optional): Image format for the screenshot. Default is png. + - `type` (string): Image format for the screenshot. Default is png. - `filename` (string, optional): File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory. - `element` (string, optional): Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too. - `ref` (string, optional): Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too. @@ -989,7 +1009,7 @@ http.createServer(async (req, res) => { - Title: Type text - Description: Type text into editable element - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - `text` (string): Text to type into the element - `submit` (boolean, optional): Whether to submit entered text (press Enter after) @@ -1046,18 +1066,25 @@ http.createServer(async (req, res) => { - Title: Click - Description: Click left mouse button at a given position - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - `x` (number): X coordinate - `y` (number): Y coordinate - Read-only: **false** +- **browser_mouse_down** + - Title: Press mouse down + - Description: Press mouse down + - Parameters: + - `button` (string, optional): Button to press, defaults to left + - Read-only: **false** + + + - **browser_mouse_drag_xy** - Title: Drag mouse - Description: Drag left mouse button to a given position - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - `startX` (number): Start X coordinate - `startY` (number): Start Y coordinate - `endX` (number): End X coordinate @@ -1070,11 +1097,29 @@ http.createServer(async (req, res) => { - Title: Move mouse - Description: Move mouse to a given position - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element - `x` (number): X coordinate - `y` (number): Y coordinate - Read-only: **false** + + +- **browser_mouse_up** + - Title: Press mouse up + - Description: Press mouse up + - Parameters: + - `button` (string, optional): Button to press, defaults to left + - Read-only: **false** + + + +- **browser_mouse_wheel** + - Title: Scroll mouse wheel + - Description: Scroll mouse wheel + - Parameters: + - `deltaX` (number): X delta + - `deltaY` (number): Y delta + - Read-only: **false** +
@@ -1100,7 +1145,7 @@ http.createServer(async (req, res) => { - Title: Create locator for element - Description: Generate locator for the given element to use in tests - Parameters: - - `element` (string): Human-readable element description used to obtain permission to interact with the element + - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot - Read-only: **true** diff --git a/package-lock.json b/package-lock.json index 8ade614..eb90783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "packages/*" ], "devDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", - "@playwright/test": "1.58.0-alpha-2026-01-16", + "@modelcontextprotocol/sdk": "^1.25.2", + "@playwright/test": "1.59.0-alpha-1769176698000", "@types/node": "^24.3.0" } }, @@ -724,6 +724,19 @@ "node": ">=18" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -775,12 +788,13 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.24.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz", - "integrity": "sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==", + "version": "1.25.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", + "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", "dev": true, "license": "MIT", "dependencies": { + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -791,6 +805,7 @@ "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", @@ -825,13 +840,13 @@ "link": true }, "node_modules/@playwright/test": { - "version": "1.58.0-alpha-2026-01-16", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.0-alpha-2026-01-16.tgz", - "integrity": "sha512-zhOc7QhE1FcIFUa1GNzs30phACtSLOgObjiz1GJSAcfp1OetskyjvspJPgHxrHggWphkQaj8IYoDe4y03rd6eg==", + "version": "1.59.0-alpha-1769176698000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1769176698000.tgz", + "integrity": "sha512-n0R0xysvhuQ7XZnNrYBjB1wA3d6+UC1kneg8yqAdnUe+SpkWSRYoH4fA4gfLK/gcb9bskb5N+/nEQaoUC9jbGQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.0-alpha-2026-01-16" + "playwright": "1.59.0-alpha-1769176698000" }, "bin": { "playwright": "cli.js" @@ -2148,6 +2163,17 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.5.tgz", + "integrity": "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2306,6 +2332,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2582,12 +2615,12 @@ } }, "node_modules/playwright": { - "version": "1.58.0-alpha-2026-01-16", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0-alpha-2026-01-16.tgz", - "integrity": "sha512-Ix0VALX+fNrXICSNnrfsvTMch0LA3WdVY8q71ReQzQ+UF0lCK9YbtRwBz65BJLBsWE1j38vBLR+YYE93MezYZA==", + "version": "1.59.0-alpha-1769176698000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1769176698000.tgz", + "integrity": "sha512-e6r9Ivkm7PJIeMU8zl1JIwktAGZD6gXb6OCegdXpqlyH43OF9EBImLrIl8ovn2hWtmQPSeTDQyaBELSL89ZR6w==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.0-alpha-2026-01-16" + "playwright-core": "1.59.0-alpha-1769176698000" }, "bin": { "playwright": "cli.js" @@ -2600,9 +2633,9 @@ } }, "node_modules/playwright-core": { - "version": "1.58.0-alpha-2026-01-16", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0-alpha-2026-01-16.tgz", - "integrity": "sha512-r3XM/xwmyoKLgHI8+ooCoKaRv8mch39UoHyLfWSols6GB2H9Lx3JhvhzRep+rYBYk+GDj0222GKuwg1PnVF8zA==", + "version": "1.59.0-alpha-1769176698000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1769176698000.tgz", + "integrity": "sha512-5w4TFCd2ucGVc3eeYmnNHr3oqkeYW9Du2Sr9kXCo16yLPD82ssYdd7CwzfARSsYNr0AOH5yrYwBpzsWR9H0+/Q==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -3820,8 +3853,8 @@ "version": "0.0.56", "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.0-alpha-2026-01-16", - "playwright-core": "1.58.0-alpha-2026-01-16" + "playwright": "1.59.0-alpha-1769176698000", + "playwright-core": "1.59.0-alpha-1769176698000" }, "bin": { "mcp-server-playwright": "cli.js" diff --git a/package.json b/package.json index 83694ce..d6f02fa 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "packages/*" ], "devDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", - "@playwright/test": "1.58.0-alpha-2026-01-16", + "@modelcontextprotocol/sdk": "^1.25.2", + "@playwright/test": "1.59.0-alpha-1769176698000", "@types/node": "^24.3.0" } } diff --git a/packages/extension/tests/extension.spec.ts b/packages/extension/tests/extension.spec.ts index 06b3e7a..43c40bb 100644 --- a/packages/extension/tests/extension.spec.ts +++ b/packages/extension/tests/extension.spec.ts @@ -134,7 +134,7 @@ test(`navigate with extension`, async ({ browserWithExtension, startClient, serv await selectorPage.getByRole('button', { name: 'Allow' }).click(); expect(await navigateResponse).toHaveResponse({ - pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); }); @@ -166,7 +166,7 @@ test(`snapshot of an existing page`, async ({ browserWithExtension, startClient, await selectorPage.locator('.tab-item', { hasText: 'Title' }).getByRole('button', { name: 'Connect' }).click(); expect(await navigateResponse).toHaveResponse({ - pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); expect(browserContext.pages()).toHaveLength(4); @@ -187,7 +187,7 @@ test(`extension not installed timeout`, async ({ browserWithExtension, startClie name: 'browser_navigate', arguments: { url: server.HELLO_WORLD }, })).toHaveResponse({ - result: expect.stringContaining('Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed.'), + error: expect.stringContaining('Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed.'), isError: true, }); @@ -216,7 +216,7 @@ testWithOldExtensionVersion(`works with old extension version`, async ({ browser await selectorPage.getByRole('button', { name: 'Allow' }).click(); expect(await navigateResponse).toHaveResponse({ - pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); }); @@ -242,7 +242,7 @@ test(`extension needs update`, async ({ browserWithExtension, startClient, serve await expect(confirmationPage.locator('.status-banner')).toContainText(`Playwright MCP version trying to connect requires newer extension version`); expect(await navigateResponse).toHaveResponse({ - result: expect.stringContaining('Extension connection timeout.'), + error: expect.stringContaining('Extension connection timeout.'), isError: true, }); }); @@ -267,10 +267,9 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi const navigateResponse = await client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD }, - timeout: 1000, }); expect(await navigateResponse).toHaveResponse({ - result: expect.stringContaining('Extension connection timeout.'), + error: expect.stringContaining('Extension connection timeout.'), isError: true, }); expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?'); @@ -300,6 +299,6 @@ test(`bypass connection dialog with token`, async ({ browserWithExtension, start }); expect(await navigateResponse).toHaveResponse({ - pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), + snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); }); diff --git a/packages/playwright-mcp/config.d.ts b/packages/playwright-mcp/config.d.ts index 9fcddec..13a4a12 100644 --- a/packages/playwright-mcp/config.d.ts +++ b/packages/playwright-mcp/config.d.ts @@ -16,7 +16,17 @@ import type * as playwright from 'playwright'; -export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf' | 'testing' | 'tracing'; +export type ToolCapability = + 'core' | + 'core-input' | + 'core-navigation' | + 'core-tabs' | + 'core-install' | + 'core-input' | + 'vision' | + 'pdf' | + 'testing' | + 'tracing'; export type Config = { /** @@ -147,6 +157,11 @@ export type Config = { */ outputDir?: string; + /** + * Whether to save snapshots, console messages, network logs and other session logs to a file or to the standard output. Defaults to "stdout". + */ + outputMode?: 'file' | 'stdout'; + console?: { /** * The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info". @@ -193,11 +208,16 @@ export type Config = { * When taking snapshots for responses, specifies the mode to use. */ mode?: 'incremental' | 'full' | 'none'; - } + }; /** * Whether to allow file uploads from anywhere on the file system. * By default (false), file uploads are restricted to paths within the MCP roots only. */ allowUnrestrictedFileAccess?: boolean; + + /** + * Specify the language to use for code generation. + */ + codegen?: 'typescript' | 'none'; }; diff --git a/packages/playwright-mcp/package.json b/packages/playwright-mcp/package.json index a144150..5a42c8f 100644 --- a/packages/playwright-mcp/package.json +++ b/packages/playwright-mcp/package.json @@ -34,8 +34,8 @@ } }, "dependencies": { - "playwright": "1.58.0-alpha-2026-01-16", - "playwright-core": "1.58.0-alpha-2026-01-16" + "playwright": "1.59.0-alpha-1769176698000", + "playwright-core": "1.59.0-alpha-1769176698000" }, "bin": { "mcp-server-playwright": "cli.js" diff --git a/packages/playwright-mcp/tests/click.spec.ts b/packages/playwright-mcp/tests/click.spec.ts index 4783110..b362519 100644 --- a/packages/playwright-mcp/tests/click.spec.ts +++ b/packages/playwright-mcp/tests/click.spec.ts @@ -33,7 +33,7 @@ test('browser_click', async ({ client, server }) => { arguments: { url: server.PREFIX }, })).toHaveResponse({ code: `await page.goto('${server.PREFIX}');`, - pageState: expect.stringContaining(`- button \"Submit\" [ref=e2]`), + snapshot: expect.stringContaining(`- button \"Submit\" [ref=e2]`), }); expect(await client.callTool({ @@ -44,6 +44,6 @@ test('browser_click', async ({ client, server }) => { }, })).toHaveResponse({ code: `await page.getByRole('button', { name: 'Submit' }).click();`, - pageState: expect.stringContaining(`button "Submit" [active] [ref=e2]`), + snapshot: expect.stringContaining(`button "Submit" [active] [ref=e2]`), }); }); diff --git a/packages/playwright-mcp/tests/core.spec.ts b/packages/playwright-mcp/tests/core.spec.ts index 13ba4a0..ba3780a 100644 --- a/packages/playwright-mcp/tests/core.spec.ts +++ b/packages/playwright-mcp/tests/core.spec.ts @@ -22,11 +22,6 @@ test('browser_navigate', async ({ client, server }) => { arguments: { url: server.HELLO_WORLD }, })).toHaveResponse({ code: `await page.goto('${server.HELLO_WORLD}');`, - pageState: `- Page URL: ${server.HELLO_WORLD} -- Page Title: Title -- Page Snapshot: -\`\`\`yaml -- generic [active] [ref=e1]: Hello, world! -\`\`\``, + snapshot: expect.stringContaining(`generic [active] [ref=e1]: Hello, world!`), }); }); diff --git a/packages/playwright-mcp/tests/fixtures.ts b/packages/playwright-mcp/tests/fixtures.ts index d8496e4..4732985 100644 --- a/packages/playwright-mcp/tests/fixtures.ts +++ b/packages/playwright-mcp/tests/fixtures.ts @@ -229,7 +229,7 @@ export const expect = baseExpect.extend({ expect(parsed).not.toEqual(expect.objectContaining(object)); else expect(parsed).toEqual(expect.objectContaining(object)); - } catch (e) { + } catch (e: any) { return { pass: isNot, message: () => e.message, @@ -250,10 +250,12 @@ function parseResponse(response: any) { const text = response.content[0].text; const sections = parseSections(text); + const error = sections.get('Error'); const result = sections.get('Result'); const code = sections.get('Ran Playwright code'); const tabs = sections.get('Open tabs'); const pageState = sections.get('Page state'); + const snapshot = sections.get('Snapshot'); const consoleMessages = sections.get('New console messages'); const modalState = sections.get('Modal state'); const downloads = sections.get('Downloads'); @@ -262,10 +264,12 @@ function parseResponse(response: any) { const attachments = response.content.slice(1); return { + error, result, code: codeNoFrame, tabs, pageState, + snapshot, consoleMessages, modalState, downloads, diff --git a/packages/playwright-mcp/update-readme.js b/packages/playwright-mcp/update-readme.js index 3abc319..7ed1245 100644 --- a/packages/playwright-mcp/update-readme.js +++ b/packages/playwright-mcp/update-readme.js @@ -18,14 +18,15 @@ const fs = require('fs') const path = require('path') -const { zodToJsonSchema } = require('zod-to-json-schema') const { execSync } = require('child_process'); const { browserTools } = require('playwright/lib/mcp/browser/tools'); const capabilities = { + 'core-navigation': 'Core automation', 'core': 'Core automation', 'core-tabs': 'Tab management', + 'core-input': 'Core automation', 'core-install': 'Browser installation', 'vision': 'Coordinate-based (opt-in via --caps=vision)', 'pdf': 'PDF generation (opt-in via --caps=pdf)', @@ -33,7 +34,15 @@ const capabilities = { 'tracing': 'Tracing (opt-in via --caps=tracing)', }; -const toolsByCapability = Object.fromEntries(Object.entries(capabilities).map(([capability, title]) => [title, browserTools.filter(tool => tool.capability === capability).sort((a, b) => a.schema.name.localeCompare(b.schema.name))])); +/** @type {Record} */ +const toolsByCapability = {}; +for (const [capability, title] of Object.entries(capabilities)) { + let tools = browserTools.filter(tool => tool.capability === capability && !tool.skillOnly); + tools = (toolsByCapability[title] || []).concat(tools); + toolsByCapability[title] = tools; +} +for (const [, tools] of Object.entries(toolsByCapability)) + tools.sort((a, b) => a.schema.name.localeCompare(b.schema.name)); /** * @param {any} tool @@ -47,7 +56,7 @@ function formatToolForReadme(tool) { lines.push(` - Title: ${tool.title}`); lines.push(` - Description: ${tool.description}`); - const inputSchema = /** @type {any} */ (zodToJsonSchema(tool.inputSchema || {})); + const inputSchema = /** @type {any} */ (tool.inputSchema ? tool.inputSchema.toJSONSchema() : {}); const requiredParams = inputSchema.required || []; if (inputSchema.properties && Object.keys(inputSchema.properties).length) { lines.push(` - Parameters:`);