@roxybrowser/playwright 2.0.2-beta.0 → 2.0.2-beta.1
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/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +52 -3
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +14 -4
- package/dist/mcp/runtime.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 +508 -407
- 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;
|
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -73364,17 +73390,21 @@ var Response$1 = class {
|
|
|
73364
73390
|
content: [{
|
|
73365
73391
|
type: "text",
|
|
73366
73392
|
text: sections.join("\n")
|
|
73367
|
-
}
|
|
73393
|
+
}, ...this.images.map((image) => ({
|
|
73394
|
+
type: "image",
|
|
73395
|
+
data: image.data,
|
|
73396
|
+
mimeType: image.mimeType
|
|
73397
|
+
}))],
|
|
73368
73398
|
...this.isClose ? { isClose: true } : {},
|
|
73369
73399
|
...this.errors.length ? { isError: true } : {}
|
|
73370
73400
|
};
|
|
73371
73401
|
}
|
|
73372
73402
|
};
|
|
73373
73403
|
//#endregion
|
|
73374
|
-
//#region src/mcp/backend/common.ts
|
|
73375
|
-
var common_default$1 = [];
|
|
73376
|
-
//#endregion
|
|
73377
73404
|
//#region src/mcp/backend/tool.ts
|
|
73405
|
+
function defineTool$1(tool) {
|
|
73406
|
+
return tool;
|
|
73407
|
+
}
|
|
73378
73408
|
function defineTabTool(tool) {
|
|
73379
73409
|
return {
|
|
73380
73410
|
...tool,
|
|
@@ -73391,6 +73421,144 @@ function missingModalStateMessage(tool) {
|
|
|
73391
73421
|
if (tool.clearsModalState === "fileChooser") return "[no_file_chooser] No file chooser visible.";
|
|
73392
73422
|
return `Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.`;
|
|
73393
73423
|
}
|
|
73424
|
+
var common_default = [defineTool$1({
|
|
73425
|
+
capability: "core-tabs",
|
|
73426
|
+
schema: {
|
|
73427
|
+
name: "browser_close",
|
|
73428
|
+
title: "Close browser",
|
|
73429
|
+
description: "Close the current browser session.",
|
|
73430
|
+
inputSchema: object({}),
|
|
73431
|
+
type: "action"
|
|
73432
|
+
},
|
|
73433
|
+
handle: async (context, _params, response) => {
|
|
73434
|
+
await context.runtime.close();
|
|
73435
|
+
response.setClose();
|
|
73436
|
+
response.addTextResult("Browser session closed.");
|
|
73437
|
+
}
|
|
73438
|
+
}), defineTool$1({
|
|
73439
|
+
capability: "core",
|
|
73440
|
+
schema: {
|
|
73441
|
+
name: "browser_resize",
|
|
73442
|
+
title: "Resize browser",
|
|
73443
|
+
description: "Resize the active page viewport.",
|
|
73444
|
+
inputSchema: object({
|
|
73445
|
+
width: number().int().positive(),
|
|
73446
|
+
height: number().int().positive()
|
|
73447
|
+
}),
|
|
73448
|
+
type: "action"
|
|
73449
|
+
},
|
|
73450
|
+
handle: async (context, params, response) => {
|
|
73451
|
+
const snapshot = await context.runtime.resize(params.width, params.height);
|
|
73452
|
+
response.setIncludeSnapshot();
|
|
73453
|
+
response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
|
|
73454
|
+
if (!snapshot) response.addTextResult(`Resized viewport to ${params.width}x${params.height}.`);
|
|
73455
|
+
}
|
|
73456
|
+
})];
|
|
73457
|
+
var console_default = [defineTool$1({
|
|
73458
|
+
capability: "core",
|
|
73459
|
+
schema: {
|
|
73460
|
+
name: "browser_console_messages",
|
|
73461
|
+
title: "Get console messages",
|
|
73462
|
+
description: "Returns all console messages",
|
|
73463
|
+
inputSchema: object({
|
|
73464
|
+
level: _enum([
|
|
73465
|
+
"error",
|
|
73466
|
+
"warning",
|
|
73467
|
+
"info",
|
|
73468
|
+
"debug"
|
|
73469
|
+
]).default("info").describe("Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\"."),
|
|
73470
|
+
all: boolean().optional().describe("Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false."),
|
|
73471
|
+
filename: string().optional().describe("Filename to save the console messages to. If not provided, messages are returned as text.")
|
|
73472
|
+
}),
|
|
73473
|
+
type: "readOnly"
|
|
73474
|
+
},
|
|
73475
|
+
handle: async (context, params, response) => {
|
|
73476
|
+
const messages = await context.runtime.consoleMessages(params.level, params.all);
|
|
73477
|
+
const errors = messages.filter((message) => message.type === "error" || message.type === "assert").length;
|
|
73478
|
+
const warnings = messages.filter((message) => message.type === "warning").length;
|
|
73479
|
+
const text = [
|
|
73480
|
+
`Total messages: ${messages.length} (Errors: ${errors}, Warnings: ${warnings})`,
|
|
73481
|
+
"",
|
|
73482
|
+
...messages.map((message) => message.formattedText)
|
|
73483
|
+
].join("\n");
|
|
73484
|
+
if (params.filename) {
|
|
73485
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73486
|
+
await writeFile(resolvedFilename, text);
|
|
73487
|
+
response.addTextResult(`Saved console messages to "${resolvedFilename}".`);
|
|
73488
|
+
return;
|
|
73489
|
+
}
|
|
73490
|
+
response.addTextResult(text);
|
|
73491
|
+
}
|
|
73492
|
+
})];
|
|
73493
|
+
var connect_default = [defineTool$1({
|
|
73494
|
+
capability: "config",
|
|
73495
|
+
schema: {
|
|
73496
|
+
name: "roxy_browser_connect",
|
|
73497
|
+
title: "Roxy Browser Connect",
|
|
73498
|
+
description: "Attach to an existing browser and seed the active tab snapshot.",
|
|
73499
|
+
inputSchema: object({
|
|
73500
|
+
endpoint: string().min(1),
|
|
73501
|
+
browser: _enum(["chrome", "firefox"]).default("chrome")
|
|
73502
|
+
}),
|
|
73503
|
+
type: "action"
|
|
73504
|
+
},
|
|
73505
|
+
handle: async (context, params, response) => {
|
|
73506
|
+
const protocol = params.browser === "firefox" ? "bidi" : "cdp";
|
|
73507
|
+
const result = await context.runtime.connect({
|
|
73508
|
+
protocol,
|
|
73509
|
+
endpoint: params.endpoint,
|
|
73510
|
+
browser: params.browser === "chrome" ? "chromium" : params.browser
|
|
73511
|
+
});
|
|
73512
|
+
response.addTextResult(formatConnectResult({
|
|
73513
|
+
...result,
|
|
73514
|
+
browserName: result.browserName === "chromium" ? "chrome" : result.browserName
|
|
73515
|
+
}));
|
|
73516
|
+
}
|
|
73517
|
+
})];
|
|
73518
|
+
var dialogs_default = [defineTool$1({
|
|
73519
|
+
capability: "core",
|
|
73520
|
+
schema: {
|
|
73521
|
+
name: "browser_handle_dialog",
|
|
73522
|
+
title: "Handle a dialog",
|
|
73523
|
+
description: "Handle a dialog",
|
|
73524
|
+
inputSchema: object({
|
|
73525
|
+
accept: boolean().describe("Whether to accept the dialog."),
|
|
73526
|
+
promptText: string().optional().describe("The text of the prompt in case of a prompt dialog.")
|
|
73527
|
+
}),
|
|
73528
|
+
type: "action"
|
|
73529
|
+
},
|
|
73530
|
+
handle: async (context, params, response) => {
|
|
73531
|
+
const snapshot = await context.runtime.handleDialog(params.accept, params.promptText);
|
|
73532
|
+
response.setIncludeSnapshot();
|
|
73533
|
+
if (!snapshot) response.addTextResult(params.accept ? "Accepted dialog." : "Dismissed dialog.");
|
|
73534
|
+
}
|
|
73535
|
+
})];
|
|
73536
|
+
var evaluate_default = [defineTool$1({
|
|
73537
|
+
capability: "core",
|
|
73538
|
+
schema: {
|
|
73539
|
+
name: "browser_evaluate",
|
|
73540
|
+
title: "Evaluate JavaScript",
|
|
73541
|
+
description: "Evaluate JavaScript expression on page or element",
|
|
73542
|
+
inputSchema: object({
|
|
73543
|
+
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73544
|
+
target: string().optional().describe("Exact target element reference from the page snapshot, or a unique element selector"),
|
|
73545
|
+
function: string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
73546
|
+
filename: string().optional().describe("Filename to save the result to. If not provided, result is returned as text.")
|
|
73547
|
+
}),
|
|
73548
|
+
type: "action"
|
|
73549
|
+
},
|
|
73550
|
+
handle: async (context, params, response) => {
|
|
73551
|
+
const result = await context.runtime.evaluate(params.function, params.target);
|
|
73552
|
+
const text = JSON.stringify(result, null, 2) ?? "undefined";
|
|
73553
|
+
if (params.filename) {
|
|
73554
|
+
const resolvedFilename = await context.resolveOutputFile(params.filename);
|
|
73555
|
+
await writeFile(resolvedFilename, text);
|
|
73556
|
+
response.addTextResult(`Saved evaluation result to "${resolvedFilename}".`);
|
|
73557
|
+
return;
|
|
73558
|
+
}
|
|
73559
|
+
response.addTextResult(text);
|
|
73560
|
+
}
|
|
73561
|
+
})];
|
|
73394
73562
|
var files_default = [defineTabTool({
|
|
73395
73563
|
capability: "core",
|
|
73396
73564
|
schema: {
|
|
@@ -73602,7 +73770,7 @@ var typeSchema = elementSchema$2.extend({
|
|
|
73602
73770
|
submit: boolean().optional().describe("Whether to submit entered text (press Enter after)"),
|
|
73603
73771
|
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
73772
|
});
|
|
73605
|
-
var keyboard_default
|
|
73773
|
+
var keyboard_default = [defineTabTool({
|
|
73606
73774
|
capability: "core-input",
|
|
73607
73775
|
schema: {
|
|
73608
73776
|
name: "browser_press_key",
|
|
@@ -73648,13 +73816,301 @@ var keyboard_default$1 = [defineTabTool({
|
|
|
73648
73816
|
});
|
|
73649
73817
|
}
|
|
73650
73818
|
})];
|
|
73819
|
+
var navigate_default = [
|
|
73820
|
+
defineTool$1({
|
|
73821
|
+
capability: "core-navigation",
|
|
73822
|
+
schema: {
|
|
73823
|
+
name: "browser_navigate",
|
|
73824
|
+
title: "Navigate to a URL",
|
|
73825
|
+
description: "Navigate to a URL",
|
|
73826
|
+
inputSchema: object({ url: string().describe("The URL to navigate to") }),
|
|
73827
|
+
type: "action"
|
|
73828
|
+
},
|
|
73829
|
+
handle: async (context, params, response) => {
|
|
73830
|
+
await context.ensureTab();
|
|
73831
|
+
await context.runtime.navigate(params.url);
|
|
73832
|
+
response.setIncludeSnapshot();
|
|
73833
|
+
response.addCode(`await page.goto('${params.url.startsWith("http") ? params.url : params.url.startsWith("localhost") ? `http://${params.url}` : `https://${params.url}`}');`);
|
|
73834
|
+
}
|
|
73835
|
+
}),
|
|
73836
|
+
defineTool$1({
|
|
73837
|
+
capability: "core-navigation",
|
|
73838
|
+
schema: {
|
|
73839
|
+
name: "browser_navigate_back",
|
|
73840
|
+
title: "Go back",
|
|
73841
|
+
description: "Go back to the previous page in the history",
|
|
73842
|
+
inputSchema: object({}),
|
|
73843
|
+
type: "action"
|
|
73844
|
+
},
|
|
73845
|
+
handle: async (context, _params, response) => {
|
|
73846
|
+
await context.runtime.goBack();
|
|
73847
|
+
response.setIncludeSnapshot();
|
|
73848
|
+
response.addCode("await page.goBack();");
|
|
73849
|
+
}
|
|
73850
|
+
}),
|
|
73851
|
+
defineTool$1({
|
|
73852
|
+
capability: "core-navigation",
|
|
73853
|
+
schema: {
|
|
73854
|
+
name: "browser_navigate_forward",
|
|
73855
|
+
title: "Go forward",
|
|
73856
|
+
description: "Go forward to the next page in the history",
|
|
73857
|
+
inputSchema: object({}),
|
|
73858
|
+
type: "action"
|
|
73859
|
+
},
|
|
73860
|
+
handle: async (context, _params, response) => {
|
|
73861
|
+
await context.runtime.goForward();
|
|
73862
|
+
response.setIncludeSnapshot();
|
|
73863
|
+
response.addCode("await page.goForward();");
|
|
73864
|
+
}
|
|
73865
|
+
}),
|
|
73866
|
+
defineTool$1({
|
|
73867
|
+
capability: "core-navigation",
|
|
73868
|
+
schema: {
|
|
73869
|
+
name: "browser_wait_for",
|
|
73870
|
+
title: "Wait for",
|
|
73871
|
+
description: "Wait for text to appear or disappear or a specified time to pass",
|
|
73872
|
+
inputSchema: object({
|
|
73873
|
+
time: number().optional().describe("The time to wait in seconds"),
|
|
73874
|
+
text: string().optional().describe("The text to wait for"),
|
|
73875
|
+
textGone: string().optional().describe("The text to wait for to disappear")
|
|
73876
|
+
}),
|
|
73877
|
+
type: "action"
|
|
73878
|
+
},
|
|
73879
|
+
handle: async (context, params, response) => {
|
|
73880
|
+
if (!params.text && !params.textGone && !params.time) throw new Error("Either time, text or textGone must be provided");
|
|
73881
|
+
const waitSeconds = params.time;
|
|
73882
|
+
if (waitSeconds !== void 0) await new Promise((resolve) => setTimeout(resolve, Math.min(3e4, waitSeconds * 1e3)));
|
|
73883
|
+
if (params.text || params.textGone) await context.runtime.waitFor({
|
|
73884
|
+
...params.text !== void 0 ? { text: params.text } : {},
|
|
73885
|
+
...params.textGone !== void 0 ? { textGone: params.textGone } : {}
|
|
73886
|
+
}, 5e3);
|
|
73887
|
+
response.setIncludeSnapshot();
|
|
73888
|
+
}
|
|
73889
|
+
})
|
|
73890
|
+
];
|
|
73891
|
+
//#endregion
|
|
73892
|
+
//#region src/mcp/backend/network.ts
|
|
73893
|
+
var requestParts = [
|
|
73894
|
+
"request-headers",
|
|
73895
|
+
"request-body",
|
|
73896
|
+
"response-headers",
|
|
73897
|
+
"response-body"
|
|
73898
|
+
];
|
|
73899
|
+
var networkRequests = defineTool$1({
|
|
73900
|
+
capability: "core",
|
|
73901
|
+
schema: {
|
|
73902
|
+
name: "browser_network_requests",
|
|
73903
|
+
title: "List network requests",
|
|
73904
|
+
description: "Returns a numbered list of network requests since loading the page. Use browser_network_request with the number to get full details.",
|
|
73905
|
+
inputSchema: object({
|
|
73906
|
+
static: boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false."),
|
|
73907
|
+
filter: string().optional().describe("Only return requests whose URL matches this regexp (e.g. \"/api/.*user\")."),
|
|
73908
|
+
filename: string().optional().describe("Filename to save the network requests to. If not provided, requests are returned as text.")
|
|
73909
|
+
}),
|
|
73910
|
+
type: "readOnly"
|
|
73911
|
+
},
|
|
73912
|
+
handle: async (context, args, response) => {
|
|
73913
|
+
const requests = await context.runtime.networkRequests();
|
|
73914
|
+
const filter = args.filter ? new RegExp(args.filter) : void 0;
|
|
73915
|
+
const lines = [];
|
|
73916
|
+
let hiddenStaticCount = 0;
|
|
73917
|
+
for (const request of requests) {
|
|
73918
|
+
if (!args.static && !isFetch(request) && isSuccessfulResponse(request)) {
|
|
73919
|
+
hiddenStaticCount++;
|
|
73920
|
+
continue;
|
|
73921
|
+
}
|
|
73922
|
+
if (filter && !filter.test(request.url)) continue;
|
|
73923
|
+
lines.push(`${request.index}. ${renderRequestLine(request)}`);
|
|
73924
|
+
}
|
|
73925
|
+
if (hiddenStaticCount > 0) lines.push(`\nNote: ${hiddenStaticCount} static request${hiddenStaticCount === 1 ? "" : "s"} not shown, run with "static" option to see ${hiddenStaticCount === 1 ? "it" : "them"}.`);
|
|
73926
|
+
const text = lines.join("\n");
|
|
73927
|
+
if (args.filename) {
|
|
73928
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73929
|
+
await writeFile(resolvedFilename, text);
|
|
73930
|
+
response.addTextResult(`Saved network requests to "${resolvedFilename}".`);
|
|
73931
|
+
return;
|
|
73932
|
+
}
|
|
73933
|
+
response.addTextResult(text);
|
|
73934
|
+
}
|
|
73935
|
+
});
|
|
73936
|
+
var networkRequest = defineTool$1({
|
|
73937
|
+
capability: "core",
|
|
73938
|
+
schema: {
|
|
73939
|
+
name: "browser_network_request",
|
|
73940
|
+
title: "Show network request details",
|
|
73941
|
+
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.",
|
|
73942
|
+
inputSchema: object({
|
|
73943
|
+
index: number().int().min(1).describe("1-based index of the request, as printed by browser_network_requests."),
|
|
73944
|
+
part: _enum(requestParts).optional().describe("Return only this part of the request. Omit to return full details."),
|
|
73945
|
+
filename: string().optional().describe("Filename to save the result to. If not provided, output is returned as text.")
|
|
73946
|
+
}),
|
|
73947
|
+
type: "readOnly"
|
|
73948
|
+
},
|
|
73949
|
+
handle: async (context, args, response) => {
|
|
73950
|
+
const request = await context.runtime.networkRequest(args.index);
|
|
73951
|
+
if (!request) {
|
|
73952
|
+
response.addError(`Request #${args.index} not found. Use browser_network_requests to see available indexes.`);
|
|
73953
|
+
return;
|
|
73954
|
+
}
|
|
73955
|
+
const text = args.part ? renderRequestPart(request, args.part) : renderRequestDetails(request);
|
|
73956
|
+
if (args.filename) {
|
|
73957
|
+
const resolvedFilename = await context.resolveOutputFile(args.filename);
|
|
73958
|
+
await writeFile(resolvedFilename, text);
|
|
73959
|
+
response.addTextResult(`Saved network request to "${resolvedFilename}".`);
|
|
73960
|
+
return;
|
|
73961
|
+
}
|
|
73962
|
+
response.addTextResult(text);
|
|
73963
|
+
}
|
|
73964
|
+
});
|
|
73965
|
+
function isSuccessfulResponse(request) {
|
|
73966
|
+
return !request.failureText && request.status !== void 0 && request.status < 400;
|
|
73967
|
+
}
|
|
73968
|
+
function isFetch(request) {
|
|
73969
|
+
return request.resourceType === "fetch" || request.resourceType === "xhr";
|
|
73970
|
+
}
|
|
73971
|
+
function renderRequestLine(request) {
|
|
73972
|
+
let line = `[${request.method.toUpperCase()}] ${request.url}`;
|
|
73973
|
+
if (request.status !== void 0) line += ` => [${request.status}] ${request.statusText ?? ""}`.trimEnd();
|
|
73974
|
+
else if (request.failureText) line += ` => [FAILED] ${request.failureText}`;
|
|
73975
|
+
return line;
|
|
73976
|
+
}
|
|
73977
|
+
function renderRequestDetails(request) {
|
|
73978
|
+
const lines = [];
|
|
73979
|
+
lines.push(`#${request.index} [${request.method.toUpperCase()}] ${request.url}`);
|
|
73980
|
+
lines.push("");
|
|
73981
|
+
lines.push(" General");
|
|
73982
|
+
if (request.status !== void 0) lines.push(` status: [${request.status}] ${request.statusText ?? ""}`.trimEnd());
|
|
73983
|
+
else if (request.failureText) lines.push(` status: [FAILED] ${request.failureText}`);
|
|
73984
|
+
if (request.durationMs !== void 0) lines.push(` duration: ${request.durationMs}ms`);
|
|
73985
|
+
lines.push(` type: ${request.resourceType}`);
|
|
73986
|
+
if (request.mimeType) lines.push(` mimeType: ${request.mimeType}`);
|
|
73987
|
+
appendHeaders(lines, "Request headers", request.requestHeaders);
|
|
73988
|
+
if (request.responseHeaders) appendHeaders(lines, "Response headers", request.responseHeaders);
|
|
73989
|
+
if (request.requestBody) lines.push("", `Call browser_network_request with part="request-body" to read the request body.`);
|
|
73990
|
+
if (request.responseBody) lines.push("", `Call browser_network_request with part="response-body" to read the response body.`);
|
|
73991
|
+
return lines.join("\n");
|
|
73992
|
+
}
|
|
73993
|
+
function renderRequestPart(request, part) {
|
|
73994
|
+
if (part === "request-headers") return renderHeaders(request.requestHeaders);
|
|
73995
|
+
if (part === "request-body") return request.requestBody ?? "";
|
|
73996
|
+
if (part === "response-headers") return renderHeaders(request.responseHeaders ?? {});
|
|
73997
|
+
return request.responseBody ?? "";
|
|
73998
|
+
}
|
|
73999
|
+
function appendHeaders(lines, title, headers) {
|
|
74000
|
+
const entries = Object.entries(headers);
|
|
74001
|
+
if (!entries.length) return;
|
|
74002
|
+
lines.push("");
|
|
74003
|
+
lines.push(` ${title}`);
|
|
74004
|
+
for (const [key, value] of entries) lines.push(` ${key}: ${value}`);
|
|
74005
|
+
}
|
|
74006
|
+
function renderHeaders(headers) {
|
|
74007
|
+
return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
74008
|
+
}
|
|
74009
|
+
var network_default = [networkRequests, networkRequest];
|
|
74010
|
+
var runCode_default = [defineTool$1({
|
|
74011
|
+
capability: "devtools",
|
|
74012
|
+
schema: {
|
|
74013
|
+
name: "browser_run_code_unsafe",
|
|
74014
|
+
title: "Run code (unsafe)",
|
|
74015
|
+
description: "Run arbitrary code against the current browser session.",
|
|
74016
|
+
inputSchema: object({ code: string().describe("JavaScript code to run against the browser session.") }),
|
|
74017
|
+
type: "action"
|
|
74018
|
+
},
|
|
74019
|
+
handle: async (context, args, response) => {
|
|
74020
|
+
const result = await context.runtime.runCodeUnsafe(args.code);
|
|
74021
|
+
response.addTextResult(JSON.stringify(result, null, 2) ?? "undefined");
|
|
74022
|
+
}
|
|
74023
|
+
})];
|
|
74024
|
+
var screenshot_default = [defineTool$1({
|
|
74025
|
+
capability: "core",
|
|
74026
|
+
schema: {
|
|
74027
|
+
name: "browser_take_screenshot",
|
|
74028
|
+
title: "Browser Take Screenshot",
|
|
74029
|
+
description: "Capture a full-page screenshot of the active tab as a base64-encoded PNG.",
|
|
74030
|
+
inputSchema: object({
|
|
74031
|
+
element: string().optional().describe("Human-readable description of the area to screenshot"),
|
|
74032
|
+
target: string().optional().describe("Element reference or CSS selector to clip screenshot to; omit for full page"),
|
|
74033
|
+
type: _enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
74034
|
+
filename: string().optional().describe("File name to save the screenshot to."),
|
|
74035
|
+
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.")
|
|
74036
|
+
}),
|
|
74037
|
+
type: "readOnly"
|
|
74038
|
+
},
|
|
74039
|
+
handle: async (context, args, response) => {
|
|
74040
|
+
const result = await context.runtime.takeScreenshot({
|
|
74041
|
+
type: args.type,
|
|
74042
|
+
...args.fullPage !== void 0 ? { fullPage: args.fullPage } : {},
|
|
74043
|
+
...args.target !== void 0 ? { target: args.target } : {}
|
|
74044
|
+
});
|
|
74045
|
+
const requestedFilename = args.filename?.trim();
|
|
74046
|
+
const resolvedFilename = await context.resolveOutputFile(requestedFilename || `page-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}.${args.type}`);
|
|
74047
|
+
await writeFile(resolvedFilename, Buffer.from(result.data, "base64"));
|
|
74048
|
+
if (requestedFilename) {
|
|
74049
|
+
response.addTextResult(`Screenshot saved to "${resolvedFilename}".`);
|
|
74050
|
+
return;
|
|
74051
|
+
}
|
|
74052
|
+
response.addTextResult(resolvedFilename);
|
|
74053
|
+
response.addImageResult(result.data, result.mimeType);
|
|
74054
|
+
}
|
|
74055
|
+
})];
|
|
74056
|
+
var tabs_default = [defineTool$1({
|
|
74057
|
+
capability: "core-tabs",
|
|
74058
|
+
schema: {
|
|
74059
|
+
name: "browser_tabs",
|
|
74060
|
+
title: "Browser Tabs",
|
|
74061
|
+
description: "List, create, select, and close browser tabs for the current MCP browser session.",
|
|
74062
|
+
inputSchema: object({
|
|
74063
|
+
action: _enum([
|
|
74064
|
+
"list",
|
|
74065
|
+
"new",
|
|
74066
|
+
"select",
|
|
74067
|
+
"close"
|
|
74068
|
+
]).describe("Operation to perform"),
|
|
74069
|
+
index: number().optional().describe("Tab index, used for close/select. If omitted for close, current tab is closed."),
|
|
74070
|
+
url: string().optional().describe("URL to navigate to in the new tab, used for new.")
|
|
74071
|
+
}),
|
|
74072
|
+
type: "action"
|
|
74073
|
+
},
|
|
74074
|
+
handle: async (context, params, response) => {
|
|
74075
|
+
switch (params.action) {
|
|
74076
|
+
case "list":
|
|
74077
|
+
await context.ensureTab();
|
|
74078
|
+
break;
|
|
74079
|
+
case "new":
|
|
74080
|
+
await context.runtime.newTab(params.url);
|
|
74081
|
+
if (params.url) {
|
|
74082
|
+
response.setIncludeSnapshot();
|
|
74083
|
+
response.addCode(`await page.goto('${params.url}');`);
|
|
74084
|
+
}
|
|
74085
|
+
break;
|
|
74086
|
+
case "close":
|
|
74087
|
+
await context.runtime.closeTab(params.index ?? 0);
|
|
74088
|
+
break;
|
|
74089
|
+
case "select":
|
|
74090
|
+
if (params.index === void 0) throw new Error("Tab index is required");
|
|
74091
|
+
await context.runtime.selectTab(params.index);
|
|
74092
|
+
break;
|
|
74093
|
+
}
|
|
74094
|
+
const tabs = await context.runtime.listTabs();
|
|
74095
|
+
response.addTextResult(formatTabs(tabs));
|
|
74096
|
+
}
|
|
74097
|
+
})];
|
|
73651
74098
|
//#endregion
|
|
73652
74099
|
//#region src/mcp/backend/tools.ts
|
|
73653
74100
|
var browserTools = [
|
|
73654
|
-
...common_default
|
|
74101
|
+
...common_default,
|
|
74102
|
+
...console_default,
|
|
74103
|
+
...connect_default,
|
|
74104
|
+
...dialogs_default,
|
|
74105
|
+
...evaluate_default,
|
|
73655
74106
|
...files_default,
|
|
73656
|
-
...keyboard_default
|
|
73657
|
-
...
|
|
74107
|
+
...keyboard_default,
|
|
74108
|
+
...navigate_default,
|
|
74109
|
+
...network_default,
|
|
74110
|
+
...runCode_default,
|
|
74111
|
+
...screenshot_default,
|
|
74112
|
+
...snapshot_default,
|
|
74113
|
+
...tabs_default
|
|
73658
74114
|
];
|
|
73659
74115
|
//#endregion
|
|
73660
74116
|
//#region src/mcp/errors.ts
|
|
@@ -73683,135 +74139,6 @@ function textResult(text, isError = false) {
|
|
|
73683
74139
|
...isError ? { isError: true } : {}
|
|
73684
74140
|
};
|
|
73685
74141
|
}
|
|
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
74142
|
object({
|
|
73816
74143
|
element: string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
73817
74144
|
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
@@ -73848,17 +74175,6 @@ object({
|
|
|
73848
74175
|
deltaY: number().optional().describe("Vertical scroll delta in pixels (default 0)")
|
|
73849
74176
|
});
|
|
73850
74177
|
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
74178
|
object({
|
|
73863
74179
|
element: string().optional().describe("Human-readable element description used to obtain permission"),
|
|
73864
74180
|
target: string().describe("Exact target element reference from the page snapshot, or a unique element selector")
|
|
@@ -73909,254 +74225,9 @@ var form_default = [defineTool({
|
|
|
73909
74225
|
return textResult(formatSnapshot(snap));
|
|
73910
74226
|
}
|
|
73911
74227
|
})];
|
|
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
74228
|
//#endregion
|
|
74144
74229
|
//#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
|
-
];
|
|
74230
|
+
var allTools = [...mouse_default, ...form_default];
|
|
74160
74231
|
//#endregion
|
|
74161
74232
|
//#region src/mcp/connectedBrowser.ts
|
|
74162
74233
|
function delay$1(ms) {
|
|
@@ -75490,6 +75561,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75490
75561
|
bidiListeners = /* @__PURE__ */ new Map();
|
|
75491
75562
|
responseDataCollector;
|
|
75492
75563
|
activeTabId;
|
|
75564
|
+
ownsSession = false;
|
|
75493
75565
|
constructor(client) {
|
|
75494
75566
|
this.client = client;
|
|
75495
75567
|
}
|
|
@@ -75497,10 +75569,12 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75497
75569
|
if (args.browser && args.browser !== "firefox") throw new McpToolError("unsupported_protocol_input", "BiDi attach only supports browser \"firefox\" in v1.");
|
|
75498
75570
|
const parsed = new URL(args.endpoint);
|
|
75499
75571
|
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
|
|
75572
|
+
const client = await getBidiClientFactory()({
|
|
75501
75573
|
browserName: "firefox",
|
|
75502
|
-
webSocketUrl: args.endpoint
|
|
75503
|
-
})
|
|
75574
|
+
webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint)
|
|
75575
|
+
});
|
|
75576
|
+
const session = new BidiConnectedBrowserSession(client);
|
|
75577
|
+
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint);
|
|
75504
75578
|
await session.initialize();
|
|
75505
75579
|
if ((await session.refreshTabs()).length === 0) await session.newTab();
|
|
75506
75580
|
return session;
|
|
@@ -75747,7 +75821,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
75747
75821
|
await this.client.networkRemoveDataCollector({ collector: this.responseDataCollector }).catch(() => {});
|
|
75748
75822
|
this.responseDataCollector = void 0;
|
|
75749
75823
|
}
|
|
75750
|
-
await this.client.sessionEnd({}).catch(() => {});
|
|
75824
|
+
if (this.ownsSession) await this.client.sessionEnd({}).catch(() => {});
|
|
75751
75825
|
this.client.close();
|
|
75752
75826
|
}
|
|
75753
75827
|
async navigate(url) {
|
|
@@ -76248,6 +76322,32 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
|
|
|
76248
76322
|
return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
|
|
76249
76323
|
}
|
|
76250
76324
|
};
|
|
76325
|
+
function normalizeFirefoxBidiEndpoint(endpoint) {
|
|
76326
|
+
const url = new URL(endpoint);
|
|
76327
|
+
if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
|
|
76328
|
+
return url.toString();
|
|
76329
|
+
}
|
|
76330
|
+
async function ensureMcpBiDiSession(client, endpoint) {
|
|
76331
|
+
await client.sessionStatus({});
|
|
76332
|
+
if (isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
|
|
76333
|
+
try {
|
|
76334
|
+
await client.browsingContextGetTree({});
|
|
76335
|
+
return false;
|
|
76336
|
+
} catch (error) {
|
|
76337
|
+
const message = String(error instanceof Error ? error.message : error);
|
|
76338
|
+
if (!message.includes("session does not exist") && !message.includes("invalid session id") && !message.includes("not active")) throw error;
|
|
76339
|
+
}
|
|
76340
|
+
try {
|
|
76341
|
+
await client.sessionNew({ capabilities: { alwaysMatch: { acceptInsecureCerts: true } } });
|
|
76342
|
+
return true;
|
|
76343
|
+
} catch (error) {
|
|
76344
|
+
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.");
|
|
76345
|
+
throw error;
|
|
76346
|
+
}
|
|
76347
|
+
}
|
|
76348
|
+
function isSessionSpecificFirefoxBidiEndpoint(endpoint) {
|
|
76349
|
+
return /^\/session\/[^/]+$/.test(new URL(endpoint).pathname);
|
|
76350
|
+
}
|
|
76251
76351
|
async function connectBrowserSession(args) {
|
|
76252
76352
|
if (args.protocol === "cdp") return CdpConnectedBrowserSession.connect(args);
|
|
76253
76353
|
return BidiConnectedBrowserSession.connect(args);
|
|
@@ -76279,7 +76379,7 @@ var McpRuntime = class {
|
|
|
76279
76379
|
constructor(sessionFactory = connectBrowserSession, options = {}) {
|
|
76280
76380
|
this.sessionFactory = sessionFactory;
|
|
76281
76381
|
this.snapshotMode = options.snapshotMode ?? "full";
|
|
76282
|
-
this.outputDir = configuredOutputDir({ outputDir: options.outputDir });
|
|
76382
|
+
this.outputDir = configuredOutputDir({ ...options.outputDir !== void 0 ? { outputDir: options.outputDir } : {} });
|
|
76283
76383
|
}
|
|
76284
76384
|
getOutputDir() {
|
|
76285
76385
|
return this.outputDir;
|
|
@@ -76348,6 +76448,7 @@ var McpRuntime = class {
|
|
|
76348
76448
|
}
|
|
76349
76449
|
async snapshot(args = {}) {
|
|
76350
76450
|
const session = this.requireConnected();
|
|
76451
|
+
this.tabs = await session.listTabs();
|
|
76351
76452
|
const activeTab = this.requireActiveTab();
|
|
76352
76453
|
const requestKey = this.snapshotRequestKey(args);
|
|
76353
76454
|
const request = {
|