From 56777fade2ad0a9567cbedc44cc2abad765c85d7 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Wed, 15 Oct 2025 16:09:58 +0200 Subject: [PATCH] feat(cli): allow enabling and disabling categories of tools --- README.md | 29 +++--- docs/tool-reference.md | 180 ++++++++++++++++++------------------ scripts/generate-docs.ts | 15 +-- src/cli.ts | 23 +++++ src/main.ts | 5 +- src/tools/ToolDefinition.ts | 4 +- src/tools/categories.ts | 23 +++-- src/tools/console.ts | 4 +- src/tools/emulation.ts | 6 +- src/tools/input.ts | 14 +-- src/tools/network.ts | 6 +- src/tools/pages.ts | 18 ++-- src/tools/performance.ts | 8 +- src/tools/screenshot.ts | 4 +- src/tools/script.ts | 4 +- src/tools/snapshot.ts | 6 +- tests/cli.test.ts | 33 ++++++- 17 files changed, 227 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 8d9af3557..e29389430 100644 --- a/README.md +++ b/README.md @@ -230,22 +230,24 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles -- **Input automation** (7 tools) - - [`click`](docs/tool-reference.md#click) - - [`drag`](docs/tool-reference.md#drag) - - [`fill`](docs/tool-reference.md#fill) - - [`fill_form`](docs/tool-reference.md#fill_form) - - [`handle_dialog`](docs/tool-reference.md#handle_dialog) - - [`hover`](docs/tool-reference.md#hover) - - [`upload_file`](docs/tool-reference.md#upload_file) -- **Navigation automation** (7 tools) +- **Core automation** (10 tools) - [`close_page`](docs/tool-reference.md#close_page) + - [`handle_dialog`](docs/tool-reference.md#handle_dialog) - [`list_pages`](docs/tool-reference.md#list_pages) - [`navigate_page`](docs/tool-reference.md#navigate_page) - [`navigate_page_history`](docs/tool-reference.md#navigate_page_history) - [`new_page`](docs/tool-reference.md#new_page) - [`select_page`](docs/tool-reference.md#select_page) + - [`take_screenshot`](docs/tool-reference.md#take_screenshot) + - [`take_snapshot`](docs/tool-reference.md#take_snapshot) - [`wait_for`](docs/tool-reference.md#wait_for) +- **Input automation** (6 tools) + - [`click`](docs/tool-reference.md#click) + - [`drag`](docs/tool-reference.md#drag) + - [`fill`](docs/tool-reference.md#fill) + - [`fill_form`](docs/tool-reference.md#fill_form) + - [`hover`](docs/tool-reference.md#hover) + - [`upload_file`](docs/tool-reference.md#upload_file) - **Emulation** (3 tools) - [`emulate_cpu`](docs/tool-reference.md#emulate_cpu) - [`emulate_network`](docs/tool-reference.md#emulate_network) @@ -257,11 +259,9 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles - **Network** (2 tools) - [`get_network_request`](docs/tool-reference.md#get_network_request) - [`list_network_requests`](docs/tool-reference.md#list_network_requests) -- **Debugging** (4 tools) +- **Debugging** (2 tools) - [`evaluate_script`](docs/tool-reference.md#evaluate_script) - [`list_console_messages`](docs/tool-reference.md#list_console_messages) - - [`take_screenshot`](docs/tool-reference.md#take_screenshot) - - [`take_snapshot`](docs/tool-reference.md#take_snapshot) @@ -314,6 +314,11 @@ The Chrome DevTools MCP server supports the following configuration option: Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp. - **Type:** array +- **`--categories`** + Categories of tools to expose. The `core` category cannot be disabled. + - **Type:** string + - **Default:** `core,input,emulation,performance,network,debugging` + Pass them via the `args` property in the JSON configuration. For example: diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 1836c924c..4afaee14b 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -2,22 +2,24 @@ # Chrome DevTools MCP Tool Reference -- **[Input automation](#input-automation)** (7 tools) - - [`click`](#click) - - [`drag`](#drag) - - [`fill`](#fill) - - [`fill_form`](#fill_form) - - [`handle_dialog`](#handle_dialog) - - [`hover`](#hover) - - [`upload_file`](#upload_file) -- **[Navigation automation](#navigation-automation)** (7 tools) +- **[Core automation](#core-automation)** (10 tools) - [`close_page`](#close_page) + - [`handle_dialog`](#handle_dialog) - [`list_pages`](#list_pages) - [`navigate_page`](#navigate_page) - [`navigate_page_history`](#navigate_page_history) - [`new_page`](#new_page) - [`select_page`](#select_page) + - [`take_screenshot`](#take_screenshot) + - [`take_snapshot`](#take_snapshot) - [`wait_for`](#wait_for) +- **[Input automation](#input-automation)** (6 tools) + - [`click`](#click) + - [`drag`](#drag) + - [`fill`](#fill) + - [`fill_form`](#fill_form) + - [`hover`](#hover) + - [`upload_file`](#upload_file) - **[Emulation](#emulation)** (3 tools) - [`emulate_cpu`](#emulate_cpu) - [`emulate_network`](#emulate_network) @@ -29,160 +31,183 @@ - **[Network](#network)** (2 tools) - [`get_network_request`](#get_network_request) - [`list_network_requests`](#list_network_requests) -- **[Debugging](#debugging)** (4 tools) +- **[Debugging](#debugging)** (2 tools) - [`evaluate_script`](#evaluate_script) - [`list_console_messages`](#list_console_messages) - - [`take_screenshot`](#take_screenshot) - - [`take_snapshot`](#take_snapshot) -## Input automation +## Core automation -### `click` +### `close_page` -**Description:** Clicks on the provided element +**Description:** Closes the page by its index. The last open page cannot be closed. **Parameters:** -- **dblClick** (boolean) _(optional)_: Set to true for double clicks. Default is false. -- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot +- **pageIdx** (number) **(required)**: The index of the page to close. Call [`list_pages`](#list_pages) to list pages. --- -### `drag` +### `handle_dialog` -**Description:** [`Drag`](#drag) an element onto another element +**Description:** If a browser dialog was opened, use this command to handle it **Parameters:** -- **from_uid** (string) **(required)**: The uid of the element to [`drag`](#drag) -- **to_uid** (string) **(required)**: The uid of the element to drop into +- **action** (enum: "accept", "dismiss") **(required)**: Whether to dismiss or accept the dialog +- **promptText** (string) _(optional)_: Optional prompt text to enter into the dialog. --- -### `fill` +### `list_pages` -**Description:** Type text into a input, text area or select an option from a <select> element. +**Description:** Get a list of pages open in the browser. + +**Parameters:** None + +--- + +### `navigate_page` + +**Description:** Navigates the currently selected page to a URL. **Parameters:** -- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot -- **value** (string) **(required)**: The value to [`fill`](#fill) in +- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. +- **url** (string) **(required)**: URL to navigate the page to --- -### `fill_form` +### `navigate_page_history` -**Description:** [`Fill`](#fill) out multiple form elements at once +**Description:** Navigates the currently selected page. **Parameters:** -- **elements** (array) **(required)**: Elements from snapshot to [`fill`](#fill) out. +- **navigate** (enum: "back", "forward") **(required)**: Whether to navigate back or navigate forward in the selected pages history +- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. --- -### `handle_dialog` +### `new_page` -**Description:** If a browser dialog was opened, use this command to handle it +**Description:** Creates a new page **Parameters:** -- **action** (enum: "accept", "dismiss") **(required)**: Whether to dismiss or accept the dialog -- **promptText** (string) _(optional)_: Optional prompt text to enter into the dialog. +- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. +- **url** (string) **(required)**: URL to load in a new page. --- -### `hover` +### `select_page` -**Description:** [`Hover`](#hover) over the provided element +**Description:** Select a page as a context for future tool calls. **Parameters:** -- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot +- **pageIdx** (number) **(required)**: The index of the page to select. Call [`list_pages`](#list_pages) to list pages. --- -### `upload_file` +### `take_screenshot` -**Description:** Upload a file through a provided element. +**Description:** Take a screenshot of the page or element. **Parameters:** -- **filePath** (string) **(required)**: The local path of the file to upload -- **uid** (string) **(required)**: The uid of the file input element or an element that will open file chooser on the page from the page content snapshot +- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response. +- **format** (enum: "png", "jpeg", "webp") _(optional)_: Type of format to save the screenshot as. Default is "png" +- **fullPage** (boolean) _(optional)_: If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid. +- **quality** (number) _(optional)_: Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format. +- **uid** (string) _(optional)_: The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot. --- -## Navigation automation +### `take_snapshot` -### `close_page` +**Description:** Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique +identifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. -**Description:** Closes the page by its index. The last open page cannot be closed. +**Parameters:** + +- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false. + +--- + +### `wait_for` + +**Description:** Wait for the specified text to appear on the selected page. **Parameters:** -- **pageIdx** (number) **(required)**: The index of the page to close. Call [`list_pages`](#list_pages) to list pages. +- **text** (string) **(required)**: Text to appear on the page +- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. --- -### `list_pages` +## Input automation -**Description:** Get a list of pages open in the browser. +### `click` -**Parameters:** None +**Description:** Clicks on the provided element + +**Parameters:** + +- **dblClick** (boolean) _(optional)_: Set to true for double clicks. Default is false. +- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot --- -### `navigate_page` +### `drag` -**Description:** Navigates the currently selected page to a URL. +**Description:** [`Drag`](#drag) an element onto another element **Parameters:** -- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. -- **url** (string) **(required)**: URL to navigate the page to +- **from_uid** (string) **(required)**: The uid of the element to [`drag`](#drag) +- **to_uid** (string) **(required)**: The uid of the element to drop into --- -### `navigate_page_history` +### `fill` -**Description:** Navigates the currently selected page. +**Description:** Type text into a input, text area or select an option from a <select> element. **Parameters:** -- **navigate** (enum: "back", "forward") **(required)**: Whether to navigate back or navigate forward in the selected pages history -- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. +- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot +- **value** (string) **(required)**: The value to [`fill`](#fill) in --- -### `new_page` +### `fill_form` -**Description:** Creates a new page +**Description:** [`Fill`](#fill) out multiple form elements at once **Parameters:** -- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. -- **url** (string) **(required)**: URL to load in a new page. +- **elements** (array) **(required)**: Elements from snapshot to [`fill`](#fill) out. --- -### `select_page` +### `hover` -**Description:** Select a page as a context for future tool calls. +**Description:** [`Hover`](#hover) over the provided element **Parameters:** -- **pageIdx** (number) **(required)**: The index of the page to select. Call [`list_pages`](#list_pages) to list pages. +- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot --- -### `wait_for` +### `upload_file` -**Description:** Wait for the specified text to appear on the selected page. +**Description:** Upload a file through a provided element. **Parameters:** -- **text** (string) **(required)**: Text to appear on the page -- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. +- **filePath** (string) **(required)**: The local path of the file to upload +- **uid** (string) **(required)**: The uid of the file input element or an element that will open file chooser on the page from the page content snapshot --- @@ -303,28 +328,3 @@ so returned values have to JSON-serializable. **Parameters:** None --- - -### `take_screenshot` - -**Description:** Take a screenshot of the page or element. - -**Parameters:** - -- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response. -- **format** (enum: "png", "jpeg", "webp") _(optional)_: Type of format to save the screenshot as. Default is "png" -- **fullPage** (boolean) _(optional)_: If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid. -- **quality** (number) _(optional)_: Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format. -- **uid** (string) _(optional)_: The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot. - ---- - -### `take_snapshot` - -**Description:** Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique -identifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. - -**Parameters:** - -- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false. - ---- diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index aaba46317..7466ab939 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -11,7 +11,7 @@ import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import type {Tool} from '@modelcontextprotocol/sdk/types.js'; import {cliOptions} from '../build/src/cli.js'; -import {ToolCategories} from '../build/src/tools/categories.js'; +import {ToolCategory, labels} from '../build/src/tools/categories.js'; const MCP_SERVER_PATH = 'build/src/index.js'; const OUTPUT_PATH = './docs/tool-reference.md'; @@ -21,7 +21,7 @@ const README_PATH = './README.md'; interface ToolWithAnnotations extends Tool { annotations?: { title?: string; - category?: ToolCategories; + category?: typeof ToolCategory; }; } @@ -67,7 +67,7 @@ function generateToolsTOC( for (const category of sortedCategories) { const categoryTools = categories[category]; - const categoryName = category; + const categoryName = labels[category]; toc += `- **${categoryName}** (${categoryTools.length} tools)\n`; // Sort tools within category for TOC @@ -209,7 +209,7 @@ async function generateToolDocumentation(): Promise { }); // Sort categories using the enum order - const categoryOrder = Object.values(ToolCategories); + const categoryOrder = Object.values(ToolCategory); const sortedCategories = Object.keys(categories).sort((a, b) => { const aIndex = categoryOrder.indexOf(a); const bIndex = categoryOrder.indexOf(b); @@ -223,8 +223,8 @@ async function generateToolDocumentation(): Promise { // Generate table of contents for (const category of sortedCategories) { const categoryTools = categories[category]; - const categoryName = category; - const anchorName = category.toLowerCase().replace(/\s+/g, '-'); + const categoryName = labels[category]; + const anchorName = categoryName.toLowerCase().replace(/\s+/g, '-'); markdown += `- **[${categoryName}](#${anchorName})** (${categoryTools.length} tools)\n`; // Sort tools within category for TOC @@ -239,8 +239,9 @@ async function generateToolDocumentation(): Promise { for (const category of sortedCategories) { const categoryTools = categories[category]; + const categoryName = labels[category]; - markdown += `## ${category}\n\n`; + markdown += `## ${categoryName}\n\n`; // Sort tools within category categoryTools.sort((a: Tool, b: Tool) => a.name.localeCompare(b.name)); diff --git a/src/cli.ts b/src/cli.ts index 513fff338..1a648e65b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,6 +8,8 @@ import type {Options as YargsOptions} from 'yargs'; import yargs from 'yargs'; import {hideBin} from 'yargs/helpers'; +import {ToolCategory} from './tools/categories.js'; + export const cliOptions = { browserUrl: { type: 'string', @@ -91,6 +93,27 @@ export const cliOptions = { describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.', }, + categories: { + type: 'string', + describe: + 'Categories of tools to expose. The `core` category cannot be disabled.', + default: Object.keys(ToolCategory) + .map(item => item.toLowerCase()) + .join(','), + coerce: (arg: string) => { + if (!arg) { + return [ToolCategory.CORE]; + } + const catgories = arg + .split(',') + .map(cat => cat.trim()) + .filter(cat => cat !== ''); + if (!catgories.length) { + return [ToolCategory.CORE]; + } + return catgories; + }, + }, } satisfies Record; export function parseArguments(version: string, argv = process.argv) { diff --git a/src/main.ts b/src/main.ts index d86ad6dc9..4c7b41cc1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -88,10 +88,13 @@ debug, and modify any data in the browser or DevTools. Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`, ); }; - +const enabledCategories = new Set(args.categories); const toolMutex = new Mutex(); function registerTool(tool: ToolDefinition): void { + if (!enabledCategories.has(tool.annotations.category.toLowerCase())) { + return; + } server.registerTool( tool.name, { diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 9ecf49a86..4e680ff85 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -10,14 +10,14 @@ import z from 'zod'; import type {TextSnapshotNode} from '../McpContext.js'; import type {TraceResult} from '../trace-processing/parse.js'; -import type {ToolCategories} from './categories.js'; +import type {ToolCategory} from './categories.js'; export interface ToolDefinition { name: string; description: string; annotations: { title?: string; - category: ToolCategories; + category: ToolCategory; /** * If true, the tool does not modify its environment. */ diff --git a/src/tools/categories.ts b/src/tools/categories.ts index 084be6fef..4344be441 100644 --- a/src/tools/categories.ts +++ b/src/tools/categories.ts @@ -4,11 +4,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum ToolCategories { - INPUT_AUTOMATION = 'Input automation', - NAVIGATION_AUTOMATION = 'Navigation automation', - EMULATION = 'Emulation', - PERFORMANCE = 'Performance', - NETWORK = 'Network', - DEBUGGING = 'Debugging', +export enum ToolCategory { + CORE = 'core', + INPUT = 'input', + EMULATION = 'emulation', + PERFORMANCE = 'performance', + NETWORK = 'network', + DEBUGGING = 'debugging', } + +export const labels = { + [ToolCategory.CORE]: 'Core automation', + [ToolCategory.INPUT]: 'Input automation', + [ToolCategory.EMULATION]: 'Emulation', + [ToolCategory.PERFORMANCE]: 'Performance', + [ToolCategory.NETWORK]: 'Network', + [ToolCategory.DEBUGGING]: 'Debugging', +}; diff --git a/src/tools/console.ts b/src/tools/console.ts index 4fb752f21..289bf8234 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {ToolCategories} from './categories.js'; +import {ToolCategory} from './categories.js'; import {defineTool} from './ToolDefinition.js'; export const consoleTool = defineTool({ @@ -12,7 +12,7 @@ export const consoleTool = defineTool({ description: 'List all console messages for the currently selected page since the last navigation.', annotations: { - category: ToolCategories.DEBUGGING, + category: ToolCategory.DEBUGGING, readOnlyHint: true, }, schema: {}, diff --git a/src/tools/emulation.ts b/src/tools/emulation.ts index 92b949e91..91e7ec10c 100644 --- a/src/tools/emulation.ts +++ b/src/tools/emulation.ts @@ -7,7 +7,7 @@ import {PredefinedNetworkConditions} from 'puppeteer-core'; import z from 'zod'; -import {ToolCategories} from './categories.js'; +import {ToolCategory} from './categories.js'; import {defineTool} from './ToolDefinition.js'; const throttlingOptions: [string, ...string[]] = [ @@ -20,7 +20,7 @@ export const emulateNetwork = defineTool({ name: 'emulate_network', description: `Emulates network conditions such as throttling or offline mode on the selected page.`, annotations: { - category: ToolCategories.EMULATION, + category: ToolCategory.EMULATION, readOnlyHint: false, }, schema: { @@ -66,7 +66,7 @@ export const emulateCpu = defineTool({ name: 'emulate_cpu', description: `Emulates CPU throttling by slowing down the selected page's execution.`, annotations: { - category: ToolCategories.EMULATION, + category: ToolCategory.EMULATION, readOnlyHint: false, }, schema: { diff --git a/src/tools/input.ts b/src/tools/input.ts index 02bb8a0f6..c064b1784 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -9,14 +9,14 @@ import z from 'zod'; import type {McpContext, TextSnapshotNode} from '../McpContext.js'; -import {ToolCategories} from './categories.js'; +import {ToolCategory} from './categories.js'; import {defineTool} from './ToolDefinition.js'; export const click = defineTool({ name: 'click', description: `Clicks on the provided element`, annotations: { - category: ToolCategories.INPUT_AUTOMATION, + category: ToolCategory.INPUT, readOnlyHint: false, }, schema: { @@ -55,7 +55,7 @@ export const hover = defineTool({ name: 'hover', description: `Hover over the provided element`, annotations: { - category: ToolCategories.INPUT_AUTOMATION, + category: ToolCategory.INPUT, readOnlyHint: false, }, schema: { @@ -139,7 +139,7 @@ export const fill = defineTool({ name: 'fill', description: `Type text into a input, text area or select an option from a