@@ -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 } from './tools/inPage.js' ;
2324import { handleDialog } from './tools/pages.js' ;
2425import type {
2526 DevToolsData ,
@@ -40,6 +41,57 @@ interface TraceInsightData {
4041 insightName : InsightName ;
4142}
4243
44+ async function getToolGroup ( page : McpPage ) : Promise < ToolGroup | undefined > {
45+ // Check if there is a `devtoolstooldiscovery` event listener
46+ const windowHandle = await page . pptrPage . evaluateHandle ( ( ) => window ) ;
47+ // @ts -expect-error internal API
48+ const client = page . pptrPage . _client ( ) ;
49+ const { listeners} : { listeners : Protocol . DOMDebugger . EventListener [ ] } =
50+ await client . send ( 'DOMDebugger.getEventListeners' , {
51+ objectId : windowHandle . remoteObject ( ) . objectId ,
52+ } ) ;
53+ if ( listeners . find ( l => l . type === 'devtoolstooldiscovery' ) === undefined ) {
54+ return ;
55+ }
56+
57+ const toolGroup = await page . pptrPage . evaluate ( ( ) => {
58+ return new Promise < ToolGroup | undefined > ( resolve => {
59+ const event = new CustomEvent ( 'devtoolstooldiscovery' ) ;
60+ // @ts -expect-error Adding custom property
61+ event . respondWith = ( toolGroup : ToolGroup ) => {
62+ if ( ! window . __dtmcp ) {
63+ window . __dtmcp = { } ;
64+ }
65+ window . __dtmcp . toolGroup = toolGroup ;
66+
67+ // When receiving a toolGroup for the first time, expose a simple execution helper
68+ if ( ! window . __dtmcp . executeTool ) {
69+ window . __dtmcp . executeTool = async ( toolName , args ) => {
70+ if ( ! window . __dtmcp ?. toolGroup ) {
71+ throw new Error ( 'No tools found on the page' ) ;
72+ }
73+ const tool = window . __dtmcp . toolGroup . tools . find (
74+ t => t . name === toolName ,
75+ ) ;
76+ if ( ! tool ) {
77+ throw new Error ( `Tool ${ toolName } not found` ) ;
78+ }
79+ return await tool . execute ( args ) ;
80+ } ;
81+ }
82+
83+ resolve ( toolGroup ) ;
84+ } ;
85+ window . dispatchEvent ( event ) ;
86+ // If the page does not call `event.respondWith`, return instead of timing out
87+ setTimeout ( ( ) => {
88+ resolve ( undefined ) ;
89+ } , 0 ) ;
90+ } ) ;
91+ } ) ;
92+ return toolGroup ;
93+ }
94+
4395export class McpResponse implements Response {
4496 #includePages = false ;
4597 #includeExtensionServiceWorkers = false ;
@@ -70,6 +122,7 @@ export class McpResponse implements Response {
70122 includePreservedMessages ?: boolean ;
71123 } ;
72124 #listExtensions?: boolean ;
125+ #listInPageTools?: boolean ;
73126 #devToolsData?: DevToolsData ;
74127 #tabId?: string ;
75128 #args: ParsedArguments ;
@@ -110,6 +163,12 @@ export class McpResponse implements Response {
110163 this . #listExtensions = true ;
111164 }
112165
166+ setListInPageTools ( ) : void {
167+ if ( this . #args. categoryInPageTools ) {
168+ this . #listInPageTools = true ;
169+ }
170+ }
171+
113172 setIncludeNetworkRequests (
114173 value : boolean ,
115174 options ?: PaginationOptions & {
@@ -357,6 +416,12 @@ export class McpResponse implements Response {
357416 if ( this . #listExtensions) {
358417 extensions = context . listExtensions ( ) ;
359418 }
419+
420+ let inPageTools : ToolGroup | undefined ;
421+ if ( this . #listInPageTools) {
422+ inPageTools = await getToolGroup ( context . getSelectedMcpPage ( ) ) ;
423+ }
424+
360425 let consoleMessages : Array < ConsoleFormatter | IssueFormatter > | undefined ;
361426 if ( this . #consoleDataOptions?. include ) {
362427 if ( ! this . #page) {
@@ -459,6 +524,7 @@ export class McpResponse implements Response {
459524 traceSummary : this . #attachedTraceSummary,
460525 extensions,
461526 lighthouseResult : this . #attachedLighthouseResult,
527+ inPageTools,
462528 } ) ;
463529 }
464530
@@ -475,6 +541,7 @@ export class McpResponse implements Response {
475541 traceInsight ?: TraceInsightData ;
476542 extensions ?: InstalledExtension [ ] ;
477543 lighthouseResult ?: LighthouseData ;
544+ inPageTools ?: ToolGroup ;
478545 } ,
479546 ) : { content : Array < TextContent | ImageContent > ; structuredContent : object } {
480547 const structuredContent : {
@@ -489,6 +556,7 @@ export class McpResponse implements Response {
489556 traceInsights ?: Array < { insightName : string ; insightKey : string } > ;
490557 lighthouseResult ?: object ;
491558 extensions ?: object [ ] ;
559+ inPageTools ?: object ;
492560 message ?: string ;
493561 networkConditions ?: string ;
494562 navigationTimeout ?: number ;
@@ -726,6 +794,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
726794 }
727795 }
728796
797+ if ( this . #listInPageTools) {
798+ structuredContent . inPageTools = data . inPageTools ?? undefined ;
799+ response . push ( '## In-page tools' ) ;
800+ if ( ! data . inPageTools ) {
801+ response . push ( 'No in-page tools available.' ) ;
802+ } else {
803+ const toolGroup = data . inPageTools ;
804+ response . push ( `${ toolGroup . name } : ${ toolGroup . description } ` ) ;
805+ response . push ( 'Available tools:' ) ;
806+ const toolDefinitionsMessage = toolGroup . tools
807+ . map ( tool => {
808+ return `name="${ tool . name } ", description="${ tool . description } ", inputSchema=${ JSON . stringify (
809+ tool . inputSchema ,
810+ ) } `;
811+ } )
812+ . join ( '\n' ) ;
813+ response . push ( toolDefinitionsMessage ) ;
814+ }
815+ }
816+
729817 if ( this . #networkRequestsOptions?. include && data . networkRequests ) {
730818 const requests = data . networkRequests ;
731819
0 commit comments