From 27b58165e467125c9827bb48a667ea10f58f83f6 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Wed, 25 Mar 2026 14:12:20 +0000 Subject: [PATCH 1/6] 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 ++++++++++++---------- 3 files changed, 42 insertions(+), 34 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 () => { From b0cd0fa77dd25f5e62d8a8e2931ff7fd159b4857 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Wed, 25 Mar 2026 14:12:20 +0000 Subject: [PATCH 2/6] 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 03e4cb205def9d2dfac2b72863e70a66b2ccd0c3 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Fri, 27 Mar 2026 08:09:43 +0000 Subject: [PATCH 3/6] list tools --- README.md | 7 --- docs/tool-reference.md | 14 ----- src/bin/chrome-devtools-mcp-cli-options.ts | 1 - tests/cli.test.ts | 2 - tests/index.test.ts | 73 ++++++++++------------ 5 files changed, 34 insertions(+), 63 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/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 () => { From 1c1796c99d00c2b6208be6707d2bfdb2eedbd89a Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Mon, 30 Mar 2026 13:23:11 +0000 Subject: [PATCH 4/6] execute in-page tool --- src/McpContext.ts | 10 ++ src/McpResponse.ts | 1 + src/third_party/index.ts | 1 + src/tools/ToolDefinition.ts | 6 ++ src/tools/inPage.ts | 64 +++++++++++- tests/tools/inPage.test.ts | 200 +++++++++++++++++++++++++++++++++++- 6 files changed, 277 insertions(+), 5 deletions(-) diff --git a/src/McpContext.ts b/src/McpContext.ts index 3ebbeae95..e5a6c6bcf 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -31,6 +31,7 @@ import type { } from './third_party/index.js'; import {Locator} from './third_party/index.js'; import {PredefinedNetworkConditions} from './third_party/index.js'; +import type {ToolGroup, ToolDefinition} from './tools/inPage.js'; import {listPages} from './tools/pages.js'; import {CLOSE_PAGE_ERROR} from './tools/ToolDefinition.js'; import type {Context, DevToolsData} from './tools/ToolDefinition.js'; @@ -101,6 +102,7 @@ export class McpContext implements Context { #screenRecorderData: {recorder: ScreenRecorder; filePath: string} | null = null; + #inPageTools?: ToolGroup; #nextPageId = 1; #extensionPages = new WeakMap(); @@ -464,6 +466,14 @@ export class McpContext implements Context { this.#updateSelectedPageTimeouts(); } + setInPageTools(toolGroup?: ToolGroup) { + this.#inPageTools = toolGroup; + } + + getInPageTools(): ToolGroup | undefined { + return this.#inPageTools; + } + #updateSelectedPageTimeouts() { const page = this.#getSelectedMcpPage(); // For waiters 5sec timeout should be sufficient. diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 1651c7728..98bf25082 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -422,6 +422,7 @@ export class McpResponse implements Response { let inPageTools: ToolGroup | undefined; if (this.#listInPageTools) { inPageTools = await getToolGroup(context.getSelectedMcpPage()); + context.setInPageTools(inPageTools); } let consoleMessages: Array | undefined; diff --git a/src/third_party/index.ts b/src/third_party/index.ts index 0acd5ca8e..3b1b6ec9d 100644 --- a/src/third_party/index.ts +++ b/src/third_party/index.ts @@ -29,6 +29,7 @@ export { type TextContent, } from '@modelcontextprotocol/sdk/types.js'; export {z as zod} from 'zod'; +export {default as ajv} from 'ajv'; export { Locator, PredefinedNetworkConditions, diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 9a0ef4355..cbf84a853 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -24,6 +24,10 @@ import type {InstalledExtension} from '../utils/ExtensionRegistry.js'; import type {PaginationOptions} from '../utils/types.js'; import type {ToolCategory} from './categories.js'; +import type { + ToolGroup, + ToolDefinition as InPageToolDefinition, +} from './inPage.js'; export interface BaseToolDefinition< Schema extends zod.ZodRawShape = zod.ZodRawShape, @@ -194,6 +198,8 @@ export type Context = Readonly<{ triggerExtensionAction(id: string): Promise; listExtensions(): InstalledExtension[]; getExtension(id: string): InstalledExtension | undefined; + setInPageTools(toolGroup?: ToolGroup): void; + getInPageTools(): ToolGroup | undefined; getSelectedMcpPage(): McpPage; getExtensionServiceWorkers(): ExtensionServiceWorker[]; getExtensionServiceWorkerId( diff --git a/src/tools/inPage.ts b/src/tools/inPage.ts index 2e1deb00b..d19ff16c3 100644 --- a/src/tools/inPage.ts +++ b/src/tools/inPage.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {type JSONSchema7} from '../third_party/index.js'; +import {zod, ajv, type JSONSchema7} from '../third_party/index.js'; import {ToolCategory} from './categories.js'; import {definePageTool} from './ToolDefinition.js'; @@ -37,9 +37,13 @@ declare global { export const listInPageTools = definePageTool({ name: 'list_in_page_tools', - description: `Lists all in-page-tools the page exposes for providing runtime information. - To call 'list_in_page_tools', call 'evaluate_script' with - 'window.__dtmcp.executeTool("list_in_page_tools", {})'.`, + description: `Lists all in-page tools the page exposes for providing runtime information. + In-page tools can be called via the 'execute_in_page_tool()' MCP tool. + Alternatively, in-page tools can be executed by calling 'evaluate_script' and adding the + following command to the script: + 'window.__dtmcp.executeTool(toolName, params)' + This might be helpful when the in-page-tools return non-serializable values or when composing + the in-page-tools with additional functionality.`, annotations: { category: ToolCategory.IN_PAGE, readOnlyHint: true, @@ -50,3 +54,55 @@ export const listInPageTools = definePageTool({ response.setListInPageTools(); }, }); + +export const executeInPageTool = definePageTool({ + name: 'execute_in_page_tool', + description: `Executes a tool exposed by the page.`, + annotations: { + category: ToolCategory.IN_PAGE, + readOnlyHint: false, + conditions: ['inPageTools'], + }, + schema: { + toolName: zod.string().describe('The name of the tool to execute'), + params: zod + .record(zod.string(), zod.unknown()) + .optional() + .describe('The parameters to pass to the tool'), + }, + handler: async (request, response, context) => { + const page = context.getSelectedMcpPage(); + const toolName = request.params.toolName; + const params = request.params.params ?? {}; + + const toolGroup = context.getInPageTools(); + const tool = toolGroup?.tools.find(t => t.name === toolName); + if (!tool) { + throw new Error(`Tool ${toolName} not found`); + } + const ajvInstance = new ajv(); + const validate = ajvInstance.compile(tool.inputSchema); + const valid = validate(params); + if (!valid) { + throw new Error( + `Invalid parameters for tool ${toolName}: ${ajvInstance.errorsText(validate.errors)}`, + ); + } + + const result = await page.pptrPage.evaluate( + async (name, args) => { + if (!window.__dtmcp?.executeTool) { + throw new Error('No tools found on the page'); + } + const toolResult = await window.__dtmcp.executeTool(name, args); + + return { + result: toolResult, + }; + }, + toolName, + params, + ); + response.appendResponseLine(JSON.stringify(result, null, 2)); + }, +}); diff --git a/tests/tools/inPage.test.ts b/tests/tools/inPage.test.ts index 448f6b0ec..531de9c53 100644 --- a/tests/tools/inPage.test.ts +++ b/tests/tools/inPage.test.ts @@ -8,8 +8,10 @@ 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 {McpContext} from '../../src/McpContext.js'; +import type {McpResponse} from '../../src/McpResponse.js'; import type {ToolGroup, ToolDefinition} from '../../src/tools/inPage.js'; -import {listInPageTools} from '../../src/tools/inPage.js'; +import {executeInPageTool, listInPageTools} from '../../src/tools/inPage.js'; import {withMcpContext} from '../utils.js'; describe('inPage', () => { @@ -145,4 +147,200 @@ describe('inPage', () => { ); }); }); + + describe('execute_in_page_tool', () => { + async function setupInPageTools( + response: McpResponse, + context: McpContext, + evaluateFn: () => void, + ) { + const page = await context.newPage(); + await page.pptrPage.evaluate(evaluateFn); + await listInPageTools.handler({params: {}, page}, response, context); + await response.handle('list_in_page_tools', context); + } + + it('executes a tool', async () => { + await withMcpContext( + async (response, context) => { + await setupInPageTools(response, context, () => { + window.__dtmcp = { + toolGroup: { + name: 'test-group', + description: 'test description', + tools: [ + { + name: 'test-tool', + description: 'test tool description', + inputSchema: { + type: 'object', + properties: { + arg: {type: 'string'}, + }, + required: ['arg'], + }, + execute: () => 'result', + }, + ], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e: Event) => { + // @ts-expect-error Event has `respondWith` + e.respondWith(window.__dtmcp?.toolGroup); + }); + }); + + await executeInPageTool.handler( + { + params: { + toolName: 'test-tool', + params: {arg: 'value'}, + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + assert.strictEqual( + response.responseLines[0], + JSON.stringify({result: 'result'}, null, 2), + ); + }, + undefined, + {categoryInPageTools: true} as ParsedArguments, + ); + }); + + it('throws if tool not found in list', async () => { + await withMcpContext(async (response, context) => { + await setupInPageTools(response, context, () => { + window.__dtmcp = { + toolGroup: { + name: 'test-group', + description: 'test description', + tools: [], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e: Event) => { + // @ts-expect-error Event has `respondWith` + e.respondWith(window.__dtmcp?.toolGroup); + }); + }); + + await assert.rejects( + async () => { + await executeInPageTool.handler( + { + params: { + toolName: 'missing-tool', + params: {}, + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + }, + {message: /Tool missing-tool not found/}, + ); + }); + }); + + it('throws if parameters are invalid', async () => { + await withMcpContext( + async (response, context) => { + await setupInPageTools(response, context, () => { + window.__dtmcp = { + toolGroup: { + name: 'test-group', + description: 'test description', + tools: [ + { + name: 'test-tool', + description: 'test tool description', + inputSchema: { + type: 'object', + properties: { + arg: {type: 'string'}, + }, + required: ['arg'], + }, + execute: () => 'result', + }, + ], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e: Event) => { + // @ts-expect-error Event has `respondWith` + e.respondWith(window.__dtmcp?.toolGroup); + }); + }); + + await assert.rejects( + async () => { + await executeInPageTool.handler( + { + params: { + toolName: 'test-tool', + params: {}, // Missing required 'arg' + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + }, + {message: /Invalid parameters for tool test-tool/}, + ); + }, + undefined, + {categoryInPageTools: true} as ParsedArguments, + ); + }); + + it('handles JSON result', async () => { + await withMcpContext( + async (response, context) => { + await setupInPageTools(response, context, () => { + window.__dtmcp = { + toolGroup: { + name: 'test-group', + description: 'test description', + tools: [ + { + name: 'test-tool', + description: 'test tool description', + inputSchema: {}, + execute: () => ({foo: 'bar'}), + }, + ], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e: Event) => { + // @ts-expect-error Event has `respondWith` + e.respondWith(window.__dtmcp?.toolGroup); + }); + }); + + await executeInPageTool.handler( + { + params: { + toolName: 'test-tool', + params: {}, + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + assert.strictEqual( + response.responseLines[0], + JSON.stringify({result: {foo: 'bar'}}, null, 2), + ); + }, + undefined, + {categoryInPageTools: true} as ParsedArguments, + ); + }); + }); }); From d33cf42f5c35ff2f58eac7bab63dec0e5023b272 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Tue, 31 Mar 2026 10:50:57 +0000 Subject: [PATCH 5/6] update license snapshot --- tests/third_party_notices.test.js.snapshot | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/third_party_notices.test.js.snapshot b/tests/third_party_notices.test.js.snapshot index 09c720a3d..c21c4e38f 100644 --- a/tests/third_party_notices.test.js.snapshot +++ b/tests/third_party_notices.test.js.snapshot @@ -765,6 +765,56 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------- DEPENDENCY DIVIDER -------------------- + +Name: uri-js +URL: https://github.com/garycourt/uri-js +Version: +License: BSD-2-Clause + +Copyright 2011 Gary Court. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court. + + +-------------------- DEPENDENCY DIVIDER -------------------- + +Name: fast-json-stable-stringify +URL: https://github.com/epoberezkin/fast-json-stable-stringify +Version: +License: MIT + +This software is released under the MIT license: + +Copyright (c) 2017 Evgeny Poberezkin +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------- DEPENDENCY DIVIDER -------------------- Name: puppeteer-core From b98266b237acc24640c5a334d56b570c6b5dc88b Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer Date: Tue, 31 Mar 2026 16:55:09 +0000 Subject: [PATCH 6/6] address comments --- src/tools/ToolDefinition.ts | 1 - src/tools/inPage.ts | 19 ++++++++++++++++--- src/tools/input.ts | 1 + tests/tools/inPage.test.ts | 8 ++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index cbf84a853..503516ec0 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -198,7 +198,6 @@ export type Context = Readonly<{ triggerExtensionAction(id: string): Promise; listExtensions(): InstalledExtension[]; getExtension(id: string): InstalledExtension | undefined; - setInPageTools(toolGroup?: ToolGroup): void; getInPageTools(): ToolGroup | undefined; getSelectedMcpPage(): McpPage; getExtensionServiceWorkers(): ExtensionServiceWorker[]; diff --git a/src/tools/inPage.ts b/src/tools/inPage.ts index d19ff16c3..f4a3d034b 100644 --- a/src/tools/inPage.ts +++ b/src/tools/inPage.ts @@ -66,14 +66,27 @@ export const executeInPageTool = definePageTool({ schema: { toolName: zod.string().describe('The name of the tool to execute'), params: zod - .record(zod.string(), zod.unknown()) + .string() .optional() - .describe('The parameters to pass to the tool'), + .describe('The JSON-stringified parameters to pass to the tool'), }, handler: async (request, response, context) => { const page = context.getSelectedMcpPage(); const toolName = request.params.toolName; - const params = request.params.params ?? {}; + let params: Record = {}; + if (request.params.params) { + try { + const parsed = JSON.parse(request.params.params); + if (typeof parsed === 'object' && parsed !== null) { + params = parsed; + } else { + throw new Error('Parsed params is not an object'); + } + } catch (e) { + const errorMessage = e instanceof Error ? e.message : String(e); + throw new Error(`Failed to parse params as JSON: ${errorMessage}`); + } + } const toolGroup = context.getInPageTools(); const tool = toolGroup?.tools.find(t => t.name === toolName); diff --git a/src/tools/input.ts b/src/tools/input.ts index 492d55378..059652a17 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -217,6 +217,7 @@ async function fillFormElement( } } +// here export const fill = definePageTool({ name: 'fill', description: `Type text into a input, text area or select an option from a