Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,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)

<!-- END AUTO GENERATED TOOLS -->

Expand Down Expand Up @@ -555,6 +557,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
Expand Down
14 changes: 14 additions & 0 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

---
90 changes: 89 additions & 1 deletion src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
import type {McpContext} from './McpContext.js';
import type {McpPage} from './McpPage.js';
import {UncaughtError} from './PageCollector.js';
import {DevTools} from './third_party/index.js';
import {DevTools, type Protocol} from './third_party/index.js';
import type {
ConsoleMessage,
ImageContent,
Page,
ResourceType,
TextContent,
} from './third_party/index.js';
import type {ToolGroup} from './tools/inPage.js';
import {handleDialog} from './tools/pages.js';
import type {
DevToolsData,
Expand All @@ -40,6 +41,57 @@ interface TraceInsightData {
insightName: InsightName;
}

async function getToolGroup(page: McpPage): Promise<ToolGroup | undefined> {
// Check if there is a `devtoolstooldiscovery` event listener
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
// @ts-expect-error internal API
const client = page.pptrPage._client();
const {listeners}: {listeners: Protocol.DOMDebugger.EventListener[]} =
await client.send('DOMDebugger.getEventListeners', {
objectId: windowHandle.remoteObject().objectId,
});
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
return;
}

const toolGroup = await page.pptrPage.evaluate(() => {
return new Promise<ToolGroup | undefined>(resolve => {
const event = new CustomEvent('devtoolstooldiscovery');
// @ts-expect-error Adding custom property
event.respondWith = (toolGroup: ToolGroup) => {
if (!window.__dtmcp) {
window.__dtmcp = {};
}
window.__dtmcp.toolGroup = toolGroup;

// When receiving a toolGroup for the first time, expose a simple execution helper
if (!window.__dtmcp.executeTool) {
window.__dtmcp.executeTool = async (toolName, args) => {
if (!window.__dtmcp?.toolGroup) {
throw new Error('No tools found on the page');
}
const tool = window.__dtmcp.toolGroup.tools.find(
t => t.name === toolName,
);
if (!tool) {
throw new Error(`Tool ${toolName} not found`);
}
return await tool.execute(args);
};
}

resolve(toolGroup);
};
window.dispatchEvent(event);
// If the page does not call `event.respondWith`, return instead of timing out
Comment thread
wolfib marked this conversation as resolved.
Outdated
setTimeout(() => {
resolve(undefined);
}, 0);
});
});
return toolGroup;
}

export class McpResponse implements Response {
#includePages = false;
#includeExtensionServiceWorkers = false;
Expand Down Expand Up @@ -70,6 +122,7 @@ export class McpResponse implements Response {
includePreservedMessages?: boolean;
};
#listExtensions?: boolean;
#listInPageTools?: boolean;
#devToolsData?: DevToolsData;
#tabId?: string;
#args: ParsedArguments;
Expand Down Expand Up @@ -110,6 +163,12 @@ export class McpResponse implements Response {
this.#listExtensions = true;
}

setListInPageTools(): void {
if (this.#args.categoryInPageTools) {
this.#listInPageTools = true;
}
}

setIncludeNetworkRequests(
value: boolean,
options?: PaginationOptions & {
Expand Down Expand Up @@ -357,6 +416,12 @@ export class McpResponse implements Response {
if (this.#listExtensions) {
extensions = context.listExtensions();
}

let inPageTools: ToolGroup | undefined;
if (this.#listInPageTools) {
inPageTools = await getToolGroup(context.getSelectedMcpPage());
Comment thread
wolfib marked this conversation as resolved.
}

let consoleMessages: Array<ConsoleFormatter | IssueFormatter> | undefined;
if (this.#consoleDataOptions?.include) {
if (!this.#page) {
Expand Down Expand Up @@ -459,6 +524,7 @@ export class McpResponse implements Response {
traceSummary: this.#attachedTraceSummary,
extensions,
lighthouseResult: this.#attachedLighthouseResult,
inPageTools,
});
}

Expand All @@ -475,6 +541,7 @@ export class McpResponse implements Response {
traceInsight?: TraceInsightData;
extensions?: InstalledExtension[];
lighthouseResult?: LighthouseData;
inPageTools?: ToolGroup;
},
): {content: Array<TextContent | ImageContent>; structuredContent: object} {
const structuredContent: {
Expand All @@ -489,6 +556,7 @@ export class McpResponse implements Response {
traceInsights?: Array<{insightName: string; insightKey: string}>;
lighthouseResult?: object;
extensions?: object[];
inPageTools?: object;
message?: string;
networkConditions?: string;
navigationTimeout?: number;
Expand Down Expand Up @@ -726,6 +794,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
}
}

if (this.#listInPageTools) {
structuredContent.inPageTools = data.inPageTools ?? undefined;
response.push('## In-page tools');
if (!data.inPageTools) {
response.push('No in-page tools available.');
} else {
const toolGroup = data.inPageTools;
response.push(`${toolGroup.name}: ${toolGroup.description}`);
response.push('Available tools:');
const toolDefinitionsMessage = toolGroup.tools
.map(tool => {
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(
tool.inputSchema,
)}`;
})
.join('\n');
response.push(toolDefinitionsMessage);
}
}

if (this.#networkRequestsOptions?.include && data.networkRequests) {
const requests = data.networkRequests;

Expand Down
6 changes: 6 additions & 0 deletions src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ export const cliOptions = {
describe:
'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
},
categoryInPageTools: {
type: 'boolean',
default: false,
describe:
'Set to true to enable tools exposed by the inspected page itself',
},
performanceCrux: {
type: 'boolean',
default: true,
Expand Down
2 changes: 1 addition & 1 deletion src/bin/chrome-devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ delete startCliOptions.autoConnect;
// Missing CLI serialization.
delete startCliOptions.viewport;
// CLI is generated based on the default tool definitions. To enable conditional
// tools, they needs to be enabled during CLI generation.
// tools, they need to be enabled during CLI generation.
delete startCliOptions.experimentalPageIdRouting;
delete startCliOptions.experimentalVision;
delete startCliOptions.experimentalInteropTools;
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ export async function createMcpServer(
) {
return;
}
if (
tool.annotations.category === ToolCategory.IN_PAGE &&
!serverArgs.categoryInPageTools
) {
return;
}
if (
tool.annotations.conditions?.includes('computerVision') &&
!serverArgs.experimentalVision
Expand Down
1 change: 1 addition & 0 deletions src/third_party/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export {default as puppeteer} from 'puppeteer-core';
export type * from 'puppeteer-core';
export {PipeTransport} from 'puppeteer-core/internal/node/PipeTransport.js';
export type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js';
export type {JSONSchema7} from 'json-schema';
export {
resolveDefaultUserDataDir,
detectBrowserPlatform,
Expand Down
1 change: 1 addition & 0 deletions src/tools/ToolDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export interface Response {
): void;
setListExtensions(): void;
attachLighthouseResult(result: LighthouseData): void;
setListInPageTools(): void;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/tools/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum ToolCategory {
NETWORK = 'network',
DEBUGGING = 'debugging',
EXTENSIONS = 'extensions',
IN_PAGE = 'in-page',
}

export const labels = {
Expand All @@ -22,4 +23,5 @@ export const labels = {
[ToolCategory.NETWORK]: 'Network',
[ToolCategory.DEBUGGING]: 'Debugging',
[ToolCategory.EXTENSIONS]: 'Extensions',
[ToolCategory.IN_PAGE]: 'In-page tools',
};
50 changes: 50 additions & 0 deletions src/tools/inPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {type JSONSchema7} from '../third_party/index.js';

import {ToolCategory} from './categories.js';
import {definePageTool} from './ToolDefinition.js';

export interface ToolDefinition {
name: string;
description: string;
inputSchema: JSONSchema7;
execute: (input: Record<string, unknown>) => unknown;
Comment thread
wolfib marked this conversation as resolved.
Outdated
}

export interface ToolGroup {
name: string;
description: string;
tools: ToolDefinition[];
}

declare global {
interface Window {
__dtmcp?: {
toolGroup?: ToolGroup;
executeTool?: (
toolName: string,
args: Record<string, unknown>,
) => unknown;
};
}
}

export const listInPageTools = definePageTool({
name: '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)'
Comment thread
wolfib marked this conversation as resolved.
Outdated
function where they can be called by 'evaluate_script'.`,
annotations: {
category: ToolCategory.IN_PAGE,
readOnlyHint: true,
},
schema: {},
handler: async (_request, response, _context) => {
response.setListInPageTools();
},
});
2 changes: 2 additions & 0 deletions src/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {ParsedArguments} from '../bin/chrome-devtools-mcp-cli-options.js';
import * as consoleTools from './console.js';
import * as emulationTools from './emulation.js';
import * as extensionTools from './extensions.js';
import * as inPageTools from './inPage.js';
import * as inputTools from './input.js';
import * as lighthouseTools from './lighthouse.js';
import * as memoryTools from './memory.js';
Expand All @@ -29,6 +30,7 @@ export const createTools = (args: ParsedArguments) => {
...Object.values(consoleTools),
...Object.values(emulationTools),
...Object.values(extensionTools),
...Object.values(inPageTools),
...Object.values(inputTools),
...Object.values(lighthouseTools),
...Object.values(memoryTools),
Expand Down
30 changes: 30 additions & 0 deletions tests/McpResponse.test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,36 @@ exports[`extensions > lists extensions 2`] = `
}
`;

exports[`in-page tools > lists in-page tools 1`] = `
## In-page tools
My Tool Group: A group of tools
Available tools:
name="myTool", description="Does something", inputSchema={"type":"object","properties":{"foo":{"type":"string"}}}
`;

exports[`in-page tools > lists in-page tools 2`] = `
{
"inPageTools": {
"name": "My Tool Group",
"description": "A group of tools",
"tools": [
{
"name": "myTool",
"description": "Does something",
"inputSchema": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
}
]
}
}
`;

exports[`lighthouse > includes lighthouse report paths 1`] = `
## Lighthouse Audit Results
Mode: navigation
Expand Down
Loading
Loading