Skip to content

Commit f7ae9e8

Browse files
authored
feat: Add list of in-page tools to responses for navigate_page, list_pages, select_page, close_page, new_page (#1762)
There is no mechanism which would allow a page to push an updated list of in-page tools to the MCP server. The next best thing I can think of is to append an updated list of in-page tools to the response for each tool call related to page navigation (navigate_page, list_pages, select_page, close_page, new_page).
1 parent c7948cf commit f7ae9e8

3 files changed

Lines changed: 129 additions & 2 deletions

File tree

src/tools/pages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const listPages = defineTool(args => {
2727
schema: {},
2828
handler: async (_request, response) => {
2929
response.setIncludePages(true);
30+
response.setListInPageTools();
3031
},
3132
};
3233
});
@@ -53,6 +54,7 @@ export const selectPage = defineTool({
5354
const page = context.getPageById(request.params.pageId);
5455
context.selectPage(page);
5556
response.setIncludePages(true);
57+
response.setListInPageTools();
5658
if (request.params.bringToFront) {
5759
await page.pptrPage.bringToFront();
5860
}
@@ -82,6 +84,7 @@ export const closePage = defineTool({
8284
}
8385
}
8486
response.setIncludePages(true);
87+
response.setListInPageTools();
8588
},
8689
});
8790

@@ -126,6 +129,7 @@ export const newPage = defineTool({
126129
);
127130

128131
response.setIncludePages(true);
132+
response.setListInPageTools();
129133
},
130134
});
131135

@@ -275,6 +279,7 @@ export const navigatePage = definePageTool({
275279
}
276280

277281
response.setIncludePages(true);
282+
response.setListInPageTools();
278283
},
279284
});
280285

tests/McpResponse.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ import {describe, it} from 'node:test';
1313
import sinon from 'sinon';
1414

1515
import type {ParsedArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js';
16+
import type {McpContext} from '../src/McpContext.js';
17+
import type {McpResponse} from '../src/McpResponse.js';
18+
import {
19+
closePage,
20+
listPages,
21+
navigatePage,
22+
newPage,
23+
selectPage,
24+
} from '../src/tools/pages.js';
1625
import type {InsightName} from '../src/trace-processing/parse.js';
1726
import {
1827
parseRawTraceBuffer,
@@ -1091,4 +1100,108 @@ describe('inPage tools', () => {
10911100
{categoryInPageTools: true} as ParsedArguments,
10921101
);
10931102
});
1103+
1104+
async function testIncludesInPageTools(
1105+
handlerAction: (
1106+
response: McpResponse,
1107+
context: McpContext,
1108+
) => Promise<void>,
1109+
toolName: string,
1110+
) {
1111+
await withMcpContext(
1112+
async (response, context) => {
1113+
const mcpPage = context.getSelectedMcpPage();
1114+
stubToolDiscovery(mcpPage.pptrPage);
1115+
1116+
const initScript = `
1117+
window.__dtmcp = {
1118+
toolGroup: {
1119+
name: 'In-Page group',
1120+
description: 'Test tools',
1121+
tools: [
1122+
{
1123+
name: 'inPageTool',
1124+
description: 'A test tool',
1125+
inputSchema: {
1126+
type: 'object',
1127+
properties: {},
1128+
},
1129+
execute: () => 'result',
1130+
},
1131+
],
1132+
},
1133+
};
1134+
window.addEventListener('devtoolstooldiscovery', (e) => {
1135+
e.respondWith(window.__dtmcp?.toolGroup);
1136+
});
1137+
`;
1138+
await mcpPage.pptrPage.evaluateOnNewDocument(initScript);
1139+
await mcpPage.pptrPage.evaluate(initScript);
1140+
1141+
await handlerAction(response, context);
1142+
1143+
const {content} = await response.handle(toolName, context);
1144+
const responseText = getTextContent(content[0]);
1145+
assert.ok(
1146+
responseText.includes('inPageTool'),
1147+
`Should include in-page tool name in the ${toolName} response`,
1148+
);
1149+
},
1150+
undefined,
1151+
{categoryInPageTools: true} as ParsedArguments,
1152+
);
1153+
}
1154+
1155+
it('includes in-page tools in list_pages response', async () => {
1156+
await testIncludesInPageTools(async (response, context) => {
1157+
const listPagesDef = listPages({
1158+
categoryInPageTools: true,
1159+
} as ParsedArguments);
1160+
await listPagesDef.handler({params: {}}, response, context);
1161+
}, 'list_pages');
1162+
});
1163+
1164+
it('includes in-page tools in select_page response', async () => {
1165+
await testIncludesInPageTools(async (response, context) => {
1166+
const pageId =
1167+
context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1;
1168+
await selectPage.handler({params: {pageId}}, response, context);
1169+
}, 'select_page');
1170+
});
1171+
1172+
it('includes in-page tools in close_page response', async () => {
1173+
await testIncludesInPageTools(async (response, context) => {
1174+
const pageId =
1175+
context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1;
1176+
await closePage.handler({params: {pageId}}, response, context);
1177+
}, 'close_page');
1178+
});
1179+
1180+
it('includes in-page tools in navigate_page response', async () => {
1181+
await testIncludesInPageTools(async (response, context) => {
1182+
await navigatePage.handler(
1183+
{
1184+
params: {type: 'url', url: 'about:blank'},
1185+
page: context.getSelectedMcpPage(),
1186+
},
1187+
response,
1188+
context,
1189+
);
1190+
}, 'navigate_page');
1191+
});
1192+
1193+
it('includes in-page tools in new_page response', async () => {
1194+
await testIncludesInPageTools(async (response, context) => {
1195+
// Workaround to ensure the test environment's new page contain in-page tools
1196+
sinon.stub(context, 'newPage').resolves(context.getSelectedMcpPage());
1197+
1198+
await newPage.handler(
1199+
{
1200+
params: {url: 'about:blank'},
1201+
},
1202+
response,
1203+
context,
1204+
);
1205+
}, 'new_page');
1206+
});
10941207
});

tests/tools/inPage.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import assert from 'node:assert';
88
import {describe, it} from 'node:test';
99

1010
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
11+
import type {ToolGroup, ToolDefinition} from '../../src/tools/inPage.js';
1112
import {listInPageTools} from '../../src/tools/inPage.js';
1213
import {withMcpContext} from '../utils.js';
1314

@@ -85,7 +86,11 @@ describe('inPage', () => {
8586
const result = await response.handle('list_in_page_tools', context);
8687
assert.ok('inPageTools' in result.structuredContent);
8788
assert.deepEqual(
88-
(result.structuredContent as {inPageTools: undefined}).inPageTools,
89+
(
90+
result.structuredContent as {
91+
inPageTools: ToolGroup<ToolDefinition>;
92+
}
93+
).inPageTools,
8994
{},
9095
);
9196
},
@@ -109,7 +114,11 @@ describe('inPage', () => {
109114
const result = await response.handle('list_in_page_tools', context);
110115
assert.ok('inPageTools' in result.structuredContent);
111116
assert.strictEqual(
112-
(result.structuredContent as {inPageTools: undefined}).inPageTools,
117+
(
118+
result.structuredContent as {
119+
inPageTools: ToolGroup<ToolDefinition>;
120+
}
121+
).inPageTools,
113122
undefined,
114123
);
115124
},

0 commit comments

Comments
 (0)