项目文件夹

文件
Wolfgang Beyer cea963be68 feat: Add a new tool for listing in-page tools (gated behind a command line flag) (#1239)
This adds a `list_in_page_tools` MCP tool. When called, it dispatches a
`devtoolstooldiscovery` event on the active page. The page announces its
exposing tools by calling the event's `respondWith` method, which causes
the exposed tools to be stashed on the page's `window` object. This list
of in-page tools is then appended to the `list_in_page_tools` response.

Calling the exposed in-page tools from the MCP server will be handled in
a follow-up.
2026-03-30 09:51:35 +00:00

140 行
4.6 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import {describe, it} from 'node:test';
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
import {listInPageTools} from '../../src/tools/inPage.js';
import {withMcpContext} from '../utils.js';
describe('inPage', () => {
describe('list_in_page_tools', () => {
it('lists tools', async () => {
await withMcpContext(
async (response, context) => {
const page = await context.newPage();
await page.pptrPage.evaluate(() => {
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
arg: {type: 'string'},
},
},
execute: () => 'result',
},
],
},
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
});
});
await listInPageTools.handler({params: {}, page}, response, context);
const result = await response.handle('list_in_page_tools', context);
// @ts-expect-error `structuredContent` has `inPageTools`
const actualGroup = result.structuredContent.inPageTools;
assert.strictEqual(actualGroup.name, 'test-group');
assert.strictEqual(actualGroup.description, 'test description');
assert.strictEqual(actualGroup.tools.length, 1);
assert.strictEqual(actualGroup.tools[0].name, 'test-tool');
assert.strictEqual(
actualGroup.tools[0].description,
'test tool description',
);
assert.deepEqual(actualGroup.tools[0].inputSchema, {
type: 'object',
properties: {
arg: {type: 'string'},
},
});
},
undefined,
{categoryInPageTools: true} as ParsedArguments,
);
});
it('handles empty response', async () => {
await withMcpContext(
async (response, context) => {
const page = await context.newPage();
await page.pptrPage.evaluate(() => {
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith({});
});
});
await listInPageTools.handler({params: {}, page}, response, context);
const result = await response.handle('list_in_page_tools', context);
assert.ok('inPageTools' in result.structuredContent);
assert.deepEqual(
(result.structuredContent as {inPageTools: undefined}).inPageTools,
{},
);
},
undefined,
{categoryInPageTools: true} as ParsedArguments,
);
});
it('handles no response', async () => {
await withMcpContext(
async (response, context) => {
const page = await context.newPage();
await page.pptrPage.evaluate(() => {
window.addEventListener('devtoolstooldiscovery', () => {
// do nothing
});
});
await listInPageTools.handler({params: {}, page}, response, context);
const result = await response.handle('list_in_page_tools', context);
assert.ok('inPageTools' in result.structuredContent);
assert.strictEqual(
(result.structuredContent as {inPageTools: undefined}).inPageTools,
undefined,
);
},
undefined,
{categoryInPageTools: true} as ParsedArguments,
);
});
it('handles no eventListener', async () => {
await withMcpContext(
async (response, context) => {
const page = await context.newPage();
await listInPageTools.handler({params: {}, page}, response, context);
const result = await response.handle('list_in_page_tools', context);
assert.ok('inPageTools' in result.structuredContent);
assert.strictEqual(
(result.structuredContent as {inPageTools: undefined}).inPageTools,
undefined,
);
},
undefined,
{categoryInPageTools: true} as ParsedArguments,
);
});
});
});