feat: allow configuring tool categories (#454)

This PR introduces a feature to disable some of the MCP server tool
categories to tailor it to specific use cases.

For example,

```
npx chrome-devtools-mcp@latest --no-category-emulation        Disable tools in the emulation category
npx chrome-devtools-mcp@latest --no-category-performance      Disable tools in the performance category
npx chrome-devtools-mcp@latest --no-category-network          Disable tools in the network category
```
This commit is contained in:
Alex Rudenko authored and GitHub committed 2025-10-22 12:32:16 +02:00
1 parent 6f24362039
commit 0fe2b8a2b4
16 files changed
+152 -52

No files matched your search

+15
View File
@@ -331,6 +331,21 @@ 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
- **`--categoryEmulation`**
Set to false to exlcude tools related to emulation.
- **Type:** boolean
- **Default:** `true`
- **`--categoryPerformance`**
Set to false to exlcude tools related to performance.
- **Type:** boolean
- **Default:** `true`
- **`--categoryNetwork`**
Set to false to exlcude tools related to network.
- **Type:** boolean
- **Default:** `true`
<!-- END AUTO GENERATED OPTIONS -->
Pass them via the `args` property in the JSON configuration. For example:
+8 -7
View File
@@ -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<void> {
});
// 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<void> {
// 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<void> {
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));
+21
View File
@@ -139,6 +139,21 @@ export const cliOptions = {
describe:
'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
},
categoryEmulation: {
type: 'boolean',
default: true,
describe: 'Set to false to exlcude tools related to emulation.',
},
categoryPerformance: {
type: 'boolean',
default: true,
describe: 'Set to false to exlcude tools related to performance.',
},
categoryNetwork: {
type: 'boolean',
default: true,
describe: 'Set to false to exlcude tools related to network.',
},
} satisfies Record<string, YargsOptions>;
export function parseArguments(version: string, argv = process.argv) {
@@ -185,6 +200,12 @@ export function parseArguments(version: string, argv = process.argv) {
`$0 --chrome-arg='--no-sandbox' --chrome-arg='--disable-setuid-sandbox'`,
'Launch Chrome without sandboxes. Use with caution.',
],
['$0 --no-category-emulation', 'Disable tools in the emulation category'],
[
'$0 --no-category-performance',
'Disable tools in the performance category',
],
['$0 --no-category-network', 'Disable tools in the network category'],
]);
return yargsInstance
+19
View File
@@ -19,6 +19,7 @@ import {
type CallToolResult,
SetLevelRequestSchema,
} from './third_party/index.js';
import {ToolCategory} from './tools/categories.js';
import * as consoleTools from './tools/console.js';
import * as emulationTools from './tools/emulation.js';
import * as inputTools from './tools/input.js';
@@ -96,6 +97,24 @@ Avoid sharing sensitive or personal information that you do not want to share wi
const toolMutex = new Mutex();
function registerTool(tool: ToolDefinition): void {
if (
tool.annotations.category === ToolCategory.EMULATION &&
args.categoryEmulation === false
) {
return;
}
if (
tool.annotations.category === ToolCategory.PERFORMANCE &&
args.categoryPerformance === false
) {
return;
}
if (
tool.annotations.category === ToolCategory.NETWORK &&
args.categoryNetwork === false
) {
return;
}
server.registerTool(
tool.name,
{
+2 -2
View File
@@ -10,7 +10,7 @@ import type {Dialog, ElementHandle, Page} from '../third_party/index.js';
import type {TraceResult} from '../trace-processing/parse.js';
import type {PaginationOptions} from '../utils/types.js';
import type {ToolCategories} from './categories.js';
import type {ToolCategory} from './categories.js';
export interface ToolDefinition<
Schema extends zod.ZodRawShape = zod.ZodRawShape,
@@ -19,7 +19,7 @@ export interface ToolDefinition<
description: string;
annotations: {
title?: string;
category: ToolCategories;
category: ToolCategory;
/**
* If true, the tool does not modify its environment.
*/
+16 -7
View File
@@ -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 {
INPUT = 'input',
NAVIGATION = 'navigation',
EMULATION = 'emulation',
PERFORMANCE = 'performance',
NETWORK = 'network',
DEBUGGING = 'debugging',
}
export const labels = {
[ToolCategory.INPUT]: 'Input automation',
[ToolCategory.NAVIGATION]: 'Navigation automation',
[ToolCategory.EMULATION]: 'Emulation',
[ToolCategory.PERFORMANCE]: 'Performance',
[ToolCategory.NETWORK]: 'Network',
[ToolCategory.DEBUGGING]: 'Debugging',
};
+3 -3
View File
@@ -7,7 +7,7 @@
import {zod} from '../third_party/index.js';
import type {ConsoleMessageType} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';
const FILTERABLE_MESSAGE_TYPES: readonly [
@@ -40,7 +40,7 @@ export const listConsoleMessages = 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: {
@@ -86,7 +86,7 @@ export const getConsoleMessage = defineTool({
name: 'get_console_message',
description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`,
annotations: {
category: ToolCategories.DEBUGGING,
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
},
schema: {
+3 -3
View File
@@ -6,7 +6,7 @@
import {zod, PredefinedNetworkConditions} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';
const throttlingOptions: [string, ...string[]] = [
@@ -19,7 +19,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: {
@@ -65,7 +65,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: {
+7 -7
View File
@@ -8,14 +8,14 @@ import type {McpContext, TextSnapshotNode} from '../McpContext.js';
import {zod} from '../third_party/index.js';
import type {ElementHandle} from '../third_party/index.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: {
@@ -54,7 +54,7 @@ export const hover = defineTool({
name: 'hover',
description: `Hover over the provided element`,
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
@@ -138,7 +138,7 @@ export const fill = defineTool({
name: 'fill',
description: `Type text into a input, text area or select an option from a <select> element.`,
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
@@ -166,7 +166,7 @@ export const drag = defineTool({
name: 'drag',
description: `Drag an element onto another element`,
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
@@ -195,7 +195,7 @@ export const fillForm = defineTool({
name: 'fill_form',
description: `Fill out multiple form elements at once`,
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
@@ -227,7 +227,7 @@ export const uploadFile = defineTool({
name: 'upload_file',
description: 'Upload a file through a provided element.',
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
+3 -3
View File
@@ -7,7 +7,7 @@
import {zod} from '../third_party/index.js';
import type {ResourceType} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';
const FILTERABLE_RESOURCE_TYPES: readonly [ResourceType, ...ResourceType[]] = [
@@ -36,7 +36,7 @@ export const listNetworkRequests = defineTool({
name: 'list_network_requests',
description: `List all requests for the currently selected page since the last navigation.`,
annotations: {
category: ToolCategories.NETWORK,
category: ToolCategory.NETWORK,
readOnlyHint: true,
},
schema: {
@@ -82,7 +82,7 @@ export const getNetworkRequest = defineTool({
name: 'get_network_request',
description: `Gets a network request by URL. You can get all requests by calling ${listNetworkRequests.name}.`,
annotations: {
category: ToolCategories.NETWORK,
category: ToolCategory.NETWORK,
readOnlyHint: true,
},
schema: {
+9 -9
View File
@@ -7,14 +7,14 @@
import {logger} from '../logger.js';
import {zod} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {CLOSE_PAGE_ERROR, defineTool, timeoutSchema} from './ToolDefinition.js';
export const listPages = defineTool({
name: 'list_pages',
description: `Get a list of pages open in the browser.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: true,
},
schema: {},
@@ -27,7 +27,7 @@ export const selectPage = defineTool({
name: 'select_page',
description: `Select a page as a context for future tool calls.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: true,
},
schema: {
@@ -49,7 +49,7 @@ export const closePage = defineTool({
name: 'close_page',
description: `Closes the page by its index. The last open page cannot be closed.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: false,
},
schema: {
@@ -77,7 +77,7 @@ export const newPage = defineTool({
name: 'new_page',
description: `Creates a new page`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: false,
},
schema: {
@@ -101,7 +101,7 @@ export const navigatePage = defineTool({
name: 'navigate_page',
description: `Navigates the currently selected page to a URL.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: false,
},
schema: {
@@ -125,7 +125,7 @@ export const navigatePageHistory = defineTool({
name: 'navigate_page_history',
description: `Navigates the currently selected page.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: false,
},
schema: {
@@ -162,7 +162,7 @@ export const resizePage = defineTool({
name: 'resize_page',
description: `Resizes the selected page's window so that the page has specified dimension`,
annotations: {
category: ToolCategories.EMULATION,
category: ToolCategory.EMULATION,
readOnlyHint: false,
},
schema: {
@@ -186,7 +186,7 @@ export const handleDialog = defineTool({
name: 'handle_dialog',
description: `If a browser dialog was opened, use this command to handle it`,
annotations: {
category: ToolCategories.INPUT_AUTOMATION,
category: ToolCategory.INPUT,
readOnlyHint: false,
},
schema: {
+4 -4
View File
@@ -15,7 +15,7 @@ import {
traceResultIsSuccess,
} from '../trace-processing/parse.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import type {Context, Response} from './ToolDefinition.js';
import {defineTool} from './ToolDefinition.js';
@@ -24,7 +24,7 @@ export const startTrace = defineTool({
description:
'Starts a performance trace recording on the selected page. This can be used to look for performance problems and insights to improve the performance of the page. It will also report Core Web Vital (CWV) scores for the page.',
annotations: {
category: ToolCategories.PERFORMANCE,
category: ToolCategory.PERFORMANCE,
readOnlyHint: true,
},
schema: {
@@ -105,7 +105,7 @@ export const stopTrace = defineTool({
description:
'Stops the active performance trace recording on the selected page.',
annotations: {
category: ToolCategories.PERFORMANCE,
category: ToolCategory.PERFORMANCE,
readOnlyHint: true,
},
schema: {},
@@ -123,7 +123,7 @@ export const analyzeInsight = defineTool({
description:
'Provides more detailed information on a specific Performance Insight that was highlighted in the results of a trace recording.',
annotations: {
category: ToolCategories.PERFORMANCE,
category: ToolCategory.PERFORMANCE,
readOnlyHint: true,
},
schema: {
+2 -2
View File
@@ -7,14 +7,14 @@
import {zod} from '../third_party/index.js';
import type {ElementHandle, Page} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';
export const screenshot = defineTool({
name: 'take_screenshot',
description: `Take a screenshot of the page or element.`,
annotations: {
category: ToolCategories.DEBUGGING,
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
},
schema: {
+2 -2
View File
@@ -7,7 +7,7 @@
import {zod} from '../third_party/index.js';
import type {Frame, JSHandle, Page} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';
export const evaluateScript = defineTool({
@@ -15,7 +15,7 @@ export const evaluateScript = defineTool({
description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON
so returned values have to JSON-serializable.`,
annotations: {
category: ToolCategories.DEBUGGING,
category: ToolCategory.DEBUGGING,
readOnlyHint: false,
},
schema: {
+3 -3
View File
@@ -6,7 +6,7 @@
import {zod} from '../third_party/index.js';
import {ToolCategories} from './categories.js';
import {ToolCategory} from './categories.js';
import {defineTool, timeoutSchema} from './ToolDefinition.js';
export const takeSnapshot = defineTool({
@@ -14,7 +14,7 @@ export const takeSnapshot = defineTool({
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.`,
annotations: {
category: ToolCategories.DEBUGGING,
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
},
schema: {
@@ -34,7 +34,7 @@ export const waitFor = defineTool({
name: 'wait_for',
description: `Wait for the specified text to appear on the selected page.`,
annotations: {
category: ToolCategories.NAVIGATION_AUTOMATION,
category: ToolCategory.NAVIGATION,
readOnlyHint: true,
},
schema: {
+35
View File
@@ -9,9 +9,19 @@ import {describe, it} from 'node:test';
import {parseArguments} from '../src/cli.js';
describe('cli args parsing', () => {
const defaultArgs = {
'category-emulation': true,
categoryEmulation: true,
'category-performance': true,
categoryPerformance: true,
'category-network': true,
categoryNetwork: true,
};
it('parses with default args', async () => {
const args = parseArguments('1.0.0', ['node', 'main.js']);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -28,6 +38,7 @@ describe('cli args parsing', () => {
'http://localhost:3000',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -46,6 +57,7 @@ describe('cli args parsing', () => {
'',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -65,6 +77,7 @@ describe('cli args parsing', () => {
'/tmp/test 123/chrome',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -83,6 +96,7 @@ describe('cli args parsing', () => {
'888x777',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -103,6 +117,7 @@ describe('cli args parsing', () => {
`--chrome-arg='--disable-setuid-sandbox'`,
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -121,6 +136,7 @@ describe('cli args parsing', () => {
'ws://127.0.0.1:9222/devtools/browser/abc123',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -139,6 +155,7 @@ describe('cli args parsing', () => {
'wss://example.com:9222/devtools/browser/abc123',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
@@ -163,4 +180,22 @@ describe('cli args parsing', () => {
'X-Custom': 'value',
});
});
it('parses disabled category', async () => {
const args = parseArguments('1.0.0', [
'node',
'main.js',
'--no-category-emulation',
]);
assert.deepStrictEqual(args, {
...defaultArgs,
_: [],
headless: false,
isolated: false,
$0: 'npx chrome-devtools-mcp@latest',
channel: 'stable',
'category-emulation': false,
categoryEmulation: false,
});
});
});