From 7404c4b33ab40a025cb3dae75c0b2d842f0a4931 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Wed, 25 Mar 2026 14:12:20 +0000 Subject: [PATCH 1/4] tool for listing in-page tools --- src/bin/chrome-devtools-mcp-cli-options.ts | 1 + tests/cli.test.ts | 2 + tests/index.test.ts | 73 ++++++++++++---------- tests/tools/inPage.test.ts | 5 +- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index 80046b115..c2a401943 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -219,6 +219,7 @@ export const cliOptions = { categoryInPageTools: { type: 'boolean', hidden: true, + default: false, describe: 'Set to true to enable tools exposed by the inspected page itself', }, diff --git a/tests/cli.test.ts b/tests/cli.test.ts index b18a4532f..1096b1d44 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -23,6 +23,8 @@ describe('cli args parsing', () => { performanceCrux: true, 'usage-statistics': true, usageStatistics: true, + 'category-in-page-tools': false, + categoryInPageTools: false, }; it('parses with default args', async () => { diff --git a/tests/index.test.ts b/tests/index.test.ts index f08350c08..fb719ccdb 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -72,47 +72,52 @@ describe('e2e', () => { }); it('has all tools', async () => { - await withClient(async client => { - const {tools} = await client.listTools(); - const exposedNames = tools.map(t => t.name).sort(); - const files = fs.readdirSync('build/src/tools'); - const definedNames = []; - for (const file of files) { - if ( - file === 'ToolDefinition.js' || - file === 'tools.js' || - file === 'slim' - ) { - continue; - } - const fileTools = await import(`../src/tools/${file}`); - for (const maybeTool of Object.values(fileTools)) { - if (typeof maybeTool === 'function') { - const tool = (maybeTool as (val: boolean) => ToolDefinition)(false); - if (tool && typeof tool === 'object' && 'name' in tool) { + await withClient( + async client => { + const {tools} = await client.listTools(); + const exposedNames = tools.map(t => t.name).sort(); + const files = fs.readdirSync('build/src/tools'); + const definedNames = []; + for (const file of files) { + if ( + file === 'ToolDefinition.js' || + file === 'tools.js' || + file === 'slim' + ) { + continue; + } + const fileTools = await import(`../src/tools/${file}`); + for (const maybeTool of Object.values(fileTools)) { + if (typeof maybeTool === 'function') { + const tool = (maybeTool as (val: boolean) => ToolDefinition)( + false, + ); + if (tool && typeof tool === 'object' && 'name' in tool) { + if (tool.annotations?.conditions) { + continue; + } + definedNames.push(tool.name); + } + continue; + } + if ( + typeof maybeTool === 'object' && + maybeTool !== null && + 'name' in maybeTool + ) { + const tool = maybeTool as ToolDefinition; if (tool.annotations?.conditions) { continue; } definedNames.push(tool.name); } - continue; - } - if ( - typeof maybeTool === 'object' && - maybeTool !== null && - 'name' in maybeTool - ) { - const tool = maybeTool as ToolDefinition; - if (tool.annotations?.conditions) { - continue; - } - definedNames.push(tool.name); } } - } - definedNames.sort(); - assert.deepStrictEqual(exposedNames, definedNames); - }); + definedNames.sort(); + assert.deepStrictEqual(exposedNames, definedNames); + }, + ['--category-in-page-tools'], + ); }); it('has experimental in-Page tools', async () => { diff --git a/tests/tools/inPage.test.ts b/tests/tools/inPage.test.ts index 79540811f..87bba09d2 100644 --- a/tests/tools/inPage.test.ts +++ b/tests/tools/inPage.test.ts @@ -8,6 +8,7 @@ import assert from 'node:assert'; import {describe, it} from 'node:test'; import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; +import type {ToolGroup, ToolDefinition} from '../../src/tools/inPage.js'; import {listInPageTools} from '../../src/tools/inPage.js'; import {withMcpContext} from '../utils.js'; @@ -85,7 +86,7 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.deepEqual( - (result.structuredContent as {inPageTools: undefined}).inPageTools, + (result.structuredContent as {inPageTools: ToolGroup}).inPageTools, {}, ); }, @@ -109,7 +110,7 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.strictEqual( - (result.structuredContent as {inPageTools: undefined}).inPageTools, + (result.structuredContent as {inPageTools: ToolGroup}).inPageTools, undefined, ); }, From faad89bbaa0d198ad176575bc268fd9c5bcd8d6d Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Wed, 25 Mar 2026 14:12:20 +0000 Subject: [PATCH 2/4] tool for listing in-page tools --- README.md | 7 +++++++ docs/tool-reference.md | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/README.md b/README.md index 438024c3e..c75ec3077 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,8 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles - [`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) +- **In-page tools** (1 tools) + - [`list_in_page_tools`](docs/tool-reference.md#list_in_page_tools) @@ -566,6 +568,11 @@ The Chrome DevTools MCP server supports the following configuration option: - **Type:** boolean - **Default:** `true` +- **`--categoryInPageTools`/ `--category-in-page-tools`** + Set to true to enable tools exposed by the inspected page itself + - **Type:** boolean + - **Default:** `false` + - **`--performanceCrux`/ `--performance-crux`** Set to false to disable sending URLs from performance traces to CrUX API to get field performance data. - **Type:** boolean diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 50b4c02c5..6697b3dd0 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -37,6 +37,8 @@ - [`list_console_messages`](#list_console_messages) - [`take_screenshot`](#take_screenshot) - [`take_snapshot`](#take_snapshot) +- **[In-page tools](#in-page-tools)** (1 tools) + - [`list_in_page_tools`](#list_in_page_tools) ## Input automation @@ -397,3 +399,15 @@ in the DevTools Elements panel (if any). - **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false. --- + +## In-page tools + +### `list_in_page_tools` + +**Description:** Lists all in-page-tools the page exposes for providing runtime information. +In-page-tools are exposed on the page via the 'window.\_\_dtmcp.executeTool(toolName, params)' +function where they can be called by '[`evaluate_script`](#evaluate_script)'. + +**Parameters:** None + +--- From cc97b29f884c91c51abd6953532ee8293d590679 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Fri, 27 Mar 2026 08:09:43 +0000 Subject: [PATCH 3/4] list tools --- README.md | 7 -- docs/tool-reference.md | 14 ---- src/bin/chrome-devtools-mcp-cli-options.ts | 1 - src/tools/pages.ts | 4 + tests/McpResponse.test.ts | 97 ++++++++++++++++++++++ tests/cli.test.ts | 2 - tests/index.test.ts | 73 ++++++++-------- tests/tools/inPage.test.ts | 12 ++- 8 files changed, 145 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index c75ec3077..438024c3e 100644 --- a/README.md +++ b/README.md @@ -475,8 +475,6 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles - [`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) -- **In-page tools** (1 tools) - - [`list_in_page_tools`](docs/tool-reference.md#list_in_page_tools) @@ -568,11 +566,6 @@ The Chrome DevTools MCP server supports the following configuration option: - **Type:** boolean - **Default:** `true` -- **`--categoryInPageTools`/ `--category-in-page-tools`** - Set to true to enable tools exposed by the inspected page itself - - **Type:** boolean - - **Default:** `false` - - **`--performanceCrux`/ `--performance-crux`** Set to false to disable sending URLs from performance traces to CrUX API to get field performance data. - **Type:** boolean diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 6697b3dd0..50b4c02c5 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -37,8 +37,6 @@ - [`list_console_messages`](#list_console_messages) - [`take_screenshot`](#take_screenshot) - [`take_snapshot`](#take_snapshot) -- **[In-page tools](#in-page-tools)** (1 tools) - - [`list_in_page_tools`](#list_in_page_tools) ## Input automation @@ -399,15 +397,3 @@ in the DevTools Elements panel (if any). - **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false. --- - -## In-page tools - -### `list_in_page_tools` - -**Description:** Lists all in-page-tools the page exposes for providing runtime information. -In-page-tools are exposed on the page via the 'window.\_\_dtmcp.executeTool(toolName, params)' -function where they can be called by '[`evaluate_script`](#evaluate_script)'. - -**Parameters:** None - ---- diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index c2a401943..80046b115 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -219,7 +219,6 @@ export const cliOptions = { categoryInPageTools: { type: 'boolean', hidden: true, - default: false, describe: 'Set to true to enable tools exposed by the inspected page itself', }, diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 5e5d3cef7..5020d19df 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -27,6 +27,7 @@ export const listPages = defineTool(args => { schema: {}, handler: async (_request, response) => { response.setIncludePages(true); + response.setListInPageTools(); }, }; }); @@ -53,6 +54,7 @@ export const selectPage = defineTool({ const page = context.getPageById(request.params.pageId); context.selectPage(page); response.setIncludePages(true); + response.setListInPageTools(); if (request.params.bringToFront) { await page.pptrPage.bringToFront(); } @@ -82,6 +84,7 @@ export const closePage = defineTool({ } } response.setIncludePages(true); + response.setListInPageTools(); }, }); @@ -275,6 +278,7 @@ export const navigatePage = definePageTool({ } response.setIncludePages(true); + response.setListInPageTools(); }, }); diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index dbdb9c571..cc089c9b4 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -13,6 +13,14 @@ import {describe, it} from 'node:test'; import sinon from 'sinon'; import type {ParsedArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js'; +import type {McpContext} from '../src/McpContext.js'; +import type {McpResponse} from '../src/McpResponse.js'; +import { + closePage, + listPages, + navigatePage, + selectPage, +} from '../src/tools/pages.js'; import type {InsightName} from '../src/trace-processing/parse.js'; import { parseRawTraceBuffer, @@ -1091,4 +1099,93 @@ describe('inPage tools', () => { {categoryInPageTools: true} as ParsedArguments, ); }); + + async function testIncludesInPageTools( + handlerAction: ( + response: McpResponse, + context: McpContext, + ) => Promise, + toolName: string, + ) { + await withMcpContext( + async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + stubToolDiscovery(mcpPage.pptrPage); + + const initScript = ` + window.__dtmcp = { + toolGroup: { + name: 'In-Page group', + description: 'Test tools', + tools: [ + { + name: 'inPageTool', + description: 'A test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + execute: () => 'result', + }, + ], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e) => { + e.respondWith(window.__dtmcp?.toolGroup); + }); + `; + await mcpPage.pptrPage.evaluateOnNewDocument(initScript); + await mcpPage.pptrPage.evaluate(initScript); + + await handlerAction(response, context); + + const {content} = await response.handle(toolName, context); + const responseText = getTextContent(content[0]); + assert.ok( + responseText.includes('inPageTool'), + `Should include in-page tool name in the ${toolName} response`, + ); + }, + undefined, + {categoryInPageTools: true} as ParsedArguments, + ); + } + + it('includes in-page tools in list_pages response', async () => { + await testIncludesInPageTools(async (response, context) => { + const listPagesDef = listPages({ + categoryInPageTools: true, + } as ParsedArguments); + await listPagesDef.handler({params: {}}, response, context); + }, 'list_pages'); + }); + + it('includes in-page tools in select_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + const pageId = + context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1; + await selectPage.handler({params: {pageId}}, response, context); + }, 'select_page'); + }); + + it('includes in-page tools in close_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + const pageId = + context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1; + await closePage.handler({params: {pageId}}, response, context); + }, 'close_page'); + }); + + it('includes in-page tools in navigate_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + await navigatePage.handler( + { + params: {type: 'url', url: 'about:blank'}, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + }, 'navigate_page'); + }); }); diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 1096b1d44..b18a4532f 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -23,8 +23,6 @@ describe('cli args parsing', () => { performanceCrux: true, 'usage-statistics': true, usageStatistics: true, - 'category-in-page-tools': false, - categoryInPageTools: false, }; it('parses with default args', async () => { diff --git a/tests/index.test.ts b/tests/index.test.ts index fb719ccdb..f08350c08 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -72,52 +72,47 @@ describe('e2e', () => { }); it('has all tools', async () => { - await withClient( - async client => { - const {tools} = await client.listTools(); - const exposedNames = tools.map(t => t.name).sort(); - const files = fs.readdirSync('build/src/tools'); - const definedNames = []; - for (const file of files) { - if ( - file === 'ToolDefinition.js' || - file === 'tools.js' || - file === 'slim' - ) { - continue; - } - const fileTools = await import(`../src/tools/${file}`); - for (const maybeTool of Object.values(fileTools)) { - if (typeof maybeTool === 'function') { - const tool = (maybeTool as (val: boolean) => ToolDefinition)( - false, - ); - if (tool && typeof tool === 'object' && 'name' in tool) { - if (tool.annotations?.conditions) { - continue; - } - definedNames.push(tool.name); - } - continue; - } - if ( - typeof maybeTool === 'object' && - maybeTool !== null && - 'name' in maybeTool - ) { - const tool = maybeTool as ToolDefinition; + await withClient(async client => { + const {tools} = await client.listTools(); + const exposedNames = tools.map(t => t.name).sort(); + const files = fs.readdirSync('build/src/tools'); + const definedNames = []; + for (const file of files) { + if ( + file === 'ToolDefinition.js' || + file === 'tools.js' || + file === 'slim' + ) { + continue; + } + const fileTools = await import(`../src/tools/${file}`); + for (const maybeTool of Object.values(fileTools)) { + if (typeof maybeTool === 'function') { + const tool = (maybeTool as (val: boolean) => ToolDefinition)(false); + if (tool && typeof tool === 'object' && 'name' in tool) { if (tool.annotations?.conditions) { continue; } definedNames.push(tool.name); } + continue; + } + if ( + typeof maybeTool === 'object' && + maybeTool !== null && + 'name' in maybeTool + ) { + const tool = maybeTool as ToolDefinition; + if (tool.annotations?.conditions) { + continue; + } + definedNames.push(tool.name); } } - definedNames.sort(); - assert.deepStrictEqual(exposedNames, definedNames); - }, - ['--category-in-page-tools'], - ); + } + definedNames.sort(); + assert.deepStrictEqual(exposedNames, definedNames); + }); }); it('has experimental in-Page tools', async () => { diff --git a/tests/tools/inPage.test.ts b/tests/tools/inPage.test.ts index 87bba09d2..448f6b0ec 100644 --- a/tests/tools/inPage.test.ts +++ b/tests/tools/inPage.test.ts @@ -86,7 +86,11 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.deepEqual( - (result.structuredContent as {inPageTools: ToolGroup}).inPageTools, + ( + result.structuredContent as { + inPageTools: ToolGroup; + } + ).inPageTools, {}, ); }, @@ -110,7 +114,11 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.strictEqual( - (result.structuredContent as {inPageTools: ToolGroup}).inPageTools, + ( + result.structuredContent as { + inPageTools: ToolGroup; + } + ).inPageTools, undefined, ); }, From ad6704343196f2f2413a05b3747af6be9ef98dce Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Mon, 30 Mar 2026 13:44:56 +0000 Subject: [PATCH 4/4] Add list of in-page tools to `new_page` result --- src/tools/pages.ts | 1 + tests/McpResponse.test.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 5020d19df..d158c1c7b 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -129,6 +129,7 @@ export const newPage = defineTool({ ); response.setIncludePages(true); + response.setListInPageTools(); }, }); diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index cc089c9b4..66be61e84 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -19,6 +19,7 @@ import { closePage, listPages, navigatePage, + newPage, selectPage, } from '../src/tools/pages.js'; import type {InsightName} from '../src/trace-processing/parse.js'; @@ -1188,4 +1189,19 @@ describe('inPage tools', () => { ); }, 'navigate_page'); }); + + it('includes in-page tools in new_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + // Workaround to ensure the test environment's new page contain in-page tools + sinon.stub(context, 'newPage').resolves(context.getSelectedMcpPage()); + + await newPage.handler( + { + params: {url: 'about:blank'}, + }, + response, + context, + ); + }, 'new_page'); + }); });