Skip to content

Commit 0667028

Browse files
committed
tool for listing in-page tools
1 parent b2b5e3f commit 0667028

14 files changed

Lines changed: 397 additions & 36 deletions

src/McpResponse.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
1212
import type {McpContext} from './McpContext.js';
1313
import type {McpPage} from './McpPage.js';
1414
import {UncaughtError} from './PageCollector.js';
15-
import {DevTools} from './third_party/index.js';
15+
import {DevTools, type Protocol} from './third_party/index.js';
1616
import type {
1717
ConsoleMessage,
1818
ImageContent,
1919
Page,
2020
ResourceType,
2121
TextContent,
2222
} from './third_party/index.js';
23+
import type {ToolGroup} from './tools/inPage.js';
2324
import {handleDialog} from './tools/pages.js';
2425
import type {
2526
DevToolsData,
@@ -40,6 +41,57 @@ interface TraceInsightData {
4041
insightName: InsightName;
4142
}
4243

44+
async function getToolGroup(page: McpPage): Promise<ToolGroup | undefined> {
45+
// Check if there is a `devtoolstooldiscovery` event listener
46+
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
47+
// @ts-expect-error internal API
48+
const client = page.pptrPage._client();
49+
const {listeners}: {listeners: Protocol.DOMDebugger.EventListener[]} =
50+
await client.send('DOMDebugger.getEventListeners', {
51+
objectId: windowHandle.remoteObject().objectId,
52+
});
53+
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
54+
return;
55+
}
56+
57+
const toolGroup = await page.pptrPage.evaluate(() => {
58+
return new Promise<ToolGroup | undefined>(resolve => {
59+
const event = new CustomEvent('devtoolstooldiscovery');
60+
// @ts-expect-error Adding custom property
61+
event.respondWith = (toolGroup: ToolGroup) => {
62+
if (!window.__dtmcp) {
63+
window.__dtmcp = {};
64+
}
65+
window.__dtmcp.toolGroup = toolGroup;
66+
67+
// When receiving a toolGroup for the first time, expose a simple execution helper
68+
if (!window.__dtmcp.executeTool) {
69+
window.__dtmcp.executeTool = async (toolName, args) => {
70+
if (!window.__dtmcp?.toolGroup) {
71+
throw new Error('No tools found on the page');
72+
}
73+
const tool = window.__dtmcp.toolGroup.tools.find(
74+
t => t.name === toolName,
75+
);
76+
if (!tool) {
77+
throw new Error(`Tool ${toolName} not found`);
78+
}
79+
return await tool.execute(args);
80+
};
81+
}
82+
83+
resolve(toolGroup);
84+
};
85+
window.dispatchEvent(event);
86+
// If the page does not call `event.respondWith`, return instead of timing out
87+
setTimeout(() => {
88+
resolve(undefined);
89+
}, 0);
90+
});
91+
});
92+
return toolGroup;
93+
}
94+
4395
export class McpResponse implements Response {
4496
#includePages = false;
4597
#includeExtensionServiceWorkers = false;
@@ -70,6 +122,7 @@ export class McpResponse implements Response {
70122
includePreservedMessages?: boolean;
71123
};
72124
#listExtensions?: boolean;
125+
#listInPageTools?: boolean;
73126
#devToolsData?: DevToolsData;
74127
#tabId?: string;
75128
#args: ParsedArguments;
@@ -110,6 +163,12 @@ export class McpResponse implements Response {
110163
this.#listExtensions = true;
111164
}
112165

166+
setListInPageTools(): void {
167+
if (this.#args.categoryInPageTools) {
168+
this.#listInPageTools = true;
169+
}
170+
}
171+
113172
setIncludeNetworkRequests(
114173
value: boolean,
115174
options?: PaginationOptions & {
@@ -357,6 +416,12 @@ export class McpResponse implements Response {
357416
if (this.#listExtensions) {
358417
extensions = context.listExtensions();
359418
}
419+
420+
let inPageTools: ToolGroup | undefined;
421+
if (this.#listInPageTools) {
422+
inPageTools = await getToolGroup(context.getSelectedMcpPage());
423+
}
424+
360425
let consoleMessages: Array<ConsoleFormatter | IssueFormatter> | undefined;
361426
if (this.#consoleDataOptions?.include) {
362427
if (!this.#page) {
@@ -459,6 +524,7 @@ export class McpResponse implements Response {
459524
traceSummary: this.#attachedTraceSummary,
460525
extensions,
461526
lighthouseResult: this.#attachedLighthouseResult,
527+
inPageTools,
462528
});
463529
}
464530

@@ -475,6 +541,7 @@ export class McpResponse implements Response {
475541
traceInsight?: TraceInsightData;
476542
extensions?: InstalledExtension[];
477543
lighthouseResult?: LighthouseData;
544+
inPageTools?: ToolGroup;
478545
},
479546
): {content: Array<TextContent | ImageContent>; structuredContent: object} {
480547
const structuredContent: {
@@ -489,6 +556,7 @@ export class McpResponse implements Response {
489556
traceInsights?: Array<{insightName: string; insightKey: string}>;
490557
lighthouseResult?: object;
491558
extensions?: object[];
559+
inPageTools?: object;
492560
message?: string;
493561
networkConditions?: string;
494562
navigationTimeout?: number;
@@ -726,6 +794,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
726794
}
727795
}
728796

797+
if (this.#listInPageTools) {
798+
structuredContent.inPageTools = data.inPageTools ?? undefined;
799+
response.push('## In-page tools');
800+
if (!data.inPageTools) {
801+
response.push('No in-page tools available.');
802+
} else {
803+
const toolGroup = data.inPageTools;
804+
response.push(`${toolGroup.name}: ${toolGroup.description}`);
805+
response.push('Available tools:');
806+
const toolDefinitionsMessage = toolGroup.tools
807+
.map(tool => {
808+
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(
809+
tool.inputSchema,
810+
)}`;
811+
})
812+
.join('\n');
813+
response.push(toolDefinitionsMessage);
814+
}
815+
}
816+
729817
if (this.#networkRequestsOptions?.include && data.networkRequests) {
730818
const requests = data.networkRequests;
731819

src/bin/chrome-devtools-mcp-cli-options.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ export const cliOptions = {
216216
describe:
217217
'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
218218
},
219+
categoryInPageTools: {
220+
type: 'boolean',
221+
default: false,
222+
describe:
223+
'Set to true to enable tools exposed by the inspected page itself',
224+
},
219225
performanceCrux: {
220226
type: 'boolean',
221227
default: true,

src/bin/chrome-devtools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ delete startCliOptions.autoConnect;
4141
// Missing CLI serialization.
4242
delete startCliOptions.viewport;
4343
// CLI is generated based on the default tool definitions. To enable conditional
44-
// tools, they needs to be enabled during CLI generation.
44+
// tools, they need to be enabled during CLI generation.
4545
delete startCliOptions.experimentalPageIdRouting;
4646
delete startCliOptions.experimentalVision;
4747
delete startCliOptions.experimentalInteropTools;

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ export async function createMcpServer(
140140
) {
141141
return;
142142
}
143+
if (
144+
tool.annotations.category === ToolCategory.IN_PAGE &&
145+
!serverArgs.categoryInPageTools
146+
) {
147+
return;
148+
}
143149
if (
144150
tool.annotations.conditions?.includes('computerVision') &&
145151
!serverArgs.experimentalVision

src/third_party/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export {default as puppeteer} from 'puppeteer-core';
3939
export type * from 'puppeteer-core';
4040
export {PipeTransport} from 'puppeteer-core/internal/node/PipeTransport.js';
4141
export type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js';
42+
export type {JSONSchema7} from 'json-schema';
4243
export {
4344
resolveDefaultUserDataDir,
4445
detectBrowserPlatform,

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export interface Response {
129129
): void;
130130
setListExtensions(): void;
131131
attachLighthouseResult(result: LighthouseData): void;
132+
setListInPageTools(): void;
132133
}
133134

134135
/**

src/tools/categories.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum ToolCategory {
1212
NETWORK = 'network',
1313
DEBUGGING = 'debugging',
1414
EXTENSIONS = 'extensions',
15+
IN_PAGE = 'in-page',
1516
}
1617

1718
export const labels = {
@@ -22,4 +23,5 @@ export const labels = {
2223
[ToolCategory.NETWORK]: 'Network',
2324
[ToolCategory.DEBUGGING]: 'Debugging',
2425
[ToolCategory.EXTENSIONS]: 'Extensions',
26+
[ToolCategory.IN_PAGE]: 'In-page tools',
2527
};

src/tools/inPage.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {type JSONSchema7} from '../third_party/index.js';
8+
9+
import {ToolCategory} from './categories.js';
10+
import {definePageTool} from './ToolDefinition.js';
11+
12+
export interface ToolDefinition {
13+
name: string;
14+
description: string;
15+
inputSchema: JSONSchema7;
16+
execute: (input: Record<string, unknown>) => unknown;
17+
}
18+
19+
export interface ToolGroup {
20+
name: string;
21+
description: string;
22+
tools: ToolDefinition[];
23+
}
24+
25+
declare global {
26+
interface Window {
27+
__dtmcp?: {
28+
toolGroup?: ToolGroup;
29+
executeTool?: (
30+
toolName: string,
31+
args: Record<string, unknown>,
32+
) => unknown;
33+
};
34+
}
35+
}
36+
37+
export const listInPageTools = definePageTool({
38+
name: 'list_in_page_tools',
39+
description: `Lists all in-page-tools the page exposes for providing runtime information.
40+
In-page-tools are exposed on the page via the 'window.__dtmcp.executeTool(toolName, params)'
41+
function where they can be called by 'evaluate_script'.`,
42+
annotations: {
43+
category: ToolCategory.IN_PAGE,
44+
readOnlyHint: true,
45+
},
46+
schema: {},
47+
handler: async (_request, response, _context) => {
48+
response.setListInPageTools();
49+
},
50+
});

src/tools/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {ParsedArguments} from '../bin/chrome-devtools-mcp-cli-options.js';
99
import * as consoleTools from './console.js';
1010
import * as emulationTools from './emulation.js';
1111
import * as extensionTools from './extensions.js';
12+
import * as inPageTools from './inPage.js';
1213
import * as inputTools from './input.js';
1314
import * as lighthouseTools from './lighthouse.js';
1415
import * as memoryTools from './memory.js';
@@ -29,6 +30,7 @@ export const createTools = (args: ParsedArguments) => {
2930
...Object.values(consoleTools),
3031
...Object.values(emulationTools),
3132
...Object.values(extensionTools),
33+
...Object.values(inPageTools),
3234
...Object.values(inputTools),
3335
...Object.values(lighthouseTools),
3436
...Object.values(memoryTools),

tests/McpResponse.test.js.snapshot

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,36 @@ exports[`extensions > lists extensions 2`] = `
12061206
}
12071207
`;
12081208

1209+
exports[`in-page tools > lists in-page tools 1`] = `
1210+
## In-page tools
1211+
My Tool Group: A group of tools
1212+
Available tools:
1213+
name="myTool", description="Does something", inputSchema={"type":"object","properties":{"foo":{"type":"string"}}}
1214+
`;
1215+
1216+
exports[`in-page tools > lists in-page tools 2`] = `
1217+
{
1218+
"inPageTools": {
1219+
"name": "My Tool Group",
1220+
"description": "A group of tools",
1221+
"tools": [
1222+
{
1223+
"name": "myTool",
1224+
"description": "Does something",
1225+
"inputSchema": {
1226+
"type": "object",
1227+
"properties": {
1228+
"foo": {
1229+
"type": "string"
1230+
}
1231+
}
1232+
}
1233+
}
1234+
]
1235+
}
1236+
}
1237+
`;
1238+
12091239
exports[`lighthouse > includes lighthouse report paths 1`] = `
12101240
## Lighthouse Audit Results
12111241
Mode: navigation

0 commit comments

Comments
 (0)