@roxybrowser/playwright 2.0.2-beta.0 → 2.0.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/bin/roxybrowser-mcp.js +69 -2
- package/dist/bin/roxybrowser-mcp.js.map +1 -1
- package/dist/mcp/backend/connect.d.ts +1 -0
- package/dist/mcp/backend/connect.d.ts.map +1 -1
- package/dist/mcp/backend/connect.js +4 -2
- package/dist/mcp/backend/connect.js.map +1 -1
- package/dist/mcp/backend/response.d.ts.map +1 -1
- package/dist/mcp/backend/response.js +25 -3
- package/dist/mcp/backend/response.js.map +1 -1
- package/dist/mcp/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +75 -5
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +51 -6
- package/dist/mcp/runtime.js.map +1 -1
- package/dist/mcp/transports/inMemory.d.ts.map +1 -1
- package/dist/mcp/transports/inMemory.js +1 -0
- package/dist/mcp/transports/inMemory.js.map +1 -1
- package/dist/mcp/types.d.ts +2 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/protocol/bidi/backend.d.ts +1 -1
- package/dist/protocol/bidi/backend.d.ts.map +1 -1
- package/dist/protocol/bidi/backend.js +6 -2
- package/dist/protocol/bidi/backend.js.map +1 -1
- package/dist/protocol/bidi/client.d.ts +1 -0
- package/dist/protocol/bidi/client.d.ts.map +1 -1
- package/dist/protocol/bidi/client.js +48 -1
- package/dist/protocol/bidi/client.js.map +1 -1
- package/dist/roxybrowser.bundle.js +583 -425
- package/dist/roxybrowser.bundle.js.map +1 -1
- package/package.json +10 -4
|
@@ -14532,8 +14532,10 @@ var WebSocketBidiClient = class {
|
|
|
14532
14532
|
eventListeners = /* @__PURE__ */ new Map();
|
|
14533
14533
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
14534
14534
|
socket;
|
|
14535
|
+
webSocketUrl;
|
|
14535
14536
|
constructor(options) {
|
|
14536
14537
|
this.capabilities = { browserName: options.browserName };
|
|
14538
|
+
this.webSocketUrl = options.webSocketUrl;
|
|
14537
14539
|
this.socket = new globalThis.WebSocket(options.webSocketUrl);
|
|
14538
14540
|
this.socket.addEventListener("message", (event) => this.handleMessage(event.data));
|
|
14539
14541
|
this.socket.addEventListener("close", () => this.handleClose());
|
|
@@ -14543,7 +14545,7 @@ var WebSocketBidiClient = class {
|
|
|
14543
14545
|
return new Promise((resolve, reject) => {
|
|
14544
14546
|
this.socket.addEventListener("open", resolve, { once: true });
|
|
14545
14547
|
this.socket.addEventListener("error", (event) => {
|
|
14546
|
-
reject(
|
|
14548
|
+
reject(new Error(formatBidiConnectError(event, this.webSocketUrl)));
|
|
14547
14549
|
}, { once: true });
|
|
14548
14550
|
});
|
|
14549
14551
|
}
|
|
@@ -14714,6 +14716,28 @@ var WebSocketBidiClient = class {
|
|
|
14714
14716
|
this.pendingCommands.clear();
|
|
14715
14717
|
}
|
|
14716
14718
|
};
|
|
14719
|
+
function formatBidiConnectError(event, webSocketUrl) {
|
|
14720
|
+
const details = extractBidiConnectErrorDetails(event);
|
|
14721
|
+
return details ? `Failed to establish a WebDriver BiDi connection to ${webSocketUrl}: ${details}` : `Failed to establish a WebDriver BiDi connection to ${webSocketUrl}.`;
|
|
14722
|
+
}
|
|
14723
|
+
function extractBidiConnectErrorDetails(event) {
|
|
14724
|
+
if (event instanceof Error) return event.message;
|
|
14725
|
+
if (!event || typeof event !== "object") return typeof event === "string" ? event : void 0;
|
|
14726
|
+
const candidate = event;
|
|
14727
|
+
const parts = [];
|
|
14728
|
+
if (typeof candidate.message === "string" && candidate.message) parts.push(candidate.message);
|
|
14729
|
+
if (typeof candidate.error === "string" && candidate.error) parts.push(candidate.error);
|
|
14730
|
+
else if (candidate.error instanceof Error && candidate.error.message) parts.push(candidate.error.message);
|
|
14731
|
+
const socketLike = candidate.target ?? candidate.currentTarget;
|
|
14732
|
+
if (socketLike && typeof socketLike === "object") {
|
|
14733
|
+
const socketParts = [];
|
|
14734
|
+
if (typeof socketLike.url === "string" && socketLike.url) socketParts.push(`url=${socketLike.url}`);
|
|
14735
|
+
if (typeof socketLike.readyState === "number") socketParts.push(`readyState=${socketLike.readyState}`);
|
|
14736
|
+
if (socketParts.length > 0) parts.push(`socket(${socketParts.join(", ")})`);
|
|
14737
|
+
}
|
|
14738
|
+
if (parts.length > 0) return parts.join("; ");
|
|
14739
|
+
if (typeof candidate.type === "string" && candidate.type) return `event type=${candidate.type}`;
|
|
14740
|
+
}
|
|
14717
14741
|
var bidiClientFactory = createBidiClient;
|
|
14718
14742
|
function getBidiClientFactory() {
|
|
14719
14743
|
return bidiClientFactory;
|
|
@@ -15981,7 +16005,7 @@ var BidiPageAdapter = class BidiPageAdapter {
|
|
|
15981
16005
|
resultOwnership: "none"
|
|
15982
16006
|
}));
|
|
15983
16007
|
if (response.type === "exception") throw new Error(response.exceptionDetails.text || "BiDi evaluation failed.");
|
|
15984
|
-
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
16008
|
+
return parseSerializedEvaluationResult(extractBiDiValue$1(response.result));
|
|
15985
16009
|
}
|
|
15986
16010
|
async evaluateFunction(expression, arg) {
|
|
15987
16011
|
const serializedArg = arg === void 0 ? "" : serializeForEvaluation$1(arg);
|
|
@@ -17225,11 +17249,11 @@ var BidiElementHandleAdapter = class BidiElementHandleAdapter {
|
|
|
17225
17249
|
return this.page.selectOptionReference(this.reference(), values, options);
|
|
17226
17250
|
}
|
|
17227
17251
|
};
|
|
17228
|
-
function extractBiDiValue(value) {
|
|
17229
|
-
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
17252
|
+
function extractBiDiValue$1(value) {
|
|
17253
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue$1(entry));
|
|
17230
17254
|
if (value.type === "object" && Array.isArray(value.value)) {
|
|
17231
17255
|
const obj = {};
|
|
17232
|
-
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
17256
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue$1(val);
|
|
17233
17257
|
return obj;
|
|
17234
17258
|
}
|
|
17235
17259
|
return value.value;
|
|
@@ -17590,13 +17614,13 @@ function buildFirefoxBidiEndpoint(wsEndpoint, sessionId) {
|
|
|
17590
17614
|
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
17591
17615
|
return url.toString();
|
|
17592
17616
|
}
|
|
17593
|
-
function isSessionSpecificFirefoxBidiEndpoint(wsEndpoint) {
|
|
17617
|
+
function isSessionSpecificFirefoxBidiEndpoint$1(wsEndpoint) {
|
|
17594
17618
|
const pathname = new URL(wsEndpoint).pathname;
|
|
17595
17619
|
return /^\/session\/[^/]+$/.test(pathname);
|
|
17596
17620
|
}
|
|
17597
17621
|
async function ensureBiDiSession(client, sessionId, wsEndpoint) {
|
|
17598
17622
|
await client.sessionStatus({});
|
|
17599
|
-
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(wsEndpoint)) return false;
|
|
17623
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint$1(wsEndpoint)) return false;
|
|
17600
17624
|
try {
|
|
17601
17625
|
await client.browsingContextGetTree({});
|
|
17602
17626
|
return false;
|
|
@@ -17621,9 +17645,9 @@ function buildFirefoxLaunchArgs(options, userDataDir, port) {
|
|
|
17621
17645
|
...options.args ?? []
|
|
17622
17646
|
];
|
|
17623
17647
|
}
|
|
17624
|
-
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), fileExistsFn = fileExists$1) {
|
|
17648
|
+
function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), playwrightFirefoxExecutablePath, fileExistsFn = fileExists$1) {
|
|
17625
17649
|
if (options.executablePath) return [options.executablePath];
|
|
17626
|
-
return filterExistingFirefoxExecutableCandidates(defaultFirefoxExecutableCandidates(platform), platform, fileExistsFn);
|
|
17650
|
+
return filterExistingFirefoxExecutableCandidates([...playwrightFirefoxExecutablePath ? [playwrightFirefoxExecutablePath] : [], ...defaultFirefoxExecutableCandidates(platform)], platform, fileExistsFn);
|
|
17627
17651
|
}
|
|
17628
17652
|
function defaultFirefoxExecutableCandidates(platform) {
|
|
17629
17653
|
const candidates = [];
|
|
@@ -73280,11 +73304,6 @@ function formatConnectResult(input) {
|
|
|
73280
73304
|
if (input.snapshot) parts.push(formatSnapshot(input.snapshot));
|
|
73281
73305
|
return parts.join("\n\n");
|
|
73282
73306
|
}
|
|
73283
|
-
function formatTabsWithOptionalSnapshot(tabs, snapshot) {
|
|
73284
|
-
const parts = [formatTabs(tabs)];
|
|
73285
|
-
if (snapshot) parts.push(formatSnapshot(snapshot));
|
|
73286
|
-
return parts.join("\n\n");
|
|
73287
|
-
}
|
|
73288
73307
|
//#endregion
|
|
73289
73308
|
//#region src/mcp/backend/response.ts
|
|
73290
73309
|
var Response$1 = class {
|
|
@@ -73294,6 +73313,7 @@ var Response$1 = class {
|
|
|
73294
73313
|
results = [];
|
|
73295
73314
|
errors = [];
|
|
73296
73315
|
code = [];
|
|
73316
|
+
images = [];
|
|
73297
73317
|
includeSnapshot = "none";
|
|
73298
73318
|
fullSnapshot;
|
|
73299
73319
|
isClose = false;
|
|
@@ -73311,6 +73331,12 @@ var Response$1 = class {
|
|
|
73311
73331
|
addCode(code) {
|
|
73312
73332
|
this.code.push(code);
|
|
73313
73333
|
}
|
|
73334
|
+
addImageResult(data, mimeType) {
|
|
73335
|
+
this.images.push({
|
|
73336
|
+
data,
|
|
73337
|
+
mimeType
|
|
73338
|
+
});
|
|
73339
|
+
}
|
|
73314
73340
|
setClose() {
|
|
73315
73341
|
this.isClose = true;
|
|
73316
73342
|
}
|
|
@@ -73338,16 +73364,21 @@ var Response$1 = class {
|
|
|
73338
73364
|
sections.push("### Code", "```js", ...this.code, "```");
|
|
73339
73365
|
}
|
|
73340
73366
|
if (this.includeSnapshot === "full") {
|
|
73341
|
-
const snapshot = await this.context.runtime.snapshot();
|
|
73367
|
+
const snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot());
|
|
73342
73368
|
if (sections.length) sections.push("");
|
|
73343
73369
|
sections.push(formatSnapshot(snapshot));
|
|
73344
73370
|
}
|
|
73345
73371
|
if (this.fullSnapshot) {
|
|
73346
|
-
|
|
73372
|
+
let snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73347
73373
|
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73348
73374
|
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73349
73375
|
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73350
|
-
});
|
|
73376
|
+
}));
|
|
73377
|
+
if (!this.fullSnapshot.filename && snapshot.text.trim().length === 0 && snapshot.url && snapshot.url !== "about:blank") snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
|
|
73378
|
+
...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
|
|
73379
|
+
...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
|
|
73380
|
+
...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
|
|
73381
|
+
}));
|
|
73351
73382
|
if (this.fullSnapshot.filename) {
|
|
73352
73383
|
const resolvedFilename = await this.context.resolveOutputFile(this.fullSnapshot.filename);
|
|
73353
73384
|
await writeFile(resolvedFilename, snapshot.text);
|
|
@@ -73364,17 +73395,30 @@ var Response$1 = class {
|
|
|
73364
73395
|
content: [{
|
|
73365
73396
|
type: "text",
|
|
73366
73397
|
text: sections.join("\n")
|
|
73367
|
-
}
|
|
73398
|
+
}, ...this.images.map((image) => ({
|
|
73399
|
+
type: "image",
|
|
73400
|
+
data: image.data,
|
|
73401
|
+
mimeType: image.mimeType
|
|
73402
|
+
}))],
|
|
73368
73403
|
...this.isClose ? { isClose: true } : {},
|
|
73369
73404
|
...this.errors.length ? { isError: true } : {}
|
|
73370
73405
|
};
|
|
73371
73406
|
}
|
|
73372
73407
|
};
|
|
73373
|
-
|
|
73374
|
-
|
|
73375
|
-
|
|
73408
|
+
async function reconcileSnapshotWithTabs(context, snapshot) {
|
|
73409
|
+
const activeTab = (await context.runtime.listTabs()).find((tab) => tab.active);
|
|
73410
|
+
if (!activeTab) return snapshot;
|
|
73411
|
+
return {
|
|
73412
|
+
...snapshot,
|
|
73413
|
+
title: activeTab.title || snapshot.title,
|
|
73414
|
+
url: activeTab.url || snapshot.url
|
|
73415
|
+
};
|
|
73416
|
+
}
|
|
73376
73417
|
//#endregion
|
|
73377
73418
|
//#region src/mcp/backend/tool.ts
|
|
73419
|
+
function defineTool$1(tool) {
|
|
73420
|
+
return tool;
|
|
73421
|
+
}
|
|
73378
73422
|
function defineTabTool(tool) {
|
|
73379
73423
|
return {
|
|
73380
73424
|
...tool,
|
|
@@ -73391,6 +73435,146 @@ function missingModalStateMessage(tool) {
|
|
|
73391
73435
|
if (tool.clearsModalState === "fileChooser") return "[no_file_chooser] No file chooser visible.";
|
|
73392
73436
|
return `Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.`;
|
|
73393
73437
|
}
|
|
73438
|
+
var common_default = [defineTool$1({
|
|
73439
|
+
capability: "core-tabs",
|
|
73440
|
+
schema: {
|
|
73441
|
+
name: "browser_close",
|
|
73442
|
+
title: "Close browser",
|
|
73443
|
+
description: "Close the current browser session.",
|
|
73444
|
+
inputSchema: object({}),
|
|
73445
|
+
type: "action"
|
|
73446
|
+
},
|
|
73447
|
+
handle: async (context, _params, response) => {
|
|
73448
|
+
await context.runtime.close();
|
|
73449
|
+
response.setClose();
|
|
73450
|
+
response.addTextResult("Browser session closed.");
|
|
73451
|
+
}
|
|
73452
|
+
}), defineTool$1({
|
|
73453
|
+
capability: "core",
|
|
73454
|
+
schema: {
|
|
73455
|
+
name: "browser_resize",
|
|
73456
|
+
title: "Resize browser",
|
|
73457
|
+
description: "Resize the active page viewport.",
|
|
73458
|
+
inputSchema: object({
|
|
73459
|
+
width: number().int().positive(),
|
|
73460
|
+
height: number().int().positive()
|
|
73461
|
+
}),
|
|
73462
|
+
type: "action"
|
|
73463
|
+
},
|
|
73464
|
+
handle: async (context, params, response) => {
|
|
73465
|
+
const snapshot = await context.runtime.resize(params.width, params.height);
|
|
73466
|
+
response.setIncludeSnapshot();
|
|
73467
|
+
response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
|
|
73468
|
+
if (!snapshot) response.addTextResult(`Resized viewport to ${params.width}x${params.height}.`);
|
|
73469
|
+
}
|
|
73470
|
+
})];
|
|
73471
|
+
var console_default = [defineTool$1({
|
|
73472
|
+
capability: "core",
|
|
73473
|
+
schema: {
|
|
73474
|
+
name: "browser_console_messages",
|
|
73475
|
+
title: "Get console messages",
|
|
73476
|
+
description: "Returns all console messages",
|
|
73477
|
+
inputSchema: object({
|
|
73478
|
+
level: _enum([
|
|
73479
|
+
"error",
|
|
73480
|
+
"warning",
|
|
73481
|
+
"info",
|
|
73482
|
+
"debug"
|
|
73483
|
+
]).default("info").describe("Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\"."),
|
|
73484
|
+
all: boolean().optional().describe("Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false."),
|
|
73485
|
+
filename: string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
|
|
73486
|
+
}),
|
|
73487
|
+
type: "readOnly"
|
|
73488
|
+
},
|
|
73489
|
+
handle: async (context, params, response) => {
|
|
73490
|
+
const messages = await context.runtime.consoleMessages(params.level, params.all);
|
|
73491
|
+
const errors = messages.filter((message) => message.type === "error" || message.type === "assert").length;
|
|
73492
|
+
const warnings = messages.filter((message) => message.type === "warning").length;
|
|
73493
|
+
const text = [
|
|
73494
|
+
`Total messages: ${messages.length} (Errors: ${errors}, Warnings: ${warnings})`,
|
|
73495
|
+
"",
|
|
73496
|
+
...messages.map((message) => message.formattedText)
|
|
73497
|
+
].join("\n");
|
|
73498
|
+
if (params.filename) {
|
|
73499
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73500
|
+
await writeFile(resolvedFilename, text);
|
|
73501
|
+
response.addTextResult(`Saved console messages to "${resolvedFilename}".`);
|
|
73502
|
+
return;
|
|
73503
|
+
}
|
|
73504
|
+
response.addTextResult(text);
|
|
73505
|
+
}
|
|
73506
|
+
})];
|
|
73507
|
+
var connect_default = [defineTool$1({
|
|
73508
|
+
capability: "config",
|
|
73509
|
+
schema: {
|
|
73510
|
+
name: "roxy_browser_connect",
|
|
73511
|
+
title: "Roxy Browser Connect",
|
|
73512
|
+
description: "Attach to an existing browser and seed the active tab snapshot.",
|
|
73513
|
+
inputSchema: object({
|
|
73514
|
+
endpoint: string().min(1),
|
|
73515
|
+
browser: _enum(["chrome", "firefox"]).default("chrome"),
|
|
73516
|
+
sessionId: string().min(1).optional()
|
|
73517
|
+
}),
|
|
73518
|
+
type: "action"
|
|
73519
|
+
},
|
|
73520
|
+
handle: async (context, params, response) => {
|
|
73521
|
+
const protocol = params.browser === "firefox" ? "bidi" : "cdp";
|
|
73522
|
+
const result = await context.runtime.connect({
|
|
73523
|
+
protocol,
|
|
73524
|
+
endpoint: params.endpoint,
|
|
73525
|
+
browser: params.browser === "chrome" ? "chromium" : params.browser,
|
|
73526
|
+
...params.sessionId ? { sessionId: params.sessionId } : {}
|
|
73527
|
+
});
|
|
73528
|
+
response.addTextResult(formatConnectResult({
|
|
73529
|
+
...result,
|
|
73530
|
+
browserName: result.browserName === "chromium" ? "chrome" : result.browserName
|
|
73531
|
+
}));
|
|
73532
|
+
}
|
|
73533
|
+
})];
|
|
73534
|
+
var dialogs_default = [defineTool$1({
|
|
73535
|
+
capability: "core",
|
|
73536
|
+
schema: {
|
|
73537
|
+
name: "browser_handle_dialog",
|
|
73538
|
+
title: "Handle a dialog",
|
|
73539
|
+
description: "Handle a dialog",
|
|
73540
|
+
inputSchema: object({
|
|
73541
|
+
accept: boolean().describe("Whether to accept the dialog."),
|
|
73542
|
+
promptText: string().optional().describe("The text of the prompt in case of a prompt dialog.")
|
|
73543
|
+
}),
|
|
73544
|
+
type: "action"
|
|
73545
|
+
},
|
|
73546
|
+
handle: async (context, params, response) => {
|
|
73547
|
+
const snapshot = await context.runtime.handleDialog(params.accept, params.promptText);
|
|
73548
|
+
response.setIncludeSnapshot();
|
|
73549
|
+
if (!snapshot) response.addTextResult(params.accept ? "Accepted dialog." : "Dismissed dialog.");
|
|
73550
|
+
}
|
|
73551
|
+
})];
|
|
73552
|
+
var evaluate_default = [defineTool$1({
|
|
73553
|
+
capability: "core",
|
|
73554
|
+
schema: {
|
|
73555
|
+
name: "browser_evaluate",
|
|
73556
|
+
title: "Evaluate JavaScript",
|
|
73557
|
+
description: "Evaluate JavaScript expression on page or element",
|
|
73558
|
+
inputSchema: object({
|
|
73559
|
+
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73560
|
+
target: string().optional().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73561
|
+
function: string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
73562
|
+
filename: string().optional().describe("Filename to save the result to. If not provided, result is returned as text.")
|
|
73563
|
+
}),
|
|
73564
|
+
type: "action"
|
|
73565
|
+
},
|
|
73566
|
+
handle: async (context, params, response) => {
|
|
73567
|
+
const result = await context.runtime.evaluate(params.function, params.target);
|
|
73568
|
+
const text = JSON.stringify(result, null, 2) ?? "undefined";
|
|
73569
|
+
if (params.filename) {
|
|
73570
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73571
|
+
await writeFile(resolvedFilename, text);
|
|
73572
|
+
response.addTextResult(`Saved evaluation result to "${resolvedFilename}".`);
|
|
73573
|
+
return;
|
|
73574
|
+
}
|
|
73575
|
+
response.addTextResult(text);
|
|
73576
|
+
}
|
|
73577
|
+
})];
|
|
73394
73578
|
var files_default = [defineTabTool({
|
|
73395
73579
|
capability: "core",
|
|
73396
73580
|
schema: {
|
|
@@ -73602,7 +73786,7 @@ var typeSchema = elementSchema$2.extend({
|
|
|
73602
73786
|
submit: boolean().optional().describe("Whether to submit entered text (press Enter after)"),
|
|
73603
73787
|
slowly: boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
|
|
73604
73788
|
});
|
|
73605
|
-
var keyboard_default
|
|
73789
|
+
var keyboard_default = [defineTabTool({
|
|
73606
73790
|
capability: "core-input",
|
|
73607
73791
|
schema: {
|
|
73608
73792
|
name: "browser_press_key",
|
|
@@ -73648,13 +73832,301 @@ var keyboard_default$1 = [defineTabTool({
|
|
|
73648
73832
|
});
|
|
73649
73833
|
}
|
|
73650
73834
|
})];
|
|
73835
|
+
var navigate_default = [
|
|
73836
|
+
defineTool$1({
|
|
73837
|
+
capability: "core-navigation",
|
|
73838
|
+
schema: {
|
|
73839
|
+
name: "browser_navigate",
|
|
73840
|
+
title: "Navigate to a URL",
|
|
73841
|
+
description: "Navigate to a URL",
|
|
73842
|
+
inputSchema: object({ url: string().describe("The URL to navigate to") }),
|
|
73843
|
+
type: "action"
|
|
73844
|
+
},
|
|
73845
|
+
handle: async (context, params, response) => {
|
|
73846
|
+
await context.ensureTab();
|
|
73847
|
+
await context.runtime.navigate(params.url);
|
|
73848
|
+
response.setIncludeSnapshot();
|
|
73849
|
+
response.addCode(`await page.goto('${params.url.startsWith("http") ? params.url : params.url.startsWith("localhost") ? `http://${params.url}` : `https://${params.url}`}');`);
|
|
73850
|
+
}
|
|
73851
|
+
}),
|
|
73852
|
+
defineTool$1({
|
|
73853
|
+
capability: "core-navigation",
|
|
73854
|
+
schema: {
|
|
73855
|
+
name: "browser_navigate_back",
|
|
73856
|
+
title: "Go back",
|
|
73857
|
+
description: "Go back to the previous page in the history",
|
|
73858
|
+
inputSchema: object({}),
|
|
73859
|
+
type: "action"
|
|
73860
|
+
},
|
|
73861
|
+
handle: async (context, _params, response) => {
|
|
73862
|
+
await context.runtime.goBack();
|
|
73863
|
+
response.setIncludeSnapshot();
|
|
73864
|
+
response.addCode("await page.goBack();");
|
|
73865
|
+
}
|
|
73866
|
+
}),
|
|
73867
|
+
defineTool$1({
|
|
73868
|
+
capability: "core-navigation",
|
|
73869
|
+
schema: {
|
|
73870
|
+
name: "browser_navigate_forward",
|
|
73871
|
+
title: "Go forward",
|
|
73872
|
+
description: "Go forward to the next page in the history",
|
|
73873
|
+
inputSchema: object({}),
|
|
73874
|
+
type: "action"
|
|
73875
|
+
},
|
|
73876
|
+
handle: async (context, _params, response) => {
|
|
73877
|
+
await context.runtime.goForward();
|
|
73878
|
+
response.setIncludeSnapshot();
|
|
73879
|
+
response.addCode("await page.goForward();");
|
|
73880
|
+
}
|
|
73881
|
+
}),
|
|
73882
|
+
defineTool$1({
|
|
73883
|
+
capability: "core-navigation",
|
|
73884
|
+
schema: {
|
|
73885
|
+
name: "browser_wait_for",
|
|
73886
|
+
title: "Wait for",
|
|
73887
|
+
description: "Wait for text to appear or disappear or a specified time to pass",
|
|
73888
|
+
inputSchema: object({
|
|
73889
|
+
time: number().optional().describe("The time to wait in seconds"),
|
|
73890
|
+
text: string().optional().describe("The text to wait for"),
|
|
73891
|
+
textGone: string().optional().describe("The text to wait for to disappear")
|
|
73892
|
+
}),
|
|
73893
|
+
type: "action"
|
|
73894
|
+
},
|
|
73895
|
+
handle: async (context, params, response) => {
|
|
73896
|
+
if (!params.text && !params.textGone && !params.time) throw new Error("Either time, text or textGone must be provided");
|
|
73897
|
+
const waitSeconds = params.time;
|
|
73898
|
+
if (waitSeconds !== void 0) await new Promise((resolve) => setTimeout(resolve, Math.min(3e4, waitSeconds * 1e3)));
|
|
73899
|
+
if (params.text || params.textGone) await context.runtime.waitFor({
|
|
73900
|
+
...params.text !== void 0 ? { text: params.text } : {},
|
|
73901
|
+
...params.textGone !== void 0 ? { textGone: params.textGone } : {}
|
|
73902
|
+
}, 5e3);
|
|
73903
|
+
response.setIncludeSnapshot();
|
|
73904
|
+
}
|
|
73905
|
+
})
|
|
73906
|
+
];
|
|
73907
|
+
//#endregion
|
|
73908
|
+
//#region src/mcp/backend/network.ts
|
|
73909
|
+
var requestParts = [
|
|
73910
|
+
"request-headers",
|
|
73911
|
+
"request-body",
|
|
73912
|
+
"response-headers",
|
|
73913
|
+
"response-body"
|
|
73914
|
+
];
|
|
73915
|
+
var networkRequests = defineTool$1({
|
|
73916
|
+
capability: "core",
|
|
73917
|
+
schema: {
|
|
73918
|
+
name: "browser_network_requests",
|
|
73919
|
+
title: "List network requests",
|
|
73920
|
+
description: "Returns a numbered list of network requests since loading the page. Use browser_network_request with the number to get full details.",
|
|
73921
|
+
inputSchema: object({
|
|
73922
|
+
static: boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false."),
|
|
73923
|
+
filter: string().optional().describe("Only return requests whose URL matches this regexp (e.g. \"/api/.*user\")."),
|
|
73924
|
+
filename: string().optional().describe("Filename to save the network requests to. If not provided, requests are returned as text.")
|
|
73925
|
+
}),
|
|
73926
|
+
type: "readOnly"
|
|
73927
|
+
},
|
|
73928
|
+
handle: async (context, args, response) => {
|
|
73929
|
+
const requests = await context.runtime.networkRequests();
|
|
73930
|
+
const filter = args.filter ? new RegExp(args.filter) : void 0;
|
|
73931
|
+
const lines = [];
|
|
73932
|
+
let hiddenStaticCount = 0;
|
|
73933
|
+
for (const request of requests) {
|
|
73934
|
+
if (!args.static && !isFetch(request) && isSuccessfulResponse(request)) {
|
|
73935
|
+
hiddenStaticCount++;
|
|
73936
|
+
continue;
|
|
73937
|
+
}
|
|
73938
|
+
if (filter && !filter.test(request.url)) continue;
|
|
73939
|
+
lines.push(`${request.index}. ${renderRequestLine(request)}`);
|
|
73940
|
+
}
|
|
73941
|
+
if (hiddenStaticCount > 0) lines.push(`\nNote: ${hiddenStaticCount} static request${hiddenStaticCount === 1 ? "" : "s"} not shown, run with "static" option to see ${hiddenStaticCount === 1 ? "it" : "them"}.`);
|
|
73942
|
+
const text = lines.join("\n");
|
|
73943
|
+
if (args.filename) {
|
|
73944
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73945
|
+
await writeFile(resolvedFilename, text);
|
|
73946
|
+
response.addTextResult(`Saved network requests to "${resolvedFilename}".`);
|
|
73947
|
+
return;
|
|
73948
|
+
}
|
|
73949
|
+
response.addTextResult(text);
|
|
73950
|
+
}
|
|
73951
|
+
});
|
|
73952
|
+
var networkRequest = defineTool$1({
|
|
73953
|
+
capability: "core",
|
|
73954
|
+
schema: {
|
|
73955
|
+
name: "browser_network_request",
|
|
73956
|
+
title: "Show network request details",
|
|
73957
|
+
description: "Returns full details (headers and body) of a single network request, or a single part if part is set. Use the number from browser_network_requests.",
|
|
73958
|
+
inputSchema: object({
|
|
73959
|
+
index: number().int().min(1).describe("1-based index of the request, as printed by browser_network_requests."),
|
|
73960
|
+
part: _enum(requestParts).optional().describe("Return only this part of the request. Omit to return full details."),
|
|
73961
|
+
filename: string().optional().describe("Filename to save the result to. If not provided, output is returned as text.")
|
|
73962
|
+
}),
|
|
73963
|
+
type: "readOnly"
|
|
73964
|
+
},
|
|
73965
|
+
handle: async (context, args, response) => {
|
|
73966
|
+
const request = await context.runtime.networkRequest(args.index);
|
|
73967
|
+
if (!request) {
|
|
73968
|
+
response.addError(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`);
|
|
73969
|
+
return;
|
|
73970
|
+
}
|
|
73971
|
+
const text = args.part ? renderRequestPart(request, args.part) : renderRequestDetails(request);
|
|
73972
|
+
if (args.filename) {
|
|
73973
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73974
|
+
await writeFile(resolvedFilename, text);
|
|
73975
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
73976
|
+
return;
|
|
73977
|
+
}
|
|
73978
|
+
response.addTextResult(text);
|
|
73979
|
+
}
|
|
73980
|
+
});
|
|
73981
|
+
function isSuccessfulResponse(request) {
|
|
73982
|
+
return !request.failureText && request.status !== void 0 && request.status < 400;
|
|
73983
|
+
}
|
|
73984
|
+
function isFetch(request) {
|
|
73985
|
+
return request.resourceType === "fetch" || request.resourceType === "xhr";
|
|
73986
|
+
}
|
|
73987
|
+
function renderRequestLine(request) {
|
|
73988
|
+
let line = `[${request.method.toUpperCase()}] ${request.url}`;
|
|
73989
|
+
if (request.status !== void 0) line += ` => [${request.status}] ${request.statusText ?? ""}`.trimEnd();
|
|
73990
|
+
else if (request.failureText) line += ` => [FAILED] ${request.failureText}`;
|
|
73991
|
+
return line;
|
|
73992
|
+
}
|
|
73993
|
+
function renderRequestDetails(request) {
|
|
73994
|
+
const lines = [];
|
|
73995
|
+
lines.push(`#${request.index} [${request.method.toUpperCase()}] ${request.url}`);
|
|
73996
|
+
lines.push("");
|
|
73997
|
+
lines.push(" General");
|
|
73998
|
+
if (request.status !== void 0) lines.push(` status: [${request.status}] ${request.statusText ?? ""}`.trimEnd());
|
|
73999
|
+
else if (request.failureText) lines.push(` status: [FAILED] ${request.failureText}`);
|
|
74000
|
+
if (request.durationMs !== void 0) lines.push(` duration: ${request.durationMs}ms`);
|
|
74001
|
+
lines.push(` type: ${request.resourceType}`);
|
|
74002
|
+
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
74003
|
+
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
74004
|
+
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
74005
|
+
if (request.requestBody) lines.push("", `Call browser_network_request with part="request-body" to read the request body.`);
|
|
74006
|
+
if (request.responseBody) lines.push("", `Call browser_network_request with part="response-body" to read the response body.`);
|
|
74007
|
+
return lines.join("\n");
|
|
74008
|
+
}
|
|
74009
|
+
function renderRequestPart(request, part) {
|
|
74010
|
+
if (part === "request-headers") return renderHeaders(request.requestHeaders);
|
|
74011
|
+
if (part === "request-body") return request.requestBody ?? "";
|
|
74012
|
+
if (part === "response-headers") return renderHeaders(request.responseHeaders ?? {});
|
|
74013
|
+
return request.responseBody ?? "";
|
|
74014
|
+
}
|
|
74015
|
+
function appendHeaders(lines, title, headers) {
|
|
74016
|
+
const entries = Object.entries(headers);
|
|
74017
|
+
if (!entries.length) return;
|
|
74018
|
+
lines.push("");
|
|
74019
|
+
lines.push(` ${title}`);
|
|
74020
|
+
for (const [key, value] of entries) lines.push(` ${key}: ${value}`);
|
|
74021
|
+
}
|
|
74022
|
+
function renderHeaders(headers) {
|
|
74023
|
+
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74024
|
+
}
|
|
74025
|
+
var network_default = [networkRequests, networkRequest];
|
|
74026
|
+
var runCode_default = [defineTool$1({
|
|
74027
|
+
capability: "devtools",
|
|
74028
|
+
schema: {
|
|
74029
|
+
name: "browser_run_code_unsafe",
|
|
74030
|
+
title: "Run code (unsafe)",
|
|
74031
|
+
description: "Run arbitrary code against the current browser session.",
|
|
74032
|
+
inputSchema: object({ code: string().describe("JavaScript code to run against the browser session.") }),
|
|
74033
|
+
type: "action"
|
|
74034
|
+
},
|
|
74035
|
+
handle: async (context, args, response) => {
|
|
74036
|
+
const result = await context.runtime.runCodeUnsafe(args.code);
|
|
74037
|
+
response.addTextResult(JSON.stringify(result, null, 2) ?? "undefined");
|
|
74038
|
+
}
|
|
74039
|
+
})];
|
|
74040
|
+
var screenshot_default = [defineTool$1({
|
|
74041
|
+
capability: "core",
|
|
74042
|
+
schema: {
|
|
74043
|
+
name: "browser_take_screenshot",
|
|
74044
|
+
title: "Browser Take Screenshot",
|
|
74045
|
+
description: "Capture a full-page screenshot of the active tab as a base64-encoded PNG.",
|
|
74046
|
+
inputSchema: object({
|
|
74047
|
+
element: string().optional().describe("Human-readable description of the area to screenshot"),
|
|
74048
|
+
target: string().optional().describe("Element reference or CSS selector to clip screenshot to; omit for full page"),
|
|
74049
|
+
type: _enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
74050
|
+
filename: string().optional().describe("File name to save the screenshot to."),
|
|
74051
|
+
fullPage: boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
|
|
74052
|
+
}),
|
|
74053
|
+
type: "readOnly"
|
|
74054
|
+
},
|
|
74055
|
+
handle: async (context, args, response) => {
|
|
74056
|
+
const result = await context.runtime.takeScreenshot({
|
|
74057
|
+
type: args.type,
|
|
74058
|
+
...args.fullPage !== void 0 ? { fullPage: args.fullPage } : {},
|
|
74059
|
+
...args.target !== void 0 ? { target: args.target } : {}
|
|
74060
|
+
});
|
|
74061
|
+
const requestedFilename = args.filename?.trim();
|
|
74062
|
+
const resolvedFilename = await context.resolveOutputFile(requestedFilename || `page-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}.${args.type}`);
|
|
74063
|
+
await writeFile(resolvedFilename, Buffer.from(result.data, "base64"));
|
|
74064
|
+
if (requestedFilename) {
|
|
74065
|
+
response.addTextResult(`Screenshot saved to "${resolvedFilename}".`);
|
|
74066
|
+
return;
|
|
74067
|
+
}
|
|
74068
|
+
response.addTextResult(resolvedFilename);
|
|
74069
|
+
response.addImageResult(result.data, result.mimeType);
|
|
74070
|
+
}
|
|
74071
|
+
})];
|
|
74072
|
+
var tabs_default = [defineTool$1({
|
|
74073
|
+
capability: "core-tabs",
|
|
74074
|
+
schema: {
|
|
74075
|
+
name: "browser_tabs",
|
|
74076
|
+
title: "Browser Tabs",
|
|
74077
|
+
description: "List, create, select, and close browser tabs for the current MCP browser session.",
|
|
74078
|
+
inputSchema: object({
|
|
74079
|
+
action: _enum([
|
|
74080
|
+
"list",
|
|
74081
|
+
"new",
|
|
74082
|
+
"select",
|
|
74083
|
+
"close"
|
|
74084
|
+
]).describe("Operation to perform"),
|
|
74085
|
+
index: number().optional().describe("Tab index, used for close/select. If omitted for close, current tab is closed."),
|
|
74086
|
+
url: string().optional().describe("URL to navigate to in the new tab, used for new.")
|
|
74087
|
+
}),
|
|
74088
|
+
type: "action"
|
|
74089
|
+
},
|
|
74090
|
+
handle: async (context, params, response) => {
|
|
74091
|
+
switch (params.action) {
|
|
74092
|
+
case "list":
|
|
74093
|
+
await context.ensureTab();
|
|
74094
|
+
break;
|
|
74095
|
+
case "new":
|
|
74096
|
+
await context.runtime.newTab(params.url);
|
|
74097
|
+
if (params.url) {
|
|
74098
|
+
response.setIncludeSnapshot();
|
|
74099
|
+
response.addCode(`await page.goto('${params.url}');`);
|
|
74100
|
+
}
|
|
74101
|
+
break;
|
|
74102
|
+
case "close":
|
|
74103
|
+
await context.runtime.closeTab(params.index ?? 0);
|
|
74104
|
+
break;
|
|
74105
|
+
case "select":
|
|
74106
|
+
if (params.index === void 0) throw new Error("Tab index is required");
|
|
74107
|
+
await context.runtime.selectTab(params.index);
|
|
74108
|
+
break;
|
|
74109
|
+
}
|
|
74110
|
+
const tabs = await context.runtime.listTabs();
|
|
74111
|
+
response.addTextResult(formatTabs(tabs));
|
|
74112
|
+
}
|
|
74113
|
+
})];
|
|
73651
74114
|
//#endregion
|
|
73652
74115
|
//#region src/mcp/backend/tools.ts
|
|
73653
74116
|
var browserTools = [
|
|
73654
|
-
...common_default
|
|
74117
|
+
...common_default,
|
|
74118
|
+
...console_default,
|
|
74119
|
+
...connect_default,
|
|
74120
|
+
...dialogs_default,
|
|
74121
|
+
...evaluate_default,
|
|
73655
74122
|
...files_default,
|
|
73656
|
-
...keyboard_default
|
|
73657
|
-
...
|
|
74123
|
+
...keyboard_default,
|
|
74124
|
+
...navigate_default,
|
|
74125
|
+
...network_default,
|
|
74126
|
+
...runCode_default,
|
|
74127
|
+
...screenshot_default,
|
|
74128
|
+
...snapshot_default,
|
|
74129
|
+
...tabs_default
|
|
73658
74130
|
];
|
|
73659
74131
|
//#endregion
|
|
73660
74132
|
//#region src/mcp/errors.ts
|
|
@@ -73683,135 +74155,6 @@ function textResult(text, isError = false) {
|
|
|
73683
74155
|
...isError ? { isError: true } : {}
|
|
73684
74156
|
};
|
|
73685
74157
|
}
|
|
73686
|
-
var connect_default = [defineTool({
|
|
73687
|
-
schema: {
|
|
73688
|
-
name: "roxy_browser_connect",
|
|
73689
|
-
title: "Roxy Browser Connect",
|
|
73690
|
-
description: "Attach to an existing browser over CDP or BiDi and seed the active tab snapshot.",
|
|
73691
|
-
inputSchema: object({
|
|
73692
|
-
protocol: _enum(["cdp", "bidi"]),
|
|
73693
|
-
endpoint: string().min(1),
|
|
73694
|
-
browser: _enum(["chromium", "firefox"]).optional()
|
|
73695
|
-
})
|
|
73696
|
-
},
|
|
73697
|
-
handle: async (args, runtime) => {
|
|
73698
|
-
return textResult(formatConnectResult(await runtime.connect({
|
|
73699
|
-
protocol: args.protocol,
|
|
73700
|
-
endpoint: args.endpoint,
|
|
73701
|
-
...args.browser ? { browser: args.browser } : {}
|
|
73702
|
-
})));
|
|
73703
|
-
}
|
|
73704
|
-
})];
|
|
73705
|
-
var common_default = [defineTool({
|
|
73706
|
-
schema: {
|
|
73707
|
-
name: "browser_close",
|
|
73708
|
-
title: "Close browser",
|
|
73709
|
-
description: "Close the page",
|
|
73710
|
-
inputSchema: object({})
|
|
73711
|
-
},
|
|
73712
|
-
handle: async (_args, runtime) => {
|
|
73713
|
-
await runtime.close();
|
|
73714
|
-
return textResult(formatTabs([]));
|
|
73715
|
-
}
|
|
73716
|
-
}), defineTool({
|
|
73717
|
-
schema: {
|
|
73718
|
-
name: "browser_resize",
|
|
73719
|
-
title: "Resize browser window",
|
|
73720
|
-
description: "Resize the browser window",
|
|
73721
|
-
inputSchema: object({
|
|
73722
|
-
width: number().describe("Width of the browser window"),
|
|
73723
|
-
height: number().describe("Height of the browser window")
|
|
73724
|
-
})
|
|
73725
|
-
},
|
|
73726
|
-
handle: async (args, runtime) => {
|
|
73727
|
-
const snap = await runtime.resize(args.width, args.height);
|
|
73728
|
-
if (!snap) return textResult(`Resized browser window to ${args.width}x${args.height}.`);
|
|
73729
|
-
return textResult(formatSnapshot(snap));
|
|
73730
|
-
}
|
|
73731
|
-
})];
|
|
73732
|
-
var tabs_default = [defineTool({
|
|
73733
|
-
schema: {
|
|
73734
|
-
name: "browser_tabs",
|
|
73735
|
-
title: "Browser Tabs",
|
|
73736
|
-
description: "List, create, select, and close browser tabs for the current MCP browser session.",
|
|
73737
|
-
inputSchema: object({
|
|
73738
|
-
action: _enum([
|
|
73739
|
-
"list",
|
|
73740
|
-
"new",
|
|
73741
|
-
"select",
|
|
73742
|
-
"close"
|
|
73743
|
-
]),
|
|
73744
|
-
index: number().int().nonnegative().optional(),
|
|
73745
|
-
url: string().url().optional()
|
|
73746
|
-
})
|
|
73747
|
-
},
|
|
73748
|
-
handle: async (args, runtime) => {
|
|
73749
|
-
if (args.action === "list") return textResult(formatTabs(await runtime.listTabs()));
|
|
73750
|
-
if (args.action === "new") {
|
|
73751
|
-
const result = await runtime.newTab(args.url);
|
|
73752
|
-
return textResult(formatTabsWithOptionalSnapshot(result.tabs, result.snapshot));
|
|
73753
|
-
}
|
|
73754
|
-
if (args.action === "select") {
|
|
73755
|
-
const result = await runtime.selectTab(args.index);
|
|
73756
|
-
return textResult(formatTabsWithOptionalSnapshot(result.tabs, result.snapshot));
|
|
73757
|
-
}
|
|
73758
|
-
const result = await runtime.closeTab(args.index);
|
|
73759
|
-
return textResult(formatTabsWithOptionalSnapshot(result.tabs, result.snapshot));
|
|
73760
|
-
}
|
|
73761
|
-
})];
|
|
73762
|
-
//#endregion
|
|
73763
|
-
//#region src/mcp/tools/navigate.ts
|
|
73764
|
-
var navigate = defineTool({
|
|
73765
|
-
schema: {
|
|
73766
|
-
name: "browser_navigate",
|
|
73767
|
-
title: "Navigate to a URL",
|
|
73768
|
-
description: "Navigate to a URL",
|
|
73769
|
-
inputSchema: object({ url: string().describe("The URL to navigate to") })
|
|
73770
|
-
},
|
|
73771
|
-
handle: async (args, runtime) => {
|
|
73772
|
-
const snap = await runtime.navigate(args.url);
|
|
73773
|
-
if (!snap) return textResult(`Navigated to "${args.url}".`);
|
|
73774
|
-
return textResult(formatSnapshot(snap));
|
|
73775
|
-
}
|
|
73776
|
-
});
|
|
73777
|
-
var goBack = defineTool({
|
|
73778
|
-
schema: {
|
|
73779
|
-
name: "browser_navigate_back",
|
|
73780
|
-
title: "Go back",
|
|
73781
|
-
description: "Go back to the previous page in the history",
|
|
73782
|
-
inputSchema: object({})
|
|
73783
|
-
},
|
|
73784
|
-
handle: async (_args, runtime) => {
|
|
73785
|
-
const snap = await runtime.goBack();
|
|
73786
|
-
if (!snap) return textResult("Navigated back.");
|
|
73787
|
-
return textResult(formatSnapshot(snap));
|
|
73788
|
-
}
|
|
73789
|
-
});
|
|
73790
|
-
object({});
|
|
73791
|
-
var navigate_default = [
|
|
73792
|
-
navigate,
|
|
73793
|
-
goBack,
|
|
73794
|
-
defineTool({
|
|
73795
|
-
schema: {
|
|
73796
|
-
name: "browser_wait_for",
|
|
73797
|
-
title: "Wait for",
|
|
73798
|
-
description: "Wait for text to appear or disappear or a specified time to pass",
|
|
73799
|
-
inputSchema: object({
|
|
73800
|
-
time: number().optional().describe("The time to wait in seconds"),
|
|
73801
|
-
text: string().optional().describe("The text to wait for"),
|
|
73802
|
-
textGone: string().optional().describe("The text to wait for to disappear")
|
|
73803
|
-
})
|
|
73804
|
-
},
|
|
73805
|
-
handle: async (args, runtime) => {
|
|
73806
|
-
if (!args.text && !args.textGone && !args.time) throw new Error("Either time, text or textGone must be provided");
|
|
73807
|
-
if (args.time) await new Promise((resolve) => setTimeout(resolve, Math.min(3e4, args.time * 1e3)));
|
|
73808
|
-
return textResult(formatSnapshot(await runtime.waitFor({
|
|
73809
|
-
...args.text !== void 0 ? { text: args.text } : {},
|
|
73810
|
-
...args.textGone !== void 0 ? { textGone: args.textGone } : {}
|
|
73811
|
-
}, 5e3)));
|
|
73812
|
-
}
|
|
73813
|
-
})
|
|
73814
|
-
];
|
|
73815
74158
|
object({
|
|
73816
74159
|
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73817
74160
|
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
@@ -73848,17 +74191,6 @@ object({
|
|
|
73848
74191
|
deltaY: number().optional().describe("Vertical scroll delta in pixels (default 0)")
|
|
73849
74192
|
});
|
|
73850
74193
|
var mouse_default = [];
|
|
73851
|
-
//#endregion
|
|
73852
|
-
//#region src/mcp/tools/keyboard.ts
|
|
73853
|
-
object({
|
|
73854
|
-
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73855
|
-
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73856
|
-
text: string().describe("Text to type into the element"),
|
|
73857
|
-
submit: boolean().optional().describe("Whether to submit entered text (press Enter after)"),
|
|
73858
|
-
slowly: boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
|
|
73859
|
-
});
|
|
73860
|
-
object({ key: string().describe("Key to press, e.g. Enter, Escape, Tab, ArrowLeft, Backspace, Delete, or printable characters") });
|
|
73861
|
-
var keyboard_default = [];
|
|
73862
74194
|
object({
|
|
73863
74195
|
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73864
74196
|
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
@@ -73909,254 +74241,9 @@ var form_default = [defineTool({
|
|
|
73909
74241
|
return textResult(formatSnapshot(snap));
|
|
73910
74242
|
}
|
|
73911
74243
|
})];
|
|
73912
|
-
var screenshot_default = [defineTool({
|
|
73913
|
-
schema: {
|
|
73914
|
-
name: "browser_take_screenshot",
|
|
73915
|
-
title: "Browser Take Screenshot",
|
|
73916
|
-
description: "Capture a full-page screenshot of the active tab as a base64-encoded PNG.",
|
|
73917
|
-
inputSchema: object({
|
|
73918
|
-
element: string().optional().describe("Human-readable description of the area to screenshot"),
|
|
73919
|
-
target: string().optional().describe("Element reference or CSS selector to clip screenshot to; omit for full page"),
|
|
73920
|
-
type: _enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
73921
|
-
filename: string().optional().describe("File name to save the screenshot to."),
|
|
73922
|
-
fullPage: boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
|
|
73923
|
-
})
|
|
73924
|
-
},
|
|
73925
|
-
handle: async (args, runtime) => {
|
|
73926
|
-
const target = args.target;
|
|
73927
|
-
const result = await runtime.takeScreenshot({
|
|
73928
|
-
type: args.type,
|
|
73929
|
-
...args.fullPage !== void 0 ? { fullPage: args.fullPage } : {},
|
|
73930
|
-
...target !== void 0 ? { target } : {}
|
|
73931
|
-
});
|
|
73932
|
-
if (args.filename) {
|
|
73933
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73934
|
-
await writeFile(resolvedFilename, Buffer.from(result.data, "base64"));
|
|
73935
|
-
return textResult(`Screenshot saved to "${resolvedFilename}".`);
|
|
73936
|
-
}
|
|
73937
|
-
return { content: [{
|
|
73938
|
-
type: "image",
|
|
73939
|
-
data: result.data,
|
|
73940
|
-
mimeType: result.mimeType
|
|
73941
|
-
}] };
|
|
73942
|
-
}
|
|
73943
|
-
})];
|
|
73944
|
-
var console_default = [defineTool({
|
|
73945
|
-
schema: {
|
|
73946
|
-
name: "browser_console_messages",
|
|
73947
|
-
title: "Get console messages",
|
|
73948
|
-
description: "Returns all console messages",
|
|
73949
|
-
inputSchema: object({
|
|
73950
|
-
level: _enum([
|
|
73951
|
-
"error",
|
|
73952
|
-
"warning",
|
|
73953
|
-
"info",
|
|
73954
|
-
"debug"
|
|
73955
|
-
]).default("info").describe("Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\"."),
|
|
73956
|
-
all: boolean().optional().describe("Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false."),
|
|
73957
|
-
filename: string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
|
|
73958
|
-
})
|
|
73959
|
-
},
|
|
73960
|
-
handle: async (args, runtime) => {
|
|
73961
|
-
const messages = await runtime.consoleMessages(args.level, args.all);
|
|
73962
|
-
const errors = messages.filter((message) => message.type === "error" || message.type === "assert").length;
|
|
73963
|
-
const warnings = messages.filter((message) => message.type === "warning").length;
|
|
73964
|
-
const text = [
|
|
73965
|
-
`Total messages: ${messages.length} (Errors: ${errors}, Warnings: ${warnings})`,
|
|
73966
|
-
"",
|
|
73967
|
-
...messages.map((message) => message.formattedText)
|
|
73968
|
-
].join("\n");
|
|
73969
|
-
if (args.filename) {
|
|
73970
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73971
|
-
await writeFile(resolvedFilename, text);
|
|
73972
|
-
return textResult(`Saved console messages to "${resolvedFilename}".`);
|
|
73973
|
-
}
|
|
73974
|
-
return textResult(text);
|
|
73975
|
-
}
|
|
73976
|
-
})];
|
|
73977
|
-
var evaluate_default = [defineTool({
|
|
73978
|
-
schema: {
|
|
73979
|
-
name: "browser_evaluate",
|
|
73980
|
-
title: "Evaluate JavaScript",
|
|
73981
|
-
description: "Evaluate JavaScript expression on page or element",
|
|
73982
|
-
inputSchema: object({
|
|
73983
|
-
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73984
|
-
target: string().optional().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73985
|
-
function: string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
73986
|
-
filename: string().optional().describe("Filename to save the result to. If not provided, result is returned as text.")
|
|
73987
|
-
})
|
|
73988
|
-
},
|
|
73989
|
-
handle: async (args, runtime) => {
|
|
73990
|
-
const result = await runtime.evaluate(args.function, args.target);
|
|
73991
|
-
const text = JSON.stringify(result, null, 2) ?? "undefined";
|
|
73992
|
-
if (args.filename) {
|
|
73993
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
73994
|
-
await writeFile(resolvedFilename, text);
|
|
73995
|
-
return textResult(`Saved evaluation result to "${resolvedFilename}".`);
|
|
73996
|
-
}
|
|
73997
|
-
return textResult(text);
|
|
73998
|
-
}
|
|
73999
|
-
})];
|
|
74000
|
-
var dialog_default = [defineTool({
|
|
74001
|
-
schema: {
|
|
74002
|
-
name: "browser_handle_dialog",
|
|
74003
|
-
title: "Handle a dialog",
|
|
74004
|
-
description: "Handle a dialog",
|
|
74005
|
-
inputSchema: object({
|
|
74006
|
-
accept: boolean().describe("Whether to accept the dialog."),
|
|
74007
|
-
promptText: string().optional().describe("The text of the prompt in case of a prompt dialog.")
|
|
74008
|
-
})
|
|
74009
|
-
},
|
|
74010
|
-
handle: async (args, runtime) => {
|
|
74011
|
-
const snap = await runtime.handleDialog(args.accept, args.promptText);
|
|
74012
|
-
if (!snap) return textResult(args.accept ? "Accepted dialog." : "Dismissed dialog.");
|
|
74013
|
-
return textResult(formatSnapshot(snap));
|
|
74014
|
-
}
|
|
74015
|
-
})];
|
|
74016
|
-
//#endregion
|
|
74017
|
-
//#region src/mcp/tools/network.ts
|
|
74018
|
-
var requestParts = [
|
|
74019
|
-
"request-headers",
|
|
74020
|
-
"request-body",
|
|
74021
|
-
"response-headers",
|
|
74022
|
-
"response-body"
|
|
74023
|
-
];
|
|
74024
|
-
var networkRequests = defineTool({
|
|
74025
|
-
schema: {
|
|
74026
|
-
name: "browser_network_requests",
|
|
74027
|
-
title: "List network requests",
|
|
74028
|
-
description: "Returns a numbered list of network requests since loading the page. Use browser_network_request with the number to get full details.",
|
|
74029
|
-
inputSchema: object({
|
|
74030
|
-
static: boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false."),
|
|
74031
|
-
filter: string().optional().describe("Only return requests whose URL matches this regexp (e.g. \"/api/.*user\")."),
|
|
74032
|
-
filename: string().optional().describe("Filename to save the network requests to. If not provided, requests are returned as text.")
|
|
74033
|
-
})
|
|
74034
|
-
},
|
|
74035
|
-
handle: async (args, runtime) => {
|
|
74036
|
-
const requests = await runtime.networkRequests();
|
|
74037
|
-
const filter = args.filter ? new RegExp(args.filter) : void 0;
|
|
74038
|
-
const lines = [];
|
|
74039
|
-
let hiddenStaticCount = 0;
|
|
74040
|
-
for (const request of requests) {
|
|
74041
|
-
if (!args.static && !isFetch(request) && isSuccessfulResponse(request)) {
|
|
74042
|
-
hiddenStaticCount++;
|
|
74043
|
-
continue;
|
|
74044
|
-
}
|
|
74045
|
-
if (filter && !filter.test(request.url)) continue;
|
|
74046
|
-
lines.push(`${request.index}. ${renderRequestLine(request)}`);
|
|
74047
|
-
}
|
|
74048
|
-
if (hiddenStaticCount > 0) lines.push(`\nNote: ${hiddenStaticCount} static request${hiddenStaticCount === 1 ? "" : "s"} not shown, run with "static" option to see ${hiddenStaticCount === 1 ? "it" : "them"}.`);
|
|
74049
|
-
const text = lines.join("\n");
|
|
74050
|
-
if (args.filename) {
|
|
74051
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
74052
|
-
await writeFile(resolvedFilename, text);
|
|
74053
|
-
return textResult(`Saved network requests to "${resolvedFilename}".`);
|
|
74054
|
-
}
|
|
74055
|
-
return textResult(text);
|
|
74056
|
-
}
|
|
74057
|
-
});
|
|
74058
|
-
var networkRequest = defineTool({
|
|
74059
|
-
schema: {
|
|
74060
|
-
name: "browser_network_request",
|
|
74061
|
-
title: "Show network request details",
|
|
74062
|
-
description: "Returns full details (headers and body) of a single network request, or a single part if part is set. Use the number from browser_network_requests.",
|
|
74063
|
-
inputSchema: object({
|
|
74064
|
-
index: number().int().min(1).describe("1-based index of the request, as printed by browser_network_requests."),
|
|
74065
|
-
part: _enum(requestParts).optional().describe("Return only this part of the request. Omit to return full details."),
|
|
74066
|
-
filename: string().optional().describe("Filename to save the result to. If not provided, output is returned as text.")
|
|
74067
|
-
})
|
|
74068
|
-
},
|
|
74069
|
-
handle: async (args, runtime) => {
|
|
74070
|
-
const request = await runtime.networkRequest(args.index);
|
|
74071
|
-
if (!request) return textResult(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`, true);
|
|
74072
|
-
const text = args.part ? renderRequestPart(request, args.part) : renderRequestDetails(request);
|
|
74073
|
-
if (args.filename) {
|
|
74074
|
-
const resolvedFilename = await resolveOutputFilePath(args.filename, { outputDir: runtime.getOutputDir() });
|
|
74075
|
-
await writeFile(resolvedFilename, text);
|
|
74076
|
-
return textResult(`Saved network request to "${resolvedFilename}".`);
|
|
74077
|
-
}
|
|
74078
|
-
return textResult(text);
|
|
74079
|
-
}
|
|
74080
|
-
});
|
|
74081
|
-
function isSuccessfulResponse(request) {
|
|
74082
|
-
return !request.failureText && request.status !== void 0 && request.status < 400;
|
|
74083
|
-
}
|
|
74084
|
-
function isFetch(request) {
|
|
74085
|
-
return request.resourceType === "fetch" || request.resourceType === "xhr";
|
|
74086
|
-
}
|
|
74087
|
-
function renderRequestLine(request) {
|
|
74088
|
-
let line = `[${request.method.toUpperCase()}] ${request.url}`;
|
|
74089
|
-
if (request.status !== void 0) line += ` => [${request.status}] ${request.statusText ?? ""}`.trimEnd();
|
|
74090
|
-
else if (request.failureText) line += ` => [FAILED] ${request.failureText}`;
|
|
74091
|
-
return line;
|
|
74092
|
-
}
|
|
74093
|
-
function renderRequestDetails(request) {
|
|
74094
|
-
const lines = [];
|
|
74095
|
-
lines.push(`#${request.index} [${request.method.toUpperCase()}] ${request.url}`);
|
|
74096
|
-
lines.push("");
|
|
74097
|
-
lines.push(" General");
|
|
74098
|
-
if (request.status !== void 0) lines.push(` status: [${request.status}] ${request.statusText ?? ""}`.trimEnd());
|
|
74099
|
-
else if (request.failureText) lines.push(` status: [FAILED] ${request.failureText}`);
|
|
74100
|
-
if (request.durationMs !== void 0) lines.push(` duration: ${request.durationMs}ms`);
|
|
74101
|
-
lines.push(` type: ${request.resourceType}`);
|
|
74102
|
-
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
74103
|
-
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
74104
|
-
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
74105
|
-
if (request.requestBody) lines.push("", `Call browser_network_request with part="request-body" to read the request body.`);
|
|
74106
|
-
if (request.responseBody) lines.push("", `Call browser_network_request with part="response-body" to read the response body.`);
|
|
74107
|
-
return lines.join("\n");
|
|
74108
|
-
}
|
|
74109
|
-
function renderRequestPart(request, part) {
|
|
74110
|
-
if (part === "request-headers") return renderHeaders(request.requestHeaders);
|
|
74111
|
-
if (part === "request-body") return request.requestBody ?? "";
|
|
74112
|
-
if (part === "response-headers") return renderHeaders(request.responseHeaders ?? {});
|
|
74113
|
-
return request.responseBody ?? "";
|
|
74114
|
-
}
|
|
74115
|
-
function appendHeaders(lines, title, headers) {
|
|
74116
|
-
const entries = Object.entries(headers);
|
|
74117
|
-
if (!entries.length) return;
|
|
74118
|
-
lines.push("");
|
|
74119
|
-
lines.push(` ${title}`);
|
|
74120
|
-
for (const [key, value] of entries) lines.push(` ${key}: ${value}`);
|
|
74121
|
-
}
|
|
74122
|
-
function renderHeaders(headers) {
|
|
74123
|
-
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74124
|
-
}
|
|
74125
|
-
var network_default = [networkRequests, networkRequest];
|
|
74126
|
-
var runCode_default = [defineTool({
|
|
74127
|
-
schema: {
|
|
74128
|
-
name: "browser_run_code_unsafe",
|
|
74129
|
-
title: "Run Playwright code (unsafe)",
|
|
74130
|
-
description: "Run a Playwright code snippet. Unsafe: executes arbitrary JavaScript in the browser context approximation and is RCE-equivalent in intent.",
|
|
74131
|
-
inputSchema: object({
|
|
74132
|
-
code: string().optional().describe("A JavaScript function containing Playwright-like code to execute."),
|
|
74133
|
-
filename: string().optional().describe("Load code from the specified file. If both code and filename are provided, code will be ignored.")
|
|
74134
|
-
})
|
|
74135
|
-
},
|
|
74136
|
-
handle: async (args, runtime) => {
|
|
74137
|
-
const code = args.filename ? await readFile(args.filename, "utf8") : args.code;
|
|
74138
|
-
if (!code) throw new Error("Either code or filename must be provided.");
|
|
74139
|
-
const result = await runtime.runCodeUnsafe(code);
|
|
74140
|
-
return textResult(JSON.stringify(result, null, 2) ?? "undefined");
|
|
74141
|
-
}
|
|
74142
|
-
})];
|
|
74143
74244
|
//#endregion
|
|
74144
74245
|
//#region src/mcp/tools/index.ts
|
|
74145
|
-
var allTools = [
|
|
74146
|
-
...connect_default,
|
|
74147
|
-
...common_default,
|
|
74148
|
-
...tabs_default,
|
|
74149
|
-
...navigate_default,
|
|
74150
|
-
...mouse_default,
|
|
74151
|
-
...keyboard_default,
|
|
74152
|
-
...form_default,
|
|
74153
|
-
...screenshot_default,
|
|
74154
|
-
...console_default,
|
|
74155
|
-
...evaluate_default,
|
|
74156
|
-
...dialog_default,
|
|
74157
|
-
...network_default,
|
|
74158
|
-
...runCode_default
|
|
74159
|
-
];
|
|
74246
|
+
var allTools = [...mouse_default, ...form_default];
|
|
74160
74247
|
//#endregion
|
|
74161
74248
|
//#region src/mcp/connectedBrowser.ts
|
|
74162
74249
|
function delay$1(ms) {
|
|
@@ -74595,13 +74682,13 @@ var CDP_KEY_MAP = {
|
|
|
74595
74682
|
async function evaluateBiDi(client, contextId, functionSource, arg) {
|
|
74596
74683
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
74597
74684
|
const response = await client.scriptEvaluate({
|
|
74598
|
-
expression,
|
|
74685
|
+
expression: wrapWithSerializedEvaluationResult(expression),
|
|
74599
74686
|
target: { context: contextId },
|
|
74600
74687
|
awaitPromise: true,
|
|
74601
74688
|
resultOwnership: "none"
|
|
74602
74689
|
});
|
|
74603
74690
|
if (response.type === "exception") throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
|
|
74604
|
-
return response.result
|
|
74691
|
+
return parseSerializedEvaluationResult(extractBiDiValue(response.result));
|
|
74605
74692
|
}
|
|
74606
74693
|
async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
74607
74694
|
const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
|
|
@@ -74623,6 +74710,16 @@ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
|
|
|
74623
74710
|
...response.result?.value?.handle !== void 0 ? { handle: response.result.value.handle } : {}
|
|
74624
74711
|
};
|
|
74625
74712
|
}
|
|
74713
|
+
function extractBiDiValue(value) {
|
|
74714
|
+
if (!value) return;
|
|
74715
|
+
if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
|
|
74716
|
+
if (value.type === "object" && Array.isArray(value.value)) {
|
|
74717
|
+
const obj = {};
|
|
74718
|
+
for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
|
|
74719
|
+
return obj;
|
|
74720
|
+
}
|
|
74721
|
+
return value.value;
|
|
74722
|
+
}
|
|
74626
74723
|
function toAriaSnapshotPayload(request = {}) {
|
|
74627
74724
|
return {
|
|
74628
74725
|
options: normalizeAriaSnapshotOptions({
|
|
@@ -75490,6 +75587,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75490
75587
|
bidiListeners = /* @__PURE__ */ new Map();
|
|
75491
75588
|
responseDataCollector;
|
|
75492
75589
|
activeTabId;
|
|
75590
|
+
ownsSession = false;
|
|
75493
75591
|
constructor(client) {
|
|
75494
75592
|
this.client = client;
|
|
75495
75593
|
}
|
|
@@ -75497,10 +75595,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75497
75595
|
if (args.browser && args.browser !== "firefox") throw new McpToolError("unsupported_protocol_input", "BiDi attach only supports browser \"firefox\" in v1.");
|
|
75498
75596
|
const parsed = new URL(args.endpoint);
|
|
75499
75597
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
|
|
75500
|
-
const
|
|
75598
|
+
const client = await getBidiClientFactory()({
|
|
75501
75599
|
browserName: "firefox",
|
|
75502
|
-
webSocketUrl: args.endpoint
|
|
75503
|
-
})
|
|
75600
|
+
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint, args.sessionId)
|
|
75601
|
+
});
|
|
75602
|
+
const session = new BidiConnectedBrowserSession(client);
|
|
75603
|
+
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
|
|
75504
75604
|
await session.initialize();
|
|
75505
75605
|
if ((await session.refreshTabs()).length === 0) await session.newTab();
|
|
75506
75606
|
return session;
|
|
@@ -75545,7 +75645,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75545
75645
|
}
|
|
75546
75646
|
async snapshot(request = {}) {
|
|
75547
75647
|
const tabId = await this.getActiveTabId();
|
|
75548
|
-
return toBrowserSnapshot(await evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request)), request, {
|
|
75648
|
+
return toBrowserSnapshot(await retryUntilReady(() => evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request))), request, {
|
|
75549
75649
|
console: this.consoleSummary(tabId),
|
|
75550
75650
|
consoleLink: await this.takeConsoleLink(tabId)
|
|
75551
75651
|
});
|
|
@@ -75747,7 +75847,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75747
75847
|
await this.client.networkRemoveDataCollector({ collector: this.responseDataCollector }).catch(() => {});
|
|
75748
75848
|
this.responseDataCollector = void 0;
|
|
75749
75849
|
}
|
|
75750
|
-
await this.client.sessionEnd({}).catch(() => {});
|
|
75850
|
+
if (this.ownsSession) await this.client.sessionEnd({}).catch(() => {});
|
|
75751
75851
|
this.client.close();
|
|
75752
75852
|
}
|
|
75753
75853
|
async navigate(url) {
|
|
@@ -76248,6 +76348,36 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76248
76348
|
return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
|
|
76249
76349
|
}
|
|
76250
76350
|
};
|
|
76351
|
+
function normalizeFirefoxBidiEndpoint(endpoint, sessionId) {
|
|
76352
|
+
const url = new URL(endpoint);
|
|
76353
|
+
if (sessionId) {
|
|
76354
|
+
url.pathname = `/session/${sessionId}`;
|
|
76355
|
+
return url.toString();
|
|
76356
|
+
}
|
|
76357
|
+
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
76358
|
+
return url.toString();
|
|
76359
|
+
}
|
|
76360
|
+
async function ensureMcpBiDiSession(client, endpoint, sessionId) {
|
|
76361
|
+
await client.sessionStatus({});
|
|
76362
|
+
if (sessionId || isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76363
|
+
try {
|
|
76364
|
+
await client.browsingContextGetTree({});
|
|
76365
|
+
return false;
|
|
76366
|
+
} catch (error) {
|
|
76367
|
+
const message = String(error instanceof Error ? error.message : error);
|
|
76368
|
+
if (!message.includes("session does not exist") && !message.includes("invalid session id") && !message.includes("not active")) throw error;
|
|
76369
|
+
}
|
|
76370
|
+
try {
|
|
76371
|
+
await client.sessionNew({ capabilities: { alwaysMatch: { acceptInsecureCerts: true } } });
|
|
76372
|
+
return true;
|
|
76373
|
+
} catch (error) {
|
|
76374
|
+
if (String(error instanceof Error ? error.message : error).includes("Maximum number of active sessions")) throw new Error("Maximum number of active BiDi sessions. Reuse an existing one with sessionId or close the current session first.");
|
|
76375
|
+
throw error;
|
|
76376
|
+
}
|
|
76377
|
+
}
|
|
76378
|
+
function isSessionSpecificFirefoxBidiEndpoint(endpoint) {
|
|
76379
|
+
return /^\/session\/[^/]+$/.test(new URL(endpoint).pathname);
|
|
76380
|
+
}
|
|
76251
76381
|
async function connectBrowserSession(args) {
|
|
76252
76382
|
if (args.protocol === "cdp") return CdpConnectedBrowserSession.connect(args);
|
|
76253
76383
|
return BidiConnectedBrowserSession.connect(args);
|
|
@@ -76279,7 +76409,7 @@ var McpRuntime = class {
|
|
|
76279
76409
|
constructor(sessionFactory = connectBrowserSession, options = {}) {
|
|
76280
76410
|
this.sessionFactory = sessionFactory;
|
|
76281
76411
|
this.snapshotMode = options.snapshotMode ?? "full";
|
|
76282
|
-
this.outputDir = configuredOutputDir({ outputDir: options.outputDir });
|
|
76412
|
+
this.outputDir = configuredOutputDir({ ...options.outputDir !== void 0 ? { outputDir: options.outputDir } : {} });
|
|
76283
76413
|
}
|
|
76284
76414
|
getOutputDir() {
|
|
76285
76415
|
return this.outputDir;
|
|
@@ -76314,6 +76444,7 @@ var McpRuntime = class {
|
|
|
76314
76444
|
this.invalidateSnapshot();
|
|
76315
76445
|
this.pendingFileUploadTarget = void 0;
|
|
76316
76446
|
this.tabs = await session.newTab(url);
|
|
76447
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76317
76448
|
const snapshot = this.tabs.some((tab) => tab.active) ? await this.snapshot() : void 0;
|
|
76318
76449
|
return snapshot ? {
|
|
76319
76450
|
tabs: this.tabs,
|
|
@@ -76327,6 +76458,7 @@ var McpRuntime = class {
|
|
|
76327
76458
|
this.invalidateSnapshot();
|
|
76328
76459
|
this.pendingFileUploadTarget = void 0;
|
|
76329
76460
|
this.tabs = await session.selectTab(tab.id);
|
|
76461
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76330
76462
|
const snapshot = await this.snapshot();
|
|
76331
76463
|
return {
|
|
76332
76464
|
tabs: this.tabs,
|
|
@@ -76340,6 +76472,7 @@ var McpRuntime = class {
|
|
|
76340
76472
|
this.invalidateSnapshot();
|
|
76341
76473
|
this.pendingFileUploadTarget = void 0;
|
|
76342
76474
|
this.tabs = await session.closeTab(tab.id);
|
|
76475
|
+
if (this.snapshotMode === "none") return { tabs: this.tabs };
|
|
76343
76476
|
const snapshot = this.tabs.some((candidate) => candidate.active) ? await this.snapshot() : void 0;
|
|
76344
76477
|
return snapshot ? {
|
|
76345
76478
|
tabs: this.tabs,
|
|
@@ -76348,25 +76481,49 @@ var McpRuntime = class {
|
|
|
76348
76481
|
}
|
|
76349
76482
|
async snapshot(args = {}) {
|
|
76350
76483
|
const session = this.requireConnected();
|
|
76351
|
-
const activeTab = this.requireActiveTab();
|
|
76352
76484
|
const requestKey = this.snapshotRequestKey(args);
|
|
76353
76485
|
const request = {
|
|
76354
76486
|
...args.boxes !== void 0 ? { boxes: args.boxes } : {},
|
|
76355
76487
|
...args.depth !== void 0 ? { depth: args.depth } : {},
|
|
76356
76488
|
...args.target ? { target: this.resolveSnapshotTarget(args.target) } : {}
|
|
76357
76489
|
};
|
|
76358
|
-
const snapshot = await
|
|
76490
|
+
const { activeTab, currentActiveTab, snapshot } = await this.captureStableSnapshot(session, request);
|
|
76359
76491
|
this.snapshotCache = {
|
|
76360
|
-
tabId:
|
|
76492
|
+
tabId: currentActiveTab.id,
|
|
76361
76493
|
requestKey,
|
|
76362
76494
|
text: snapshot.text,
|
|
76363
76495
|
refs: { ...snapshot.refs },
|
|
76364
|
-
title: snapshot.title,
|
|
76365
|
-
url: snapshot.url,
|
|
76496
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76497
|
+
url: currentActiveTab.url || snapshot.url,
|
|
76366
76498
|
...snapshot.console ? { console: { ...snapshot.console } } : {},
|
|
76367
76499
|
...snapshot.consoleLink ? { consoleLink: snapshot.consoleLink } : {}
|
|
76368
76500
|
};
|
|
76369
|
-
return
|
|
76501
|
+
return {
|
|
76502
|
+
...snapshot,
|
|
76503
|
+
title: currentActiveTab.title || snapshot.title,
|
|
76504
|
+
url: currentActiveTab.url || snapshot.url
|
|
76505
|
+
};
|
|
76506
|
+
}
|
|
76507
|
+
async captureStableSnapshot(session, request) {
|
|
76508
|
+
let lastAttempt;
|
|
76509
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
76510
|
+
this.tabs = await session.listTabs();
|
|
76511
|
+
const activeTab = this.requireActiveTab();
|
|
76512
|
+
const snapshot = await session.snapshot(request);
|
|
76513
|
+
const refreshedTabs = await session.listTabs();
|
|
76514
|
+
this.tabs = refreshedTabs;
|
|
76515
|
+
const currentActiveTab = refreshedTabs.find((tab) => tab.active) ?? refreshedTabs.find((tab) => tab.id === activeTab.id) ?? activeTab;
|
|
76516
|
+
const captured = {
|
|
76517
|
+
activeTab,
|
|
76518
|
+
currentActiveTab,
|
|
76519
|
+
snapshot
|
|
76520
|
+
};
|
|
76521
|
+
lastAttempt = captured;
|
|
76522
|
+
if (snapshot.text.trim().length > 0 || currentActiveTab.url === "about:blank") return captured;
|
|
76523
|
+
await delay(150 * (attempt + 1));
|
|
76524
|
+
}
|
|
76525
|
+
if (!lastAttempt) throw new McpToolError("action_failed", "Unable to capture page snapshot.");
|
|
76526
|
+
return lastAttempt;
|
|
76370
76527
|
}
|
|
76371
76528
|
async click(target, opts) {
|
|
76372
76529
|
const session = this.requireConnected();
|
|
@@ -78205,6 +78362,7 @@ async function createRoxyBrowserMcpInMemory(options = {}) {
|
|
|
78205
78362
|
await bundle.server.connect(serverTransport);
|
|
78206
78363
|
return {
|
|
78207
78364
|
server: bundle.server,
|
|
78365
|
+
runtimeManager: bundle.runtimeManager,
|
|
78208
78366
|
serverTransport,
|
|
78209
78367
|
clientTransport,
|
|
78210
78368
|
close: async () => {
|