豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 40c241b

Browse files
authored
refactor: move dialog handling to McpPage (#1059)
- renamed getSelectedPage to getSelectedPptrPage - removes getSelectedPptrPage from tool interfaces - moves dialog handling to McpPage - makes responses to be optionally McpPage-scoped
1 parent 302e5a0 commit 40c241b

22 files changed

+248
-213
lines changed

src/McpContext.ts

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import type {
2323
BrowserContext,
2424
ConsoleMessage,
2525
Debugger,
26-
Dialog,
2726
ElementHandle,
2827
HTTPRequest,
2928
Page,
@@ -205,7 +204,7 @@ export class McpContext implements Context {
205204
}
206205

207206
#resolveTargetPage(): Page {
208-
return this.#requestPage?.pptrPage ?? this.getSelectedPage();
207+
return this.#requestPage?.pptrPage ?? this.getSelectedPptrPage();
209208
}
210209

211210
resolveCdpRequestId(cdpRequestId: string): number | undefined {
@@ -327,7 +326,7 @@ export class McpContext implements Context {
327326
}
328327

329328
async restoreEmulation(targetPage?: Page) {
330-
const page = targetPage ?? this.getSelectedPage();
329+
const page = targetPage ?? this.getSelectedPptrPage();
331330
const mcpPage = this.#getMcpPage(page);
332331
const currentSetting = mcpPage.emulationSettings;
333332
await this.emulate(currentSetting, targetPage);
@@ -344,7 +343,7 @@ export class McpContext implements Context {
344343
},
345344
targetPage?: Page,
346345
): Promise<void> {
347-
const page = targetPage ?? this.getSelectedPage();
346+
const page = targetPage ?? this.getSelectedPptrPage();
348347
const mcpPage = this.#getMcpPage(page);
349348
const newSettings: EmulationSettings = {...mcpPage.emulationSettings};
350349
let timeoutsNeedUpdate = false;
@@ -492,23 +491,7 @@ export class McpContext implements Context {
492491
return this.#options.performanceCrux;
493492
}
494493

495-
getDialog(page?: Page): Dialog | undefined {
496-
const targetPage =
497-
page ?? this.#requestPage?.pptrPage ?? this.#selectedPage;
498-
if (!targetPage) {
499-
return undefined;
500-
}
501-
return this.#mcpPages.get(targetPage)?.dialog;
502-
}
503-
504-
clearDialog(page?: Page): void {
505-
const targetPage = page ?? this.#selectedPage;
506-
if (targetPage) {
507-
this.#mcpPages.get(targetPage)?.clearDialog();
508-
}
509-
}
510-
511-
getSelectedPage(): Page {
494+
getSelectedPptrPage(): Page {
512495
const page = this.#selectedPage;
513496
if (!page) {
514497
throw new Error('No page selected');
@@ -522,7 +505,7 @@ export class McpContext implements Context {
522505
}
523506

524507
getSelectedMcpPage(): McpPage {
525-
const page = this.getSelectedPage();
508+
const page = this.getSelectedPptrPage();
526509
return this.#getMcpPage(page);
527510
}
528511

@@ -555,7 +538,7 @@ export class McpContext implements Context {
555538
}
556539

557540
#getSelectedMcpPage(): McpPage {
558-
return this.#getMcpPage(this.getSelectedPage());
541+
return this.#getMcpPage(this.getSelectedPptrPage());
559542
}
560543

561544
isPageSelected(page: Page): boolean {
@@ -595,7 +578,7 @@ export class McpContext implements Context {
595578
}
596579

597580
#updateSelectedPageTimeouts() {
598-
const page = this.getSelectedPage();
581+
const page = this.getSelectedPptrPage();
599582
// For waiters 5sec timeout should be sufficient.
600583
// Increased in case we throttle the CPU
601584
const cpuMultiplier = this.getCpuThrottlingRate();
@@ -922,7 +905,7 @@ export class McpContext implements Context {
922905
devtoolsData: DevToolsData | undefined = undefined,
923906
targetPage?: Page,
924907
): Promise<void> {
925-
const page = targetPage ?? this.getSelectedPage();
908+
const page = targetPage ?? this.getSelectedPptrPage();
926909
const mcpPage = this.#getMcpPage(page);
927910
const rootNode = await page.accessibility.snapshot({
928911
includeIframes: true,
@@ -1063,7 +1046,7 @@ export class McpContext implements Context {
10631046
action: () => Promise<unknown>,
10641047
options?: {timeout?: number},
10651048
): Promise<void> {
1066-
const page = this.getSelectedPage();
1049+
const page = this.getSelectedPptrPage();
10671050
const cpuMultiplier = this.getCpuThrottlingRate();
10681051
const networkMultiplier = getNetworkMultiplierFromString(
10691052
this.getNetworkConditions(),
@@ -1085,7 +1068,7 @@ export class McpContext implements Context {
10851068
timeout?: number,
10861069
targetPage?: Page,
10871070
): Promise<Element> {
1088-
const page = targetPage ?? this.getSelectedPage();
1071+
const page = targetPage ?? this.getSelectedPptrPage();
10891072
const frames = page.frames();
10901073

10911074
let locator = this.#locatorClass.race(

src/McpPage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export class McpPage implements ContextPage {
5959
return this.#dialog;
6060
}
6161

62+
getDialog(): Dialog | undefined {
63+
return this.dialog;
64+
}
65+
6266
clearDialog(): void {
6367
this.#dialog = undefined;
6468
}

src/McpResponse.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {IssueFormatter} from './formatters/IssueFormatter.js';
1010
import {NetworkFormatter} from './formatters/NetworkFormatter.js';
1111
import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
1212
import type {McpContext} from './McpContext.js';
13+
import type {McpPage} from './McpPage.js';
1314
import {UncaughtError} from './PageCollector.js';
1415
import {DevTools} from './third_party/index.js';
1516
import type {
@@ -70,11 +71,16 @@ export class McpResponse implements Response {
7071
#devToolsData?: DevToolsData;
7172
#tabId?: string;
7273
#args: ParsedArguments;
74+
#page?: McpPage;
7375

7476
constructor(args: ParsedArguments) {
7577
this.#args = args;
7678
}
7779

80+
setPage(page: McpPage): void {
81+
this.#page = page;
82+
}
83+
7884
attachDevToolsData(data: DevToolsData): void {
7985
this.#devToolsData = data;
8086
}
@@ -517,7 +523,7 @@ export class McpResponse implements Response {
517523
structuredContent.colorScheme = colorScheme;
518524
}
519525

520-
const dialog = context.getDialog();
526+
const dialog = this.#page?.getDialog();
521527
if (dialog) {
522528
const defaultValueIfNeeded =
523529
dialog.type() === 'prompt'

src/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ export async function createMcpServer(
153153
const schema =
154154
'pageScoped' in tool &&
155155
tool.pageScoped &&
156-
serverArgs.experimentalPageIdRouting
156+
serverArgs.experimentalPageIdRouting &&
157+
!serverArgs.slim
157158
? {...tool.schema, ...pageIdSchema}
158159
: tool.schema;
159160

@@ -179,10 +180,13 @@ export async function createMcpServer(
179180
try {
180181
if ('pageScoped' in tool && tool.pageScoped) {
181182
const page =
182-
serverArgs.experimentalPageIdRouting && params.pageId
183+
serverArgs.experimentalPageIdRouting &&
184+
params.pageId &&
185+
!serverArgs.slim
183186
? context.resolvePageById(params.pageId)
184187
: context.getSelectedMcpPage();
185188
context.setRequestPage(page);
189+
response.setPage(page);
186190
await tool.handler(
187191
{
188192
params,

src/tools/ToolDefinition.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ export type Context = Readonly<{
136136
isCruxEnabled(): boolean;
137137
recordedTraces(): TraceResult[];
138138
storeTraceRecording(result: TraceResult): void;
139-
// TODO: Remove once slim tools are converted to pageScoped: true.
140-
getSelectedPage(): Page;
141-
getDialog(page?: Page): Dialog | undefined;
142-
clearDialog(page?: Page): void;
143139
getPageById(pageId: number): Page;
144140
newPage(
145141
background?: boolean,
@@ -198,6 +194,9 @@ export type ContextPage = Readonly<{
198194
readonly pptrPage: Page;
199195
getAXNodeByUid(uid: string): TextSnapshotNode | undefined;
200196
getElementByUid(uid: string): Promise<ElementHandle<Element>>;
197+
198+
getDialog(): Dialog | undefined;
199+
clearDialog(): void;
201200
}>;
202201

203202
export function defineTool<Schema extends zod.ZodRawShape>(

src/tools/pages.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export const navigatePage = definePageTool({
187187
void dialog.dismiss();
188188
}
189189
// We are not going to report the dialog like regular dialogs.
190-
context.clearDialog(page.pptrPage);
190+
page.clearDialog();
191191
}
192192
};
193193

@@ -333,9 +333,9 @@ export const handleDialog = definePageTool({
333333
.optional()
334334
.describe('Optional prompt text to enter into the dialog.'),
335335
},
336-
handler: async (request, response, context) => {
336+
handler: async (request, response, _context) => {
337337
const page = request.page;
338-
const dialog = context.getDialog(page.pptrPage);
338+
const dialog = page.getDialog();
339339
if (!dialog) {
340340
throw new Error('No open dialog found');
341341
}
@@ -363,7 +363,7 @@ export const handleDialog = definePageTool({
363363
}
364364
}
365365

366-
context.clearDialog(page.pptrPage);
366+
page.clearDialog();
367367
response.setIncludePages(true);
368368
},
369369
});

src/tools/slim/tools.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import type {Dialog} from '../../third_party/index.js';
88
import {zod} from '../../third_party/index.js';
99
import {ToolCategory} from '../categories.js';
10-
import {defineTool} from '../ToolDefinition.js';
10+
import {definePageTool} from '../ToolDefinition.js';
1111

12-
export const screenshot = defineTool({
12+
export const screenshot = definePageTool({
1313
name: 'screenshot',
1414
description: `Takes a screenshot`,
1515
annotations: {
@@ -19,8 +19,8 @@ export const screenshot = defineTool({
1919
},
2020
schema: {},
2121
handler: async (request, response, context) => {
22-
const page = context.getSelectedPage();
23-
const screenshot = await page.screenshot({
22+
const page = request.page;
23+
const screenshot = await page.pptrPage.screenshot({
2424
type: 'png',
2525
optimizeForSpeed: true,
2626
});
@@ -32,7 +32,7 @@ export const screenshot = defineTool({
3232
},
3333
});
3434

35-
export const navigate = defineTool({
35+
export const navigate = definePageTool({
3636
name: 'navigate',
3737
description: `Loads a URL`,
3838
annotations: {
@@ -42,8 +42,9 @@ export const navigate = defineTool({
4242
schema: {
4343
url: zod.string().describe('URL to navigate to'),
4444
},
45-
handler: async (request, response, context) => {
46-
const page = context.getSelectedPage();
45+
handler: async (request, response) => {
46+
const page = request.page;
47+
4748
const options = {
4849
timeout: 30_000,
4950
};
@@ -53,22 +54,22 @@ export const navigate = defineTool({
5354
response.appendResponseLine(`Accepted a beforeunload dialog.`);
5455
void dialog.accept();
5556
// We are not going to report the dialog like regular dialogs.
56-
context.clearDialog();
57+
page.clearDialog();
5758
}
5859
};
5960

60-
page.on('dialog', dialogHandler);
61+
page.pptrPage.on('dialog', dialogHandler);
6162

6263
try {
63-
await page.goto(request.params.url, options);
64-
response.appendResponseLine(`Navigated to ${page.url()}.`);
64+
await page.pptrPage.goto(request.params.url, options);
65+
response.appendResponseLine(`Navigated to ${page.pptrPage.url()}.`);
6566
} finally {
66-
page.off('dialog', dialogHandler);
67+
page.pptrPage.off('dialog', dialogHandler);
6768
}
6869
},
6970
});
7071

71-
export const evaluate = defineTool({
72+
export const evaluate = definePageTool({
7273
name: 'evaluate',
7374
description: `Evaluates a JavaScript script`,
7475
annotations: {
@@ -78,10 +79,10 @@ export const evaluate = defineTool({
7879
schema: {
7980
script: zod.string().describe(`JS script to run on the page`),
8081
},
81-
handler: async (request, response, context) => {
82-
const page = context.getSelectedPage();
82+
handler: async (request, response) => {
83+
const page = request.page;
8384
try {
84-
const result = await page.evaluate(request.params.script);
85+
const result = await page.pptrPage.evaluate(request.params.script);
8586
response.appendResponseLine(JSON.stringify(result));
8687
} catch (err) {
8788
response.appendResponseLine(String(err.message));

tests/McpContext.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('McpContext', () => {
2222

2323
it('list pages', async () => {
2424
await withMcpContext(async (_response, context) => {
25-
const page = context.getSelectedPage();
25+
const page = context.getSelectedPptrPage();
2626
await page.setContent(
2727
html`<button>Click me</button>
2828
<input
@@ -104,7 +104,7 @@ describe('McpContext', () => {
104104
it('resolves uid from a non-selected page snapshot', async () => {
105105
await withMcpContext(async (_response, context) => {
106106
// Page 1: set content and snapshot
107-
const page1 = context.getSelectedPage();
107+
const page1 = context.getSelectedPptrPage();
108108
await page1.setContent(html`<button>Page1 Button</button>`);
109109
await context.createTextSnapshot(false, undefined, page1);
110110

@@ -230,7 +230,7 @@ describe('McpContext', () => {
230230
it('resolves for default context page alongside isolated contexts', async () => {
231231
await withMcpContext(async (_response, context) => {
232232
// Default context page (already exists from withMcpContext setup).
233-
const defaultPage = context.getSelectedPage();
233+
const defaultPage = context.getSelectedPptrPage();
234234
await defaultPage.setContent(html`<button>Default Button</button>`);
235235
await context.createTextSnapshot(false, undefined, defaultPage);
236236
const defaultUid = '1_1';

0 commit comments

Comments
 (0)