Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 9894227

Browse files
committed
add list of inPageTools to responses to 'list_pages', 'select_page', 'new_page', and 'navigate_page'
1 parent ca6b0b4 commit 9894227

5 files changed

Lines changed: 43 additions & 37 deletions

File tree

src/McpContext.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export class McpContext implements Context {
127127
#userAgentMap = new WeakMap<Page, string>();
128128
#colorSchemeMap = new WeakMap<Page, 'dark' | 'light'>();
129129
#dialog?: Dialog;
130-
#inPageTools?: ToolGroup;
130+
#inPageTools?: ToolGroup | null;
131131

132132
#pageIdMap = new WeakMap<Page, number>();
133133
#nextPageId = 1;
@@ -435,11 +435,11 @@ export class McpContext implements Context {
435435
});
436436
}
437437

438-
setInPageTools(toolGroup: ToolGroup|undefined) {
438+
setInPageTools(toolGroup: ToolGroup | null) {
439439
this.#inPageTools = toolGroup;
440440
}
441441

442-
getInPageTools(): ToolGroup | undefined {
442+
getInPageTools(): ToolGroup | null | undefined {
443443
return this.#inPageTools;
444444
}
445445

src/McpResponse.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {IssueFormatter} from './formatters/IssueFormatter.js';
99
import {NetworkFormatter} from './formatters/NetworkFormatter.js';
1010
import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
1111
import type {McpContext} from './McpContext.js';
12-
import {DevTools} from './third_party/index.js';
12+
import {DevTools, type Page} from './third_party/index.js';
1313
import type {
1414
ConsoleMessage,
1515
ImageContent,
@@ -36,6 +36,25 @@ interface TraceInsightData {
3636
insightName: InsightName;
3737
}
3838

39+
async function getToolGroup(page: Page) {
40+
return await page.evaluate(() => {
41+
return new Promise<ToolGroup | null>(resolve => {
42+
const event = new CustomEvent('devtoolstooldiscovery');
43+
// @ts-expect-error adding custom property
44+
event.respondWith = (toolGroup: ToolGroup) => {
45+
window.__mcp_tool_group = toolGroup;
46+
resolve(toolGroup ?? null);
47+
};
48+
window.dispatchEvent(event);
49+
// TODO: replace with checking for existence of event listener?
50+
// Can `respondWith` be called asynchronously?
51+
setTimeout(() => {
52+
resolve(null);
53+
}, 0);
54+
});
55+
});
56+
}
57+
3958
export class McpResponse implements Response {
4059
#includePages = false;
4160
#snapshotParams?: SnapshotParams;
@@ -314,9 +333,10 @@ export class McpResponse implements Response {
314333
extensions = context.listExtensions();
315334
}
316335

317-
let inPageTools: ToolGroup | undefined;
336+
let inPageTools: ToolGroup | null | undefined;
318337
if (this.#listInPageTools) {
319-
inPageTools = context.getInPageTools();
338+
inPageTools = await getToolGroup(context.getSelectedPage());
339+
context.setInPageTools(inPageTools);
320340
}
321341

322342
let consoleMessages: Array<ConsoleFormatter | IssueFormatter> | undefined;
@@ -429,7 +449,7 @@ export class McpResponse implements Response {
429449
traceSummary?: TraceResult;
430450
traceInsight?: TraceInsightData;
431451
extensions?: InstalledExtension[];
432-
inPageTools?: ToolGroup;
452+
inPageTools?: ToolGroup | null;
433453
},
434454
): {content: Array<TextContent | ImageContent>; structuredContent: object} {
435455
const structuredContent: {
@@ -609,7 +629,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
609629
}
610630

611631
if (this.#listInPageTools) {
612-
structuredContent.inPageTools = data.inPageTools;
632+
structuredContent.inPageTools = data.inPageTools ?? undefined;
613633
response.push('## In-page tools');
614634
if (!data.inPageTools) {
615635
response.push('No in-page tools installed.');

src/tools/ToolDefinition.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ export type Context = Readonly<{
151151
uninstallExtension(id: string): Promise<void>;
152152
listExtensions(): InstalledExtension[];
153153
getExtension(id: string): InstalledExtension | undefined;
154-
setInPageTools(toolGroup: ToolGroup|undefined): void;
155-
getInPageTools(): ToolGroup|undefined;
154+
setInPageTools(toolGroup: ToolGroup | null): void;
155+
getInPageTools(): ToolGroup | null | undefined;
156156
}>;
157157

158158
export function defineTool<Schema extends zod.ZodRawShape>(

src/tools/inPage.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,6 @@ declare global {
2828
}
2929
}
3030

31-
async function getToolGroup(page: Page) {
32-
return await page.evaluate(() => {
33-
return new Promise<ToolGroup | undefined>(resolve => {
34-
const event = new CustomEvent('devtoolstooldiscovery');
35-
// @ts-expect-error adding custom property
36-
event.respondWith = (toolGroup: ToolGroup) => {
37-
window.__mcp_tool_group = toolGroup;
38-
resolve(toolGroup);
39-
};
40-
window.dispatchEvent(event);
41-
// TODO: replace with checking for existence of event listener?
42-
// Can `respondWith` be called asynchronously?
43-
setTimeout(() => {
44-
resolve(undefined);
45-
}, 0);
46-
});
47-
});
48-
}
49-
5031
export const listInPageTools = defineTool({
5132
name: 'list_in_page_tools',
5233
description: `Lists all tools the page exposes for providing runtime information.`,
@@ -55,10 +36,7 @@ export const listInPageTools = defineTool({
5536
readOnlyHint: true,
5637
},
5738
schema: {},
58-
handler: async (_request, response, context) => {
59-
const page = context.getSelectedPage();
60-
const toolGroup = await getToolGroup(page);
61-
context.setInPageTools(toolGroup);
39+
handler: async (_request, response, _context) => {
6240
response.setListInPageTools();
6341
},
6442
});
@@ -80,10 +58,10 @@ export const executeInPageTool = defineTool({
8058
const params = request.params.params ?? {};
8159

8260
// Get tools from context
83-
// const toolGroup = context.getInPageTools();
61+
const toolGroup = context.getInPageTools();
8462
// Alternatively: get tools from page
85-
const toolGroup = await getToolGroup(page);
86-
context.setInPageTools(toolGroup);
63+
// const toolGroup = await getToolGroup(page);
64+
// context.setInPageTools(toolGroup);
8765
const tool = toolGroup?.tools.find(t => t.name === toolName);
8866
if (!tool) {
8967
throw new Error(`Tool ${toolName} not found`);

src/tools/pages.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ export const listPages = defineTool({
1919
readOnlyHint: true,
2020
},
2121
schema: {},
22-
handler: async (_request, response) => {
22+
handler: async (_request, response, context) => {
2323
response.setIncludePages(true);
24+
// Only include in-page tools in the response, if they haven't already been
25+
// sent before.
26+
if (context.getInPageTools() === undefined) {
27+
response.setListInPageTools();
28+
}
2429
},
2530
});
2631

@@ -46,6 +51,7 @@ export const selectPage = defineTool({
4651
const page = context.getPageById(request.params.pageId);
4752
context.selectPage(page);
4853
response.setIncludePages(true);
54+
response.setListInPageTools();
4955
if (request.params.bringToFront) {
5056
await page.bringToFront();
5157
}
@@ -108,6 +114,7 @@ export const newPage = defineTool({
108114
);
109115

110116
response.setIncludePages(true);
117+
response.setListInPageTools();
111118
},
112119
});
113120

@@ -257,6 +264,7 @@ export const navigatePage = defineTool({
257264
}
258265

259266
response.setIncludePages(true);
267+
response.setListInPageTools();
260268
},
261269
});
262270

0 commit comments

Comments
 (0)