@@ -12,14 +12,15 @@ import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
1212import type { McpContext } from './McpContext.js' ;
1313import type { McpPage } from './McpPage.js' ;
1414import { UncaughtError } from './PageCollector.js' ;
15- import { DevTools } from './third_party/index.js' ;
15+ import { DevTools , type Protocol } from './third_party/index.js' ;
1616import type {
1717 ConsoleMessage ,
1818 ImageContent ,
1919 Page ,
2020 ResourceType ,
2121 TextContent ,
2222} from './third_party/index.js' ;
23+ import type { ToolGroup , ToolDefinition } from './tools/inPage.js' ;
2324import { handleDialog } from './tools/pages.js' ;
2425import type {
2526 DevToolsData ,
@@ -40,6 +41,59 @@ interface TraceInsightData {
4041 insightName : InsightName ;
4142}
4243
44+ async function getToolGroup (
45+ page : McpPage ,
46+ ) : Promise < ToolGroup < ToolDefinition > | undefined > {
47+ // Check if there is a `devtoolstooldiscovery` event listener
48+ const windowHandle = await page . pptrPage . evaluateHandle ( ( ) => window ) ;
49+ // @ts -expect-error internal API
50+ const client = page . pptrPage . _client ( ) ;
51+ const { listeners} : { listeners : Protocol . DOMDebugger . EventListener [ ] } =
52+ await client . send ( 'DOMDebugger.getEventListeners' , {
53+ objectId : windowHandle . remoteObject ( ) . objectId ,
54+ } ) ;
55+ if ( listeners . find ( l => l . type === 'devtoolstooldiscovery' ) === undefined ) {
56+ return ;
57+ }
58+
59+ const toolGroup = await page . pptrPage . evaluate ( ( ) => {
60+ return new Promise < ToolGroup < ToolDefinition > | undefined > ( resolve => {
61+ const event = new CustomEvent ( 'devtoolstooldiscovery' ) ;
62+ // @ts -expect-error Adding custom property
63+ event . respondWith = ( toolGroup : ToolGroup ) => {
64+ if ( ! window . __dtmcp ) {
65+ window . __dtmcp = { } ;
66+ }
67+ window . __dtmcp . toolGroup = toolGroup ;
68+
69+ // When receiving a toolGroup for the first time, expose a simple execution helper
70+ if ( ! window . __dtmcp . executeTool ) {
71+ window . __dtmcp . executeTool = async ( toolName , args ) => {
72+ if ( ! window . __dtmcp ?. toolGroup ) {
73+ throw new Error ( 'No tools found on the page' ) ;
74+ }
75+ const tool = window . __dtmcp . toolGroup . tools . find (
76+ t => t . name === toolName ,
77+ ) ;
78+ if ( ! tool ) {
79+ throw new Error ( `Tool ${ toolName } not found` ) ;
80+ }
81+ return await tool . execute ( args ) ;
82+ } ;
83+ }
84+
85+ resolve ( toolGroup ) ;
86+ } ;
87+ window . dispatchEvent ( event ) ;
88+ // If the page does not synchronously call `event.respondWith`, return instead of timing out
89+ setTimeout ( ( ) => {
90+ resolve ( undefined ) ;
91+ } , 0 ) ;
92+ } ) ;
93+ } ) ;
94+ return toolGroup ;
95+ }
96+
4397export class McpResponse implements Response {
4498 #includePages = false ;
4599 #includeExtensionServiceWorkers = false ;
@@ -70,6 +124,7 @@ export class McpResponse implements Response {
70124 includePreservedMessages ?: boolean ;
71125 } ;
72126 #listExtensions?: boolean ;
127+ #listInPageTools?: boolean ;
73128 #devToolsData?: DevToolsData ;
74129 #tabId?: string ;
75130 #args: ParsedArguments ;
@@ -110,6 +165,12 @@ export class McpResponse implements Response {
110165 this . #listExtensions = true ;
111166 }
112167
168+ setListInPageTools ( ) : void {
169+ if ( this . #args. categoryInPageTools ) {
170+ this . #listInPageTools = true ;
171+ }
172+ }
173+
113174 setIncludeNetworkRequests (
114175 value : boolean ,
115176 options ?: PaginationOptions & {
@@ -357,6 +418,12 @@ export class McpResponse implements Response {
357418 if ( this . #listExtensions) {
358419 extensions = context . listExtensions ( ) ;
359420 }
421+
422+ let inPageTools : ToolGroup < ToolDefinition > | undefined ;
423+ if ( this . #listInPageTools) {
424+ inPageTools = await getToolGroup ( context . getSelectedMcpPage ( ) ) ;
425+ }
426+
360427 let consoleMessages : Array < ConsoleFormatter | IssueFormatter > | undefined ;
361428 if ( this . #consoleDataOptions?. include ) {
362429 if ( ! this . #page) {
@@ -459,6 +526,7 @@ export class McpResponse implements Response {
459526 traceSummary : this . #attachedTraceSummary,
460527 extensions,
461528 lighthouseResult : this . #attachedLighthouseResult,
529+ inPageTools,
462530 } ) ;
463531 }
464532
@@ -475,6 +543,7 @@ export class McpResponse implements Response {
475543 traceInsight ?: TraceInsightData ;
476544 extensions ?: InstalledExtension [ ] ;
477545 lighthouseResult ?: LighthouseData ;
546+ inPageTools ?: ToolGroup < ToolDefinition > ;
478547 } ,
479548 ) : { content : Array < TextContent | ImageContent > ; structuredContent : object } {
480549 const structuredContent : {
@@ -489,6 +558,7 @@ export class McpResponse implements Response {
489558 traceInsights ?: Array < { insightName : string ; insightKey : string } > ;
490559 lighthouseResult ?: object ;
491560 extensions ?: object [ ] ;
561+ inPageTools ?: object ;
492562 message ?: string ;
493563 networkConditions ?: string ;
494564 navigationTimeout ?: number ;
@@ -726,6 +796,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
726796 }
727797 }
728798
799+ if ( this . #listInPageTools) {
800+ structuredContent . inPageTools = data . inPageTools ?? undefined ;
801+ response . push ( '## In-page tools' ) ;
802+ if ( ! data . inPageTools || ! data . inPageTools . tools ) {
803+ response . push ( 'No in-page tools available.' ) ;
804+ } else {
805+ const toolGroup = data . inPageTools ;
806+ response . push ( `${ toolGroup . name } : ${ toolGroup . description } ` ) ;
807+ response . push ( 'Available tools:' ) ;
808+ const toolDefinitionsMessage = toolGroup . tools
809+ . map ( tool => {
810+ return `name="${ tool . name } ", description="${ tool . description } ", inputSchema=${ JSON . stringify (
811+ tool . inputSchema ,
812+ ) } `;
813+ } )
814+ . join ( '\n' ) ;
815+ response . push ( toolDefinitionsMessage ) ;
816+ }
817+ }
818+
729819 if ( this . #networkRequestsOptions?. include && data . networkRequests ) {
730820 const requests = data . networkRequests ;
731821
0 commit comments