44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7+ import type { ExtensionServiceWorker } from '../McpContext.js' ;
78import { zod } from '../third_party/index.js' ;
8- import type { Frame , JSHandle , Page } from '../third_party/index.js' ;
9+ import type { Frame , JSHandle , Page , WebWorker } from '../third_party/index.js' ;
910
1011import { ToolCategory } from './categories.js' ;
12+ import type { Context } from './ToolDefinition.js' ;
1113import { definePageTool } from './ToolDefinition.js' ;
1214
13- export const evaluateScript = definePageTool ( {
14- name : 'evaluate_script' ,
15- description : `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,
15+ export type Evaluatable = Page | Frame | WebWorker ;
16+
17+ export const evaluateScript = definePageTool ( cliArgs => {
18+ return {
19+ name : 'evaluate_script' ,
20+ description : `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,
1621so returned values have to be JSON-serializable.` ,
17- annotations : {
18- category : ToolCategory . DEBUGGING ,
19- readOnlyHint : false ,
20- } ,
21- schema : {
22- function : zod . string ( ) . describe (
23- `A JavaScript function declaration to be executed by the tool in the currently selected page.
22+ annotations : {
23+ category : ToolCategory . DEBUGGING ,
24+ readOnlyHint : false ,
25+ } ,
26+ schema : {
27+ function : zod . string ( ) . describe (
28+ `A JavaScript function declaration to be executed by the tool in the currently selected page.
2429Example without arguments: \`() => {
2530 return document.title
2631}\` or \`async () => {
@@ -30,57 +35,121 @@ Example with arguments: \`(el) => {
3035 return el.innerText;
3136}\`
3237` ,
33- ) ,
34- args : zod
35- . array (
36- zod . object ( {
37- uid : zod
38- . string ( )
39- . describe (
40- 'The uid of an element on the page from the page content snapshot' ,
41- ) ,
42- } ) ,
43- )
44- . optional ( )
45- . describe ( `An optional list of arguments to pass to the function.` ) ,
46- } ,
47- handler : async ( request , response , context ) => {
48- const args : Array < JSHandle < unknown > > = [ ] ;
49- try {
50- const frames = new Set < Frame > ( ) ;
51- for ( const el of request . params . args ?? [ ] ) {
52- const handle = await context . getElementByUid ( el . uid , request . page ) ;
53- frames . add ( handle . frame ) ;
54- args . push ( handle ) ;
55- }
56- let pageOrFrame : Page | Frame ;
57- // We can't evaluate the element handle across frames
58- if ( frames . size > 1 ) {
59- throw new Error (
60- "Elements from different frames can't be evaluated together." ,
38+ ) ,
39+ args : zod
40+ . array (
41+ zod . object ( {
42+ uid : zod
43+ . string ( )
44+ . describe (
45+ 'The uid of an element on the page from the page content snapshot' ,
46+ ) ,
47+ } ) ,
48+ )
49+ . optional ( )
50+ . describe ( `An optional list of arguments to pass to the function.` ) ,
51+ ...( cliArgs ?. categoryExtensions
52+ ? {
53+ serviceWorkerId : zod
54+ . string ( )
55+ . optional ( )
56+ . describe (
57+ `An optional service worker id to evaluate the script in.` ,
58+ ) ,
59+ }
60+ : { } ) ,
61+ } ,
62+ handler : async ( request , response , context ) => {
63+ const args : Array < JSHandle < unknown > > = [ ] ;
64+ try {
65+ const frames = new Set < Frame > ( ) ;
66+ for ( const el of request . params . args ?? [ ] ) {
67+ const handle = await context . getElementByUid ( el . uid , request . page ) ;
68+ frames . add ( handle . frame ) ;
69+ args . push ( handle ) ;
70+ }
71+
72+ const evaluatable = await getEvaluatable (
73+ context ,
74+ frames ,
75+ cliArgs ?. categoryExtensions ,
76+ request . params . serviceWorkerId as string | undefined ,
6177 ) ;
62- } else {
63- pageOrFrame = [ ...frames . values ( ) ] [ 0 ] ?? request . page ;
64- }
65- const fn = await pageOrFrame . evaluateHandle (
66- `(${ request . params . function } )` ,
67- ) ;
68- args . unshift ( fn ) ;
69- await context . waitForEventsAfterAction ( async ( ) => {
70- const result = await pageOrFrame . evaluate (
71- async ( fn , ...args ) => {
72- // @ts -expect-error no types.
73- return JSON . stringify ( await fn ( ...args ) ) ;
74- } ,
75- ...args ,
78+
79+ const fn = await evaluatable . evaluateHandle (
80+ `(${ request . params . function } )` ,
7681 ) ;
77- response . appendResponseLine ( 'Script ran on page and returned:' ) ;
78- response . appendResponseLine ( '```json' ) ;
79- response . appendResponseLine ( `${ result } ` ) ;
80- response . appendResponseLine ( '```' ) ;
81- } ) ;
82- } finally {
83- void Promise . allSettled ( args . map ( arg => arg . dispose ( ) ) ) ;
84- }
85- } ,
82+ args . unshift ( fn ) ;
83+
84+ await context . waitForEventsAfterAction ( async ( ) => {
85+ const result = await evaluatable . evaluate (
86+ async ( fn , ...args ) => {
87+ // @ts -expect-error no types.
88+ return JSON . stringify ( await fn ( ...args ) ) ;
89+ } ,
90+ ...args ,
91+ ) ;
92+ response . appendResponseLine ( 'Script ran on page and returned:' ) ;
93+ response . appendResponseLine ( '```json' ) ;
94+ response . appendResponseLine ( `${ result } ` ) ;
95+ response . appendResponseLine ( '```' ) ;
96+ } ) ;
97+ } finally {
98+ void Promise . allSettled ( args . map ( arg => arg . dispose ( ) ) ) ;
99+ }
100+ } ,
101+ } ;
86102} ) ;
103+
104+ const getEvaluatable = async (
105+ context : Context ,
106+ frames : Set < Frame > ,
107+ enableExtensions ?: boolean ,
108+ serviceWorkerId ?: string ,
109+ ) : Promise < Evaluatable > => {
110+ if ( enableExtensions && serviceWorkerId ) {
111+ return getWebWorker ( context , serviceWorkerId ) ;
112+ }
113+ return getPageOrFrame ( context , frames ) ;
114+ } ;
115+
116+ const getPageOrFrame = async (
117+ context : Context ,
118+ frames : Set < Frame > ,
119+ ) : Promise < Page | Frame > => {
120+ let pageOrFrame : Page | Frame ;
121+ // We can't evaluate the element handle across frames
122+ if ( frames . size > 1 ) {
123+ throw new Error (
124+ "Elements from different frames can't be evaluated together." ,
125+ ) ;
126+ } else {
127+ pageOrFrame = [ ...frames . values ( ) ] [ 0 ] ?? context . getSelectedPage ( ) ;
128+ }
129+
130+ return pageOrFrame ;
131+ } ;
132+
133+ const getWebWorker = async (
134+ context : Context ,
135+ serviceWorkerId : string ,
136+ ) : Promise < WebWorker > => {
137+ const serviceWorkers = context . getExtensionServiceWorkers ( ) ;
138+
139+ const serviceWorker = serviceWorkers . find (
140+ ( sw : ExtensionServiceWorker ) =>
141+ context . getExtensionServiceWorkerId ( sw ) === serviceWorkerId ,
142+ ) ;
143+
144+ if ( serviceWorker && serviceWorker . target ) {
145+ const worker = await serviceWorker . target . worker ( ) ;
146+
147+ if ( ! worker ) {
148+ throw new Error ( 'Service worker target not found.' ) ;
149+ }
150+
151+ return worker ;
152+ } else {
153+ throw new Error ( 'Service worker not found.' ) ;
154+ }
155+ } ;
0 commit comments