1+ /**
2+ * @license
3+ * Copyright 2026 Google LLC
4+ * SPDX-License-Identifier: Apache-2.0
5+ */
6+
7+ import { type Page , zod , ajv , type JSONSchema7 } from '../third_party/index.js' ;
8+
9+ import { ToolCategory } from './categories.js' ;
10+ import { defineTool } from './ToolDefinition.js' ;
11+
12+ export interface ToolDefinition {
13+ name : string ;
14+ description : string ;
15+ inputSchema : JSONSchema7 ;
16+ execute : ( input : any ) => any ;
17+ }
18+
19+ export interface ToolGroup {
20+ name : string ;
21+ description : string ;
22+ tools : ToolDefinition [ ] ;
23+ }
24+
25+ declare global {
26+ interface Window {
27+ __mcp_tool_group ?: ToolGroup ;
28+ }
29+ }
30+
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+
50+ export const listInPageTools = defineTool ( {
51+ name : 'list_in_page_tools' ,
52+ description : `Lists all tools the page exposes for providing runtime information.` ,
53+ annotations : {
54+ category : ToolCategory . IN_PAGE ,
55+ readOnlyHint : true ,
56+ } ,
57+ schema : { } ,
58+ handler : async ( _request , response , context ) => {
59+ const page = context . getSelectedPage ( ) ;
60+ const toolGroup = await getToolGroup ( page ) ;
61+ context . setInPageTools ( toolGroup ) ;
62+ response . setListInPageTools ( ) ;
63+ } ,
64+ } ) ;
65+
66+ export const executeInPageTool = defineTool ( {
67+ name : 'execute_in_page_tool' ,
68+ description : `Executes a tool exposed by the page.` ,
69+ annotations : {
70+ category : ToolCategory . IN_PAGE ,
71+ readOnlyHint : false ,
72+ } ,
73+ schema : {
74+ toolName : zod . string ( ) . describe ( 'The name of the tool to execute' ) ,
75+ params : zod . record ( zod . string ( ) , zod . unknown ( ) ) . optional ( ) . describe ( 'The parameters to pass to the tool' ) ,
76+ } ,
77+ handler : async ( request , response , context ) => {
78+ const page = context . getSelectedPage ( ) ;
79+ const toolName = request . params . toolName ;
80+ const params = request . params . params ?? { } ;
81+
82+ // Get tools from context
83+ // const toolGroup = context.getInPageTools();
84+ // Alternatively: get tools from page
85+ const toolGroup = await getToolGroup ( page ) ;
86+ context . setInPageTools ( toolGroup ) ;
87+ const tool = toolGroup ?. tools . find ( t => t . name === toolName ) ;
88+ if ( ! tool ) {
89+ throw new Error ( `Tool ${ toolName } not found` ) ;
90+ }
91+ const ajvInstance = new ajv ( ) ;
92+ const validate = ajvInstance . compile ( tool . inputSchema ) ;
93+ const valid = validate ( params ) ;
94+ if ( ! valid ) {
95+ throw new Error ( `Invalid parameters for tool ${ toolName } : ${ ajvInstance . errorsText ( validate . errors ) } ` ) ;
96+ }
97+
98+ const result = await page . evaluate ( async ( name , args ) => {
99+ if ( ! window . __mcp_tool_group ) {
100+ throw new Error ( 'No tools found on the page' ) ;
101+ }
102+ const tool = window . __mcp_tool_group . tools . find ( t => t . name === name ) ;
103+ if ( tool ) {
104+ return await tool . execute ( args ) ;
105+ }
106+ throw new Error ( `Tool ${ name } not found` ) ;
107+ } , toolName , params ) ;
108+ response . appendResponseLine ( typeof result === 'string' ? result : JSON . stringify ( result , null , 2 ) ) ;
109+ } ,
110+ } ) ;
0 commit comments