diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 5e5d3cef7..d158c1c7b 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -27,6 +27,7 @@ export const listPages = defineTool(args => { schema: {}, handler: async (_request, response) => { response.setIncludePages(true); + response.setListInPageTools(); }, }; }); @@ -53,6 +54,7 @@ export const selectPage = defineTool({ const page = context.getPageById(request.params.pageId); context.selectPage(page); response.setIncludePages(true); + response.setListInPageTools(); if (request.params.bringToFront) { await page.pptrPage.bringToFront(); } @@ -82,6 +84,7 @@ export const closePage = defineTool({ } } response.setIncludePages(true); + response.setListInPageTools(); }, }); @@ -126,6 +129,7 @@ export const newPage = defineTool({ ); response.setIncludePages(true); + response.setListInPageTools(); }, }); @@ -275,6 +279,7 @@ export const navigatePage = definePageTool({ } response.setIncludePages(true); + response.setListInPageTools(); }, }); diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index dbdb9c571..66be61e84 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -13,6 +13,15 @@ import {describe, it} from 'node:test'; import sinon from 'sinon'; import type {ParsedArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js'; +import type {McpContext} from '../src/McpContext.js'; +import type {McpResponse} from '../src/McpResponse.js'; +import { + closePage, + listPages, + navigatePage, + newPage, + selectPage, +} from '../src/tools/pages.js'; import type {InsightName} from '../src/trace-processing/parse.js'; import { parseRawTraceBuffer, @@ -1091,4 +1100,108 @@ describe('inPage tools', () => { {categoryInPageTools: true} as ParsedArguments, ); }); + + async function testIncludesInPageTools( + handlerAction: ( + response: McpResponse, + context: McpContext, + ) => Promise, + toolName: string, + ) { + await withMcpContext( + async (response, context) => { + const mcpPage = context.getSelectedMcpPage(); + stubToolDiscovery(mcpPage.pptrPage); + + const initScript = ` + window.__dtmcp = { + toolGroup: { + name: 'In-Page group', + description: 'Test tools', + tools: [ + { + name: 'inPageTool', + description: 'A test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + execute: () => 'result', + }, + ], + }, + }; + window.addEventListener('devtoolstooldiscovery', (e) => { + e.respondWith(window.__dtmcp?.toolGroup); + }); + `; + await mcpPage.pptrPage.evaluateOnNewDocument(initScript); + await mcpPage.pptrPage.evaluate(initScript); + + await handlerAction(response, context); + + const {content} = await response.handle(toolName, context); + const responseText = getTextContent(content[0]); + assert.ok( + responseText.includes('inPageTool'), + `Should include in-page tool name in the ${toolName} response`, + ); + }, + undefined, + {categoryInPageTools: true} as ParsedArguments, + ); + } + + it('includes in-page tools in list_pages response', async () => { + await testIncludesInPageTools(async (response, context) => { + const listPagesDef = listPages({ + categoryInPageTools: true, + } as ParsedArguments); + await listPagesDef.handler({params: {}}, response, context); + }, 'list_pages'); + }); + + it('includes in-page tools in select_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + const pageId = + context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1; + await selectPage.handler({params: {pageId}}, response, context); + }, 'select_page'); + }); + + it('includes in-page tools in close_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + const pageId = + context.getPageId(context.getSelectedMcpPage().pptrPage) ?? 1; + await closePage.handler({params: {pageId}}, response, context); + }, 'close_page'); + }); + + it('includes in-page tools in navigate_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + await navigatePage.handler( + { + params: {type: 'url', url: 'about:blank'}, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + }, 'navigate_page'); + }); + + it('includes in-page tools in new_page response', async () => { + await testIncludesInPageTools(async (response, context) => { + // Workaround to ensure the test environment's new page contain in-page tools + sinon.stub(context, 'newPage').resolves(context.getSelectedMcpPage()); + + await newPage.handler( + { + params: {url: 'about:blank'}, + }, + response, + context, + ); + }, 'new_page'); + }); }); diff --git a/tests/tools/inPage.test.ts b/tests/tools/inPage.test.ts index 79540811f..448f6b0ec 100644 --- a/tests/tools/inPage.test.ts +++ b/tests/tools/inPage.test.ts @@ -8,6 +8,7 @@ import assert from 'node:assert'; import {describe, it} from 'node:test'; import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; +import type {ToolGroup, ToolDefinition} from '../../src/tools/inPage.js'; import {listInPageTools} from '../../src/tools/inPage.js'; import {withMcpContext} from '../utils.js'; @@ -85,7 +86,11 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.deepEqual( - (result.structuredContent as {inPageTools: undefined}).inPageTools, + ( + result.structuredContent as { + inPageTools: ToolGroup; + } + ).inPageTools, {}, ); }, @@ -109,7 +114,11 @@ describe('inPage', () => { const result = await response.handle('list_in_page_tools', context); assert.ok('inPageTools' in result.structuredContent); assert.strictEqual( - (result.structuredContent as {inPageTools: undefined}).inPageTools, + ( + result.structuredContent as { + inPageTools: ToolGroup; + } + ).inPageTools, undefined, ); },