feat: Handle multiple providers of third-party developer tools (#2168)

This allows a page to have multiple providers of third-party developer
tools, which each respond to the `devtoolstooldiscovery` event.

- Multiple `ToolGroup`s
- MCP tool responses only mention third-party developer tools, if there
are any. Otherwise this part of the output is skipped.
This commit is contained in:
Wolfgang Beyer
2026-06-02 17:07:32 +02:00
committed by GitHub
parent bf0574da8c
commit 30d59a7872
7 changed files with 408 additions and 278 deletions
+3 -3
View File
@@ -14,7 +14,7 @@ import type {
WebMCPTool,
} from './third_party/index.js';
import {takeSnapshot} from './tools/snapshot.js';
import type {ToolGroup, ToolDefinition} from './tools/thirdPartyDeveloper.js';
import type {ToolGroups} from './tools/thirdPartyDeveloper.js';
import type {
ContextPage,
DevToolsData,
@@ -59,7 +59,7 @@ export class McpPage implements ContextPage {
#dialog?: Dialog;
#dialogHandler: (dialog: Dialog) => void;
thirdPartyDeveloperTools: ToolGroup<ToolDefinition> | undefined;
thirdPartyDeveloperTools: ToolGroups = [];
constructor(page: Page, id: number) {
this.pptrPage = page;
@@ -90,7 +90,7 @@ export class McpPage implements ContextPage {
}
}
getThirdPartyDeveloperTools(): ToolGroup<ToolDefinition> | undefined {
getThirdPartyDeveloperTools(): ToolGroups {
return this.thirdPartyDeveloperTools;
}
+82 -56
View File
@@ -28,7 +28,7 @@ import type {
Extension,
} from './third_party/index.js';
import {handleDialog} from './tools/pages.js';
import type {ToolGroup, ToolDefinition} from './tools/thirdPartyDeveloper.js';
import type {ToolGroups} from './tools/thirdPartyDeveloper.js';
import type {
DevToolsData,
ImageContentData,
@@ -99,9 +99,7 @@ export function replaceHtmlElementsWithUids(schema: JSONSchema7Definition) {
}
}
async function getToolGroup(
page: McpPage,
): Promise<ToolGroup<ToolDefinition> | undefined | null> {
async function getToolGroups(page: McpPage): Promise<ToolGroups> {
// Check if there is a `devtoolstooldiscovery` event listener
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
// @ts-expect-error internal API
@@ -111,51 +109,88 @@ async function getToolGroup(
objectId: windowHandle.remoteObject().objectId,
});
if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
return;
return [];
}
const toolGroup = await page.pptrPage.evaluate(() => {
return new Promise<ToolGroup<ToolDefinition> | undefined | null>(
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;
const toolGroups = await page.pptrPage.evaluate(() => {
return new Promise<ToolGroups>(resolve => {
const event = new CustomEvent('devtoolstooldiscovery');
const groups: ToolGroups = [];
// @ts-expect-error Adding custom property
event.respondWith = toolGroup => {
if (!window.__dtmcp) {
window.__dtmcp = {};
}
if (!window.__dtmcp.toolGroups) {
window.__dtmcp.toolGroups = [];
}
// 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);
};
if (
typeof toolGroup.name !== 'string' ||
typeof toolGroup.description !== 'string' ||
!Array.isArray(toolGroup.tools)
) {
console.error('Invalid toolGroup:', toolGroup);
return;
}
for (const tool of toolGroup.tools) {
if (
typeof tool.name !== 'string' ||
typeof tool.description !== 'string' ||
typeof tool.inputSchema !== 'object' ||
typeof tool.execute !== 'function'
) {
console.error('Invalid tool:', tool);
return;
}
}
resolve(toolGroup);
};
window.dispatchEvent(event);
// If the page does not synchronously call `event.respondWith`, return instead of timing out
window.__dtmcp.toolGroups.push(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?.toolGroups ||
window.__dtmcp.toolGroups.length === 0
) {
throw new Error('No tools found on the page');
}
for (const group of window.__dtmcp.toolGroups) {
const tool = group.tools?.find(t => t.name === toolName);
if (tool) {
return await tool.execute(args);
}
}
throw new Error(`Tool ${toolName} not found`);
};
}
groups.push(toolGroup);
};
window.dispatchEvent(event);
// If at least one toolGroup was added synchronously, resolve with the array.
// Otherwise, use setTimeout to allow for any microtask/asynchronous respondWith calls, or resolve with an empty array.
if (groups.length > 0) {
resolve(groups);
} else {
setTimeout(() => {
resolve(null);
if (groups.length > 0) {
resolve(groups);
} else {
resolve([]);
}
}, 0);
},
);
}
});
});
for (const tool of toolGroup?.tools ?? []) {
replaceHtmlElementsWithUids(tool.inputSchema);
for (const group of toolGroups) {
for (const tool of group.tools ?? []) {
replaceHtmlElementsWithUids(tool.inputSchema);
}
}
return toolGroup;
return toolGroups;
}
export class McpResponse implements Response {
@@ -562,14 +597,13 @@ export class McpResponse implements Response {
extensions = await context.listExtensions();
}
// Null indicates no tools.
let thirdPartyDeveloperTools: ToolGroup<ToolDefinition> | undefined | null;
let thirdPartyDeveloperTools: ToolGroups = [];
if (
this.#args.categoryExperimentalThirdParty &&
this.#listThirdPartyDeveloperTools &&
this.#page
) {
thirdPartyDeveloperTools = await getToolGroup(this.#page);
thirdPartyDeveloperTools = await getToolGroups(this.#page);
if (thirdPartyDeveloperTools) {
this.#page.thirdPartyDeveloperTools = thirdPartyDeveloperTools;
}
@@ -707,7 +741,7 @@ export class McpResponse implements Response {
traceInsight?: TraceInsightData;
extensions?: Map<string, Extension>;
lighthouseResult?: LighthouseData;
thirdPartyDeveloperTools?: ToolGroup<ToolDefinition> | null;
thirdPartyDeveloperTools: ToolGroups;
webmcpTools?: WebMCPTool[];
errorMessage?: string;
},
@@ -724,7 +758,7 @@ export class McpResponse implements Response {
traceInsights?: Array<{insightName: string; insightKey: string}>;
lighthouseResult?: object;
extensions?: object[];
thirdPartyDeveloperTools?: object;
thirdPartyDeveloperTools?: object[];
webmcpTools?: object[];
message?: string;
networkConditions?: string;
@@ -1052,19 +1086,11 @@ Call ${handleDialog.name} to handle it before continuing.`);
}
}
if (data.thirdPartyDeveloperTools !== undefined) {
if (data.thirdPartyDeveloperTools) {
structuredContent.thirdPartyDeveloperTools =
data.thirdPartyDeveloperTools;
}
if (data.thirdPartyDeveloperTools.length) {
structuredContent.thirdPartyDeveloperTools =
data.thirdPartyDeveloperTools;
response.push('## Third-party developer tools');
if (
data.thirdPartyDeveloperTools === null ||
!data.thirdPartyDeveloperTools?.tools
) {
response.push('No third-party developer tools available.');
} else {
const toolGroup = data.thirdPartyDeveloperTools;
for (const toolGroup of data.thirdPartyDeveloperTools) {
response.push(`${toolGroup.name}: ${toolGroup.description}`);
response.push('Available tools:');
const toolDefinitionsMessage = toolGroup.tools
+3 -7
View File
@@ -27,10 +27,7 @@ import type {PaginationOptions} from '../utils/types.js';
import type {WaitForEventsResult} from '../WaitForHelper.js';
import type {ToolCategory} from './categories.js';
import type {
ToolGroup,
ToolDefinition as ThirdPartyDeveloperToolDefinition,
} from './thirdPartyDeveloper.js';
import type {ToolGroups} from './thirdPartyDeveloper.js';
export interface BaseToolDefinition<
Schema extends zod.ZodRawShape = zod.ZodRawShape,
@@ -270,9 +267,8 @@ export type ContextPage = Readonly<{
action: () => Promise<unknown>,
options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string},
): Promise<WaitForEventsResult>;
getThirdPartyDeveloperTools():
| ToolGroup<ThirdPartyDeveloperToolDefinition>
| undefined;
getThirdPartyDeveloperTools(): ToolGroups;
executeThirdPartyDeveloperTool(
toolName: string,
params: Record<string, unknown>,
+18 -4
View File
@@ -21,11 +21,17 @@ export interface ToolGroup<T extends ToolDefinition> {
tools: T[];
}
export type ToolGroups = Array<ToolGroup<ToolDefinition>>;
declare global {
interface Window {
__dtmcp?: {
toolGroup?: ToolGroup<
ToolDefinition & {execute: (args: Record<string, unknown>) => unknown}
toolGroups?: Array<
ToolGroup<
ToolDefinition & {
execute: (args: Record<string, unknown>) => unknown;
}
>
>;
stashedElements?: Element[];
executeTool?: (
@@ -90,8 +96,16 @@ export const executeThirdPartyDeveloperTool = definePageTool({
}
}
const toolGroup = request.page.getThirdPartyDeveloperTools();
const tool = toolGroup?.tools.find(t => t.name === toolName);
const toolGroups = request.page.getThirdPartyDeveloperTools();
let tool;
if (toolGroups) {
for (const group of toolGroups) {
tool = group.tools?.find(t => t.name === toolName);
if (tool) {
break;
}
}
}
if (!tool) {
throw new Error(`Tool ${toolName} not found`);
}
+17 -15
View File
@@ -1261,24 +1261,26 @@ name="myTool", description="Does something", inputSchema={"type":"object","prope
exports[`third-party developer tools > lists third-party developer tools 2`] = `
{
"thirdPartyDeveloperTools": {
"name": "My Tool Group",
"description": "A group of tools",
"tools": [
{
"name": "myTool",
"description": "Does something",
"inputSchema": {
"type": "object",
"properties": {
"foo": {
"type": "string"
"thirdPartyDeveloperTools": [
{
"name": "My Tool Group",
"description": "A group of tools",
"tools": [
{
"name": "myTool",
"description": "Does something",
"inputSchema": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
]
}
]
}
]
}
`;
+16 -14
View File
@@ -1070,22 +1070,24 @@ describe('third-party developer tools', () => {
async (response, context) => {
const mcpPage = context.getSelectedMcpPage();
stubToolDiscovery(mcpPage.pptrPage);
sinon.stub(mcpPage.pptrPage, 'evaluate').resolves({
name: 'My Tool Group',
description: 'A group of tools',
tools: [
{
name: 'myTool',
description: 'Does something',
inputSchema: {
type: 'object',
properties: {
foo: {type: 'string'},
sinon.stub(mcpPage.pptrPage, 'evaluate').resolves([
{
name: 'My Tool Group',
description: 'A group of tools',
tools: [
{
name: 'myTool',
description: 'Does something',
inputSchema: {
type: 'object',
properties: {
foo: {type: 'string'},
},
},
},
},
],
});
],
},
]);
response.setListThirdPartyDeveloperTools();
const {content, structuredContent} = await response.handle(
'test',
+269 -179
View File
@@ -17,10 +17,7 @@ import {
executeThirdPartyDeveloperTool,
listThirdPartyDeveloperTools,
} from '../../src/tools/thirdPartyDeveloper.js';
import type {
ToolGroup,
ToolDefinition,
} from '../../src/tools/thirdPartyDeveloper.js';
import type {ToolGroups} from '../../src/tools/thirdPartyDeveloper.js';
import {withMcpContext} from '../utils.js';
describe('thirdPartyDeveloperTools', () => {
@@ -33,27 +30,29 @@ describe('thirdPartyDeveloperTools', () => {
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'},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
arg: {type: 'string'},
},
},
execute: () => 'result',
},
execute: () => 'result',
},
],
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -68,7 +67,9 @@ describe('thirdPartyDeveloperTools', () => {
context,
);
// @ts-expect-error `structuredContent` has `thirdPartyDeveloperTools`
const actualGroup = result.structuredContent.thirdPartyDeveloperTools;
const groups = result.structuredContent.thirdPartyDeveloperTools;
assert.strictEqual(groups.length, 1);
const actualGroup = groups[0];
assert.strictEqual(actualGroup.name, 'test-group');
assert.strictEqual(actualGroup.description, 'test description');
assert.strictEqual(actualGroup.tools.length, 1);
@@ -111,14 +112,14 @@ describe('thirdPartyDeveloperTools', () => {
'list_3p_developer_tools',
context,
);
assert.ok('thirdPartyDeveloperTools' in result.structuredContent);
assert.deepEqual(
assert.ok(result.structuredContent);
assert.deepStrictEqual(
(
result.structuredContent as {
thirdPartyDeveloperTools: ToolGroup<ToolDefinition>;
thirdPartyDeveloperTools?: ToolGroups;
}
).thirdPartyDeveloperTools,
{},
undefined,
);
},
undefined,
@@ -147,10 +148,11 @@ describe('thirdPartyDeveloperTools', () => {
'list_3p_developer_tools',
context,
);
assert.strictEqual(
assert.ok(result.structuredContent);
assert.deepStrictEqual(
(
result.structuredContent as {
thirdPartyDeveloperTools: ToolGroup<ToolDefinition>;
thirdPartyDeveloperTools?: ToolGroups;
}
).thirdPartyDeveloperTools,
undefined,
@@ -176,9 +178,13 @@ describe('thirdPartyDeveloperTools', () => {
'list_3p_developer_tools',
context,
);
assert.strictEqual(
(result.structuredContent as {thirdPartyDeveloperTools: undefined})
.thirdPartyDeveloperTools,
assert.ok(result.structuredContent);
assert.deepStrictEqual(
(
result.structuredContent as {
thirdPartyDeveloperTools?: ToolGroups;
}
).thirdPartyDeveloperTools,
undefined,
);
},
@@ -186,6 +192,68 @@ describe('thirdPartyDeveloperTools', () => {
{categoryExperimentalThirdParty: true} as ParsedArguments,
);
});
it('lists multiple toolgroups', async () => {
await withMcpContext(
async (response, context) => {
const page = await context.newPage();
response.setPage(page);
await page.pptrPage.evaluate(() => {
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith?.({
name: 'group-1',
description: 'desc-1',
tools: [
{
name: 'tool-1',
description: 'tool-1-desc',
inputSchema: {},
execute: () => 'r1',
},
],
});
});
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith?.({
name: 'group-2',
description: 'desc-2',
tools: [
{
name: 'tool-2',
description: 'tool-2-desc',
inputSchema: {},
execute: () => 'r2',
},
],
});
});
});
await listThirdPartyDeveloperTools.handler(
{params: {}, page},
response,
context,
);
const result = await response.handle(
'list_3p_developer_tools',
context,
);
const actualGroups =
// @ts-expect-error structuredContent has `thirdPartyDeveloperTools`
result.structuredContent.thirdPartyDeveloperTools;
assert.ok(actualGroups);
assert.strictEqual(actualGroups.length, 2);
assert.strictEqual(actualGroups[0].name, 'group-1');
assert.strictEqual(actualGroups[1].name, 'group-2');
},
undefined,
{categoryExperimentalThirdParty: true} as ParsedArguments,
);
});
});
describe('execute_3p_developer_tool', () => {
@@ -210,28 +278,30 @@ describe('thirdPartyDeveloperTools', () => {
async (response, context) => {
await setupThirdPartyDeveloperTools(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'},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
arg: {type: 'string'},
},
required: ['arg'],
},
required: ['arg'],
execute: () => 'result',
},
execute: () => 'result',
},
],
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -260,15 +330,17 @@ describe('thirdPartyDeveloperTools', () => {
await withMcpContext(async (response, context) => {
await setupThirdPartyDeveloperTools(response, context, () => {
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [],
},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -296,28 +368,30 @@ describe('thirdPartyDeveloperTools', () => {
async (response, context) => {
await setupThirdPartyDeveloperTools(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'},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
arg: {type: 'string'},
},
required: ['arg'],
},
required: ['arg'],
execute: () => 'result',
},
execute: () => 'result',
},
],
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -348,22 +422,24 @@ describe('thirdPartyDeveloperTools', () => {
async (response, context) => {
await setupThirdPartyDeveloperTools(response, context, () => {
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => ({foo: 'bar'}),
},
],
},
toolGroups: [
{
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);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -393,23 +469,25 @@ describe('thirdPartyDeveloperTools', () => {
const page = await context.newPage();
response.setPage(page);
page.thirdPartyDeveloperTools = {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
element: {type: 'object'},
page.thirdPartyDeveloperTools = [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {
type: 'object',
properties: {
element: {type: 'object'},
},
required: ['element'],
},
required: ['element'],
},
},
],
};
],
},
];
await page.pptrPage.evaluate(() => {
window.__dtmcp = {
@@ -484,25 +562,27 @@ describe('thirdPartyDeveloperTools', () => {
async (response, context) => {
await setupThirdPartyDeveloperTools(response, context, () => {
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => ({
foo: 'bar',
func: () => undefined,
}),
},
],
},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => ({
foo: 'bar',
func: () => undefined,
}),
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -532,26 +612,28 @@ describe('thirdPartyDeveloperTools', () => {
async (response, context) => {
await setupThirdPartyDeveloperTools(response, context, () => {
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => {
const obj: Record<string, unknown> = {foo: 'bar'};
obj.self = obj;
return obj;
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => {
const obj: Record<string, unknown> = {foo: 'bar'};
obj.self = obj;
return obj;
},
},
},
],
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -584,25 +666,27 @@ describe('thirdPartyDeveloperTools', () => {
val = 'value';
}
window.__dtmcp = {
toolGroup: {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => ({
foo: 'bar',
custom: new CustomClass(),
}),
},
],
},
toolGroups: [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
execute: () => ({
foo: 'bar',
custom: new CustomClass(),
}),
},
],
},
],
};
window.addEventListener('devtoolstooldiscovery', (e: Event) => {
// @ts-expect-error Event has `respondWith`
e.respondWith(window.__dtmcp?.toolGroup);
e.respondWith(window.__dtmcp?.toolGroups[0]);
});
});
@@ -637,17 +721,19 @@ describe('thirdPartyDeveloperTools', () => {
const page = await context.newPage();
response.setPage(page);
page.thirdPartyDeveloperTools = {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
};
page.thirdPartyDeveloperTools = [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
},
];
await page.pptrPage.evaluate(() => {
window.__dtmcp = {
@@ -694,17 +780,19 @@ describe('thirdPartyDeveloperTools', () => {
const page = await context.newPage();
response.setPage(page);
page.thirdPartyDeveloperTools = {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
};
page.thirdPartyDeveloperTools = [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
},
];
await page.pptrPage.evaluate(() => {
window.__dtmcp = {
@@ -760,17 +848,19 @@ describe('thirdPartyDeveloperTools', () => {
const page = await context.newPage();
response.setPage(page);
page.thirdPartyDeveloperTools = {
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
};
page.thirdPartyDeveloperTools = [
{
name: 'test-group',
description: 'test description',
tools: [
{
name: 'test-tool',
description: 'test tool description',
inputSchema: {},
},
],
},
];
await page.pptrPage.evaluate(() => {
window.__dtmcp = {