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:
+3
-3
@@ -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
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user