Skip to content

Commit 1dba927

Browse files
committed
chore: avoid unnecessary page and uids retrieval
1 parent 4160e27 commit 1dba927

3 files changed

Lines changed: 110 additions & 39 deletions

File tree

src/McpContext.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ export class McpContext implements Context {
621621
const mcpPage = this.#mcpPages.get(page);
622622
if (!mcpPage?.textSnapshot) {
623623
throw new Error(
624-
`No snapshot found for page ${mcpPage?.id ?? '?'}. Use take_snapshot to capture one.`,
624+
`No snapshot found for page ${mcpPage?.id ?? '?'}. Use ${takeSnapshot.name} to capture one.`,
625625
);
626626
}
627627
const node = mcpPage.textSnapshot.idToNode.get(uid);
@@ -662,7 +662,9 @@ export class McpContext implements Context {
662662
}
663663
}
664664
if (!anySnapshot) {
665-
throw new Error(`No snapshot found. Use take_snapshot to capture one.`);
665+
throw new Error(
666+
`No snapshot found. Use ${takeSnapshot.name} to capture one.`,
667+
);
666668
}
667669
throw new Error('No such element found in any snapshot.');
668670
}

src/tools/script.ts

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {zod} from '../third_party/index.js';
99
import type {Frame, JSHandle, Page, WebWorker} from '../third_party/index.js';
1010

1111
import {ToolCategory} from './categories.js';
12-
import type {Context} from './ToolDefinition.js';
12+
import type {Context, Response} from './ToolDefinition.js';
1313
import {defineTool, pageIdSchema} from './ToolDefinition.js';
1414

1515
export type Evaluatable = Page | Frame | WebWorker;
@@ -61,63 +61,77 @@ Example with arguments: \`(el) => {
6161
: {}),
6262
},
6363
handler: async (request, response, context) => {
64+
const {
65+
serviceWorkerId,
66+
args: uidArgs,
67+
function: fnString,
68+
pageId,
69+
} = request.params;
70+
71+
if (cliArgs?.categoryExtensions && serviceWorkerId) {
72+
if (uidArgs && uidArgs.length > 0) {
73+
throw new Error(
74+
'args (element uids) cannot be used when evaluating in a service worker.',
75+
);
76+
}
77+
if (pageId) {
78+
throw new Error('specify either a pageId or a serviceWorkerId.');
79+
}
80+
81+
const worker = await getWebWorker(context, serviceWorkerId);
82+
await performEvaluation(worker, fnString, [], response, context);
83+
return;
84+
}
85+
6486
const page: Page = cliArgs?.experimentalPageIdRouting
6587
? context.resolvePageById(request.params.pageId)
6688
: context.getSelectedPage();
6789

6890
const args: Array<JSHandle<unknown>> = [];
6991
try {
7092
const frames = new Set<Frame>();
71-
for (const el of request.params.args ?? []) {
93+
for (const el of uidArgs ?? []) {
7294
const handle = await context.getElementByUid(el.uid, page);
7395
frames.add(handle.frame);
7496
args.push(handle);
7597
}
7698

77-
const evaluatable = await getEvaluatable(
78-
context,
79-
page,
80-
frames,
81-
cliArgs?.categoryExtensions,
82-
request.params.serviceWorkerId as string | undefined,
83-
);
84-
85-
const fn = await evaluatable.evaluateHandle(
86-
`(${request.params.function})`,
87-
);
88-
args.unshift(fn);
89-
90-
await context.waitForEventsAfterAction(async () => {
91-
const result = await evaluatable.evaluate(
92-
async (fn, ...args) => {
93-
// @ts-expect-error no types.
94-
return JSON.stringify(await fn(...args));
95-
},
96-
...args,
97-
);
98-
response.appendResponseLine('Script ran on page and returned:');
99-
response.appendResponseLine('```json');
100-
response.appendResponseLine(`${result}`);
101-
response.appendResponseLine('```');
102-
});
99+
const evaluatable = await getPageOrFrame(context, page, frames);
100+
101+
await performEvaluation(evaluatable, fnString, args, response, context);
103102
} finally {
104103
void Promise.allSettled(args.map(arg => arg.dispose()));
105104
}
106105
},
107106
};
108107
});
109108

110-
const getEvaluatable = async (
109+
const performEvaluation = async (
110+
evaluatable: Evaluatable,
111+
fnString: string,
112+
args: Array<JSHandle<unknown>>,
113+
response: Response,
111114
context: Context,
112-
page: Page,
113-
frames: Set<Frame>,
114-
enableExtensions?: boolean,
115-
serviceWorkerId?: string,
116-
): Promise<Evaluatable> => {
117-
if (enableExtensions && serviceWorkerId) {
118-
return getWebWorker(context, serviceWorkerId);
115+
) => {
116+
const fn = await evaluatable.evaluateHandle(`(${fnString})`);
117+
try {
118+
await context.waitForEventsAfterAction(async () => {
119+
const result = await evaluatable.evaluate(
120+
async (fn, ...args) => {
121+
// @ts-expect-error no types for function fn
122+
return JSON.stringify(await fn(...args));
123+
},
124+
fn,
125+
...args,
126+
);
127+
response.appendResponseLine('Script ran on page and returned:');
128+
response.appendResponseLine('```json');
129+
response.appendResponseLine(`${result}`);
130+
response.appendResponseLine('```');
131+
});
132+
} finally {
133+
void fn.dispose();
119134
}
120-
return getPageOrFrame(context, page, frames);
121135
};
122136

123137
const getPageOrFrame = async (

tests/tools/script.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,60 @@ describe('script', () => {
247247
{categoryExtensions: true} as ParsedArguments,
248248
);
249249
});
250+
251+
it('throws error when both pageId and serviceWorkerId are provided', async () => {
252+
await withMcpContext(
253+
async (response, context) => {
254+
await assert.rejects(
255+
evaluateScript({
256+
categoryExtensions: true,
257+
} as ParsedArguments).handler(
258+
{
259+
params: {
260+
function: String(() => 'test'),
261+
serviceWorkerId: 'example_service_worker',
262+
pageId: '1',
263+
},
264+
},
265+
response,
266+
context,
267+
),
268+
{
269+
message: 'specify either a pageId or a serviceWorkerId.',
270+
},
271+
);
272+
},
273+
{},
274+
{categoryExtensions: true} as ParsedArguments,
275+
);
276+
});
277+
278+
it('throws error when args are provided with serviceWorkerId', async () => {
279+
await withMcpContext(
280+
async (response, context) => {
281+
await assert.rejects(
282+
evaluateScript({
283+
categoryExtensions: true,
284+
} as ParsedArguments).handler(
285+
{
286+
params: {
287+
function: String(() => 'test'),
288+
serviceWorkerId: 'example_service_worker',
289+
args: [{uid: '1_1'}],
290+
},
291+
},
292+
response,
293+
context,
294+
),
295+
{
296+
message:
297+
'args (element uids) cannot be used when evaluating in a service worker.',
298+
},
299+
);
300+
},
301+
{},
302+
{categoryExtensions: true} as ParsedArguments,
303+
);
304+
});
250305
});
251306
});

0 commit comments

Comments
 (0)