firefox-devtools-mcp 0.8.0 → 0.9.0
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/dist/index.js
CHANGED
|
@@ -16821,10 +16821,10 @@ var require_validate = __commonJS({
|
|
|
16821
16821
|
return;
|
|
16822
16822
|
}
|
|
16823
16823
|
}
|
|
16824
|
-
|
|
16824
|
+
validateFunction2(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it));
|
|
16825
16825
|
}
|
|
16826
16826
|
exports.validateFunctionCode = validateFunctionCode;
|
|
16827
|
-
function
|
|
16827
|
+
function validateFunction2({ gen, validateName, schema, schemaEnv, opts }, body) {
|
|
16828
16828
|
if (opts.code.es5) {
|
|
16829
16829
|
gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => {
|
|
16830
16830
|
gen.code((0, codegen_1._)`"use strict"; ${funcSourceUrl(schema, opts)}`);
|
|
@@ -16857,7 +16857,7 @@ var require_validate = __commonJS({
|
|
|
16857
16857
|
}
|
|
16858
16858
|
function topSchemaObjCode(it) {
|
|
16859
16859
|
const { schema, opts, gen } = it;
|
|
16860
|
-
|
|
16860
|
+
validateFunction2(it, () => {
|
|
16861
16861
|
if (opts.$comment && schema.$comment)
|
|
16862
16862
|
commentKeyword(it);
|
|
16863
16863
|
checkNoDefault(it);
|
|
@@ -21995,6 +21995,32 @@ var init_logger = __esm({
|
|
|
21995
21995
|
// src/cli.ts
|
|
21996
21996
|
import yargs from "yargs";
|
|
21997
21997
|
import { hideBin } from "yargs/helpers";
|
|
21998
|
+
function parsePrefs(prefs) {
|
|
21999
|
+
const result = {};
|
|
22000
|
+
if (!prefs || prefs.length === 0) {
|
|
22001
|
+
return result;
|
|
22002
|
+
}
|
|
22003
|
+
for (const pref of prefs) {
|
|
22004
|
+
const eqIndex = pref.indexOf("=");
|
|
22005
|
+
if (eqIndex === -1) {
|
|
22006
|
+
continue;
|
|
22007
|
+
}
|
|
22008
|
+
const name = pref.slice(0, eqIndex);
|
|
22009
|
+
const rawValue = pref.slice(eqIndex + 1);
|
|
22010
|
+
let value;
|
|
22011
|
+
if (rawValue === "true") {
|
|
22012
|
+
value = true;
|
|
22013
|
+
} else if (rawValue === "false") {
|
|
22014
|
+
value = false;
|
|
22015
|
+
} else if (/^-?\d+$/.test(rawValue)) {
|
|
22016
|
+
value = parseInt(rawValue, 10);
|
|
22017
|
+
} else {
|
|
22018
|
+
value = rawValue;
|
|
22019
|
+
}
|
|
22020
|
+
result[name] = value;
|
|
22021
|
+
}
|
|
22022
|
+
return result;
|
|
22023
|
+
}
|
|
21998
22024
|
function parseArguments(version3, argv = process.argv) {
|
|
21999
22025
|
const yargsInstance = yargs(hideBin(argv)).scriptName("npx firefox-devtools-mcp@latest").options(cliOptions).example([
|
|
22000
22026
|
[
|
|
@@ -22066,6 +22092,30 @@ var init_cli = __esm({
|
|
|
22066
22092
|
type: "number",
|
|
22067
22093
|
description: "Marionette port to connect to when using --connect-existing (default: 2828)",
|
|
22068
22094
|
default: Number(process.env.MARIONETTE_PORT ?? "2828")
|
|
22095
|
+
},
|
|
22096
|
+
env: {
|
|
22097
|
+
type: "array",
|
|
22098
|
+
description: "Environment variables for Firefox in KEY=VALUE format. Can be specified multiple times. Example: --env MOZ_LOG=HTMLMediaElement:4"
|
|
22099
|
+
},
|
|
22100
|
+
outputFile: {
|
|
22101
|
+
type: "string",
|
|
22102
|
+
description: "Path to file where Firefox output (stdout/stderr) will be written. If not specified, output is written to ~/.firefox-devtools-mcp/output/"
|
|
22103
|
+
},
|
|
22104
|
+
pref: {
|
|
22105
|
+
type: "array",
|
|
22106
|
+
string: true,
|
|
22107
|
+
description: "Set Firefox preference at startup via moz:firefoxOptions (format: name=value). Can be specified multiple times.",
|
|
22108
|
+
alias: "p"
|
|
22109
|
+
},
|
|
22110
|
+
enableScript: {
|
|
22111
|
+
type: "boolean",
|
|
22112
|
+
description: "Enable the evaluate_script tool, which allows executing arbitrary JavaScript in the page context.",
|
|
22113
|
+
default: (process.env.ENABLE_SCRIPT ?? "false") === "true"
|
|
22114
|
+
},
|
|
22115
|
+
enablePrivilegedContext: {
|
|
22116
|
+
type: "boolean",
|
|
22117
|
+
description: "Enable privileged context tools: list/select privileged contexts, evaluate privileged scripts, get/set Firefox prefs, and list extensions. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1.",
|
|
22118
|
+
default: (process.env.ENABLE_PRIVILEGED_CONTEXT ?? "false") === "true"
|
|
22069
22119
|
}
|
|
22070
22120
|
};
|
|
22071
22121
|
}
|
|
@@ -22075,6 +22125,9 @@ var init_cli = __esm({
|
|
|
22075
22125
|
import { Builder, Browser } from "selenium-webdriver";
|
|
22076
22126
|
import firefox from "selenium-webdriver/firefox.js";
|
|
22077
22127
|
import { spawn } from "child_process";
|
|
22128
|
+
import { mkdirSync, openSync, closeSync } from "fs";
|
|
22129
|
+
import { homedir } from "os";
|
|
22130
|
+
import { join } from "path";
|
|
22078
22131
|
function findGeckodriverInCache(fs, path, cacheBase) {
|
|
22079
22132
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
22080
22133
|
const binaryName = `geckodriver${ext}`;
|
|
@@ -22387,6 +22440,9 @@ var init_core3 = __esm({
|
|
|
22387
22440
|
kill() {
|
|
22388
22441
|
this.gdProcess.kill();
|
|
22389
22442
|
}
|
|
22443
|
+
getBidi() {
|
|
22444
|
+
throw new Error("BiDi not available in connect-existing mode");
|
|
22445
|
+
}
|
|
22390
22446
|
};
|
|
22391
22447
|
FirefoxCore = class {
|
|
22392
22448
|
constructor(options) {
|
|
@@ -22394,6 +22450,9 @@ var init_core3 = __esm({
|
|
|
22394
22450
|
}
|
|
22395
22451
|
driver = null;
|
|
22396
22452
|
currentContextId = null;
|
|
22453
|
+
originalEnv = {};
|
|
22454
|
+
logFilePath;
|
|
22455
|
+
logFileFd;
|
|
22397
22456
|
/**
|
|
22398
22457
|
* Launch Firefox (or connect to an existing instance) and establish BiDi connection
|
|
22399
22458
|
*/
|
|
@@ -22407,6 +22466,24 @@ var init_core3 = __esm({
|
|
|
22407
22466
|
const port = this.options.marionettePort ?? 2828;
|
|
22408
22467
|
this.driver = await GeckodriverHttpDriver.connect(port);
|
|
22409
22468
|
} else {
|
|
22469
|
+
if (this.options.logFile) {
|
|
22470
|
+
this.logFilePath = this.options.logFile;
|
|
22471
|
+
} else if (this.options.env && Object.keys(this.options.env).length > 0) {
|
|
22472
|
+
const outputDir = join(homedir(), ".firefox-devtools-mcp", "output");
|
|
22473
|
+
mkdirSync(outputDir, { recursive: true });
|
|
22474
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
22475
|
+
this.logFilePath = join(outputDir, `firefox-${timestamp}.log`);
|
|
22476
|
+
}
|
|
22477
|
+
if (this.options.env) {
|
|
22478
|
+
for (const [key, value] of Object.entries(this.options.env)) {
|
|
22479
|
+
this.originalEnv[key] = process.env[key];
|
|
22480
|
+
process.env[key] = value;
|
|
22481
|
+
logDebug(`Set env ${key}=${value}`);
|
|
22482
|
+
}
|
|
22483
|
+
if (this.options.env.MOZ_LOG_FILE) {
|
|
22484
|
+
logDebug("Note: MOZ_LOG_FILE in env will be used, but may be blocked by sandbox");
|
|
22485
|
+
}
|
|
22486
|
+
}
|
|
22410
22487
|
const firefoxOptions = new firefox.Options();
|
|
22411
22488
|
firefoxOptions.enableBidi();
|
|
22412
22489
|
if (this.options.headless) {
|
|
@@ -22425,12 +22502,24 @@ var init_core3 = __esm({
|
|
|
22425
22502
|
firefoxOptions.addArguments(...this.options.args);
|
|
22426
22503
|
}
|
|
22427
22504
|
if (this.options.profilePath) {
|
|
22428
|
-
firefoxOptions.
|
|
22505
|
+
firefoxOptions.addArguments("--profile", this.options.profilePath);
|
|
22506
|
+
log(`\u{1F4C1} Using Firefox profile: ${this.options.profilePath}`);
|
|
22429
22507
|
}
|
|
22430
22508
|
if (this.options.acceptInsecureCerts) {
|
|
22431
22509
|
firefoxOptions.setAcceptInsecureCerts(true);
|
|
22432
22510
|
}
|
|
22433
|
-
|
|
22511
|
+
if (this.options.prefs) {
|
|
22512
|
+
for (const [name, value] of Object.entries(this.options.prefs)) {
|
|
22513
|
+
firefoxOptions.setPreference(name, value);
|
|
22514
|
+
}
|
|
22515
|
+
}
|
|
22516
|
+
const serviceBuilder = new firefox.ServiceBuilder();
|
|
22517
|
+
if (this.logFilePath) {
|
|
22518
|
+
this.logFileFd = openSync(this.logFilePath, "a");
|
|
22519
|
+
serviceBuilder.setStdio(["ignore", this.logFileFd, this.logFileFd]);
|
|
22520
|
+
log(`\u{1F4DD} Capturing Firefox output to: ${this.logFilePath}`);
|
|
22521
|
+
}
|
|
22522
|
+
this.driver = await new Builder().forBrowser(Browser.FIREFOX).setFirefoxOptions(firefoxOptions).setFirefoxService(serviceBuilder).build();
|
|
22434
22523
|
}
|
|
22435
22524
|
log(
|
|
22436
22525
|
this.options.connectExisting ? "\u2705 Connected to existing Firefox" : "\u2705 Firefox launched with BiDi"
|
|
@@ -22491,6 +22580,80 @@ var init_core3 = __esm({
|
|
|
22491
22580
|
setCurrentContextId(contextId) {
|
|
22492
22581
|
this.currentContextId = contextId;
|
|
22493
22582
|
}
|
|
22583
|
+
/**
|
|
22584
|
+
* Get log file path
|
|
22585
|
+
*/
|
|
22586
|
+
getLogFilePath() {
|
|
22587
|
+
return this.logFilePath;
|
|
22588
|
+
}
|
|
22589
|
+
/**
|
|
22590
|
+
* Get current launch options
|
|
22591
|
+
*/
|
|
22592
|
+
getOptions() {
|
|
22593
|
+
return this.options;
|
|
22594
|
+
}
|
|
22595
|
+
/**
|
|
22596
|
+
* Wait for WebSocket to be in OPEN state
|
|
22597
|
+
*/
|
|
22598
|
+
async waitForWebSocketOpen(ws, timeout = 5e3) {
|
|
22599
|
+
if (ws.readyState === 1) {
|
|
22600
|
+
return;
|
|
22601
|
+
}
|
|
22602
|
+
if (ws.readyState === 0) {
|
|
22603
|
+
return new Promise((resolve4, reject) => {
|
|
22604
|
+
const timeoutId = setTimeout(() => {
|
|
22605
|
+
ws.off("open", onOpen);
|
|
22606
|
+
reject(new Error("Timeout waiting for WebSocket to open"));
|
|
22607
|
+
}, timeout);
|
|
22608
|
+
const onOpen = () => {
|
|
22609
|
+
clearTimeout(timeoutId);
|
|
22610
|
+
ws.off("open", onOpen);
|
|
22611
|
+
resolve4();
|
|
22612
|
+
};
|
|
22613
|
+
ws.on("open", onOpen);
|
|
22614
|
+
});
|
|
22615
|
+
}
|
|
22616
|
+
throw new Error(`WebSocket is not open: readyState ${ws.readyState}`);
|
|
22617
|
+
}
|
|
22618
|
+
/**
|
|
22619
|
+
* Send raw BiDi command and get response
|
|
22620
|
+
*/
|
|
22621
|
+
async sendBiDiCommand(method, params = {}) {
|
|
22622
|
+
if (!this.driver) {
|
|
22623
|
+
throw new Error("Driver not connected");
|
|
22624
|
+
}
|
|
22625
|
+
const bidi = await this.driver.getBidi();
|
|
22626
|
+
const ws = bidi.socket;
|
|
22627
|
+
await this.waitForWebSocketOpen(ws);
|
|
22628
|
+
const id = Math.floor(Math.random() * 1e6);
|
|
22629
|
+
return new Promise((resolve4, reject) => {
|
|
22630
|
+
const messageHandler = (data) => {
|
|
22631
|
+
try {
|
|
22632
|
+
const payload = JSON.parse(data.toString());
|
|
22633
|
+
if (payload.id === id) {
|
|
22634
|
+
ws.off("message", messageHandler);
|
|
22635
|
+
if (payload.error) {
|
|
22636
|
+
reject(new Error(`BiDi error: ${JSON.stringify(payload.error)}`));
|
|
22637
|
+
} else {
|
|
22638
|
+
resolve4(payload.result);
|
|
22639
|
+
}
|
|
22640
|
+
}
|
|
22641
|
+
} catch (err) {
|
|
22642
|
+
}
|
|
22643
|
+
};
|
|
22644
|
+
ws.on("message", messageHandler);
|
|
22645
|
+
const command = {
|
|
22646
|
+
id,
|
|
22647
|
+
method,
|
|
22648
|
+
params
|
|
22649
|
+
};
|
|
22650
|
+
ws.send(JSON.stringify(command));
|
|
22651
|
+
setTimeout(() => {
|
|
22652
|
+
ws.off("message", messageHandler);
|
|
22653
|
+
reject(new Error(`BiDi command timeout: ${method}`));
|
|
22654
|
+
}, 1e4);
|
|
22655
|
+
});
|
|
22656
|
+
}
|
|
22494
22657
|
/**
|
|
22495
22658
|
* Close driver and cleanup.
|
|
22496
22659
|
* When connected to an existing Firefox instance, only kills geckodriver
|
|
@@ -22505,6 +22668,25 @@ var init_core3 = __esm({
|
|
|
22505
22668
|
}
|
|
22506
22669
|
this.driver = null;
|
|
22507
22670
|
}
|
|
22671
|
+
if (this.logFileFd !== void 0) {
|
|
22672
|
+
try {
|
|
22673
|
+
closeSync(this.logFileFd);
|
|
22674
|
+
logDebug("Log file closed");
|
|
22675
|
+
} catch (error2) {
|
|
22676
|
+
logDebug(
|
|
22677
|
+
`Error closing log file: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
22678
|
+
);
|
|
22679
|
+
}
|
|
22680
|
+
this.logFileFd = void 0;
|
|
22681
|
+
}
|
|
22682
|
+
for (const [key, value] of Object.entries(this.originalEnv)) {
|
|
22683
|
+
if (value === void 0) {
|
|
22684
|
+
delete process.env[key];
|
|
22685
|
+
} else {
|
|
22686
|
+
process.env[key] = value;
|
|
22687
|
+
}
|
|
22688
|
+
}
|
|
22689
|
+
this.originalEnv = {};
|
|
22508
22690
|
log("\u2705 Firefox DevTools closed");
|
|
22509
22691
|
}
|
|
22510
22692
|
};
|
|
@@ -24060,6 +24242,13 @@ var init_firefox = __esm({
|
|
|
24060
24242
|
// ============================================================================
|
|
24061
24243
|
// Internal / Advanced
|
|
24062
24244
|
// ============================================================================
|
|
24245
|
+
/**
|
|
24246
|
+
* Send raw BiDi command (for advanced operations)
|
|
24247
|
+
* @internal
|
|
24248
|
+
*/
|
|
24249
|
+
async sendBiDiCommand(method, params = {}) {
|
|
24250
|
+
return await this.core.sendBiDiCommand(method, params);
|
|
24251
|
+
}
|
|
24063
24252
|
/**
|
|
24064
24253
|
* Get WebDriver instance (for advanced operations)
|
|
24065
24254
|
* @internal
|
|
@@ -24067,6 +24256,13 @@ var init_firefox = __esm({
|
|
|
24067
24256
|
getDriver() {
|
|
24068
24257
|
return this.core.getDriver();
|
|
24069
24258
|
}
|
|
24259
|
+
/**
|
|
24260
|
+
* Get current browsing context ID (for advanced operations)
|
|
24261
|
+
* @internal
|
|
24262
|
+
*/
|
|
24263
|
+
getCurrentContextId() {
|
|
24264
|
+
return this.core.getCurrentContextId();
|
|
24265
|
+
}
|
|
24070
24266
|
/**
|
|
24071
24267
|
* Check if Firefox is still connected and responsive
|
|
24072
24268
|
* Returns false if Firefox was closed or connection is broken
|
|
@@ -24074,6 +24270,18 @@ var init_firefox = __esm({
|
|
|
24074
24270
|
async isConnected() {
|
|
24075
24271
|
return await this.core.isConnected();
|
|
24076
24272
|
}
|
|
24273
|
+
/**
|
|
24274
|
+
* Get log file path (if logging is enabled)
|
|
24275
|
+
*/
|
|
24276
|
+
getLogFilePath() {
|
|
24277
|
+
return this.core.getLogFilePath();
|
|
24278
|
+
}
|
|
24279
|
+
/**
|
|
24280
|
+
* Get current launch options
|
|
24281
|
+
*/
|
|
24282
|
+
getOptions() {
|
|
24283
|
+
return this.core.getOptions();
|
|
24284
|
+
}
|
|
24077
24285
|
/**
|
|
24078
24286
|
* Reset all internal state (used when Firefox is detected as closed)
|
|
24079
24287
|
*/
|
|
@@ -24372,6 +24580,136 @@ var init_pages2 = __esm({
|
|
|
24372
24580
|
}
|
|
24373
24581
|
});
|
|
24374
24582
|
|
|
24583
|
+
// src/tools/script.ts
|
|
24584
|
+
function validateFunction(fnString) {
|
|
24585
|
+
if (!fnString || typeof fnString !== "string") {
|
|
24586
|
+
throw new Error("function parameter is required and must be a string");
|
|
24587
|
+
}
|
|
24588
|
+
if (fnString.length > MAX_FUNCTION_SIZE) {
|
|
24589
|
+
throw new Error(
|
|
24590
|
+
`Function too large (${fnString.length} bytes, max ${MAX_FUNCTION_SIZE} bytes). This tool is not designed for massive scripts.`
|
|
24591
|
+
);
|
|
24592
|
+
}
|
|
24593
|
+
const trimmed = fnString.trim();
|
|
24594
|
+
const isFunctionLike = trimmed.startsWith("function") || trimmed.startsWith("async function") || trimmed.startsWith("(") || trimmed.startsWith("async (");
|
|
24595
|
+
if (!isFunctionLike) {
|
|
24596
|
+
throw new Error(
|
|
24597
|
+
`Invalid function format. Expected a function or arrow function, got: "${trimmed.substring(0, 50)}...".
|
|
24598
|
+
|
|
24599
|
+
Valid examples:
|
|
24600
|
+
() => document.title
|
|
24601
|
+
async () => { return await fetch("/api") }
|
|
24602
|
+
(el) => el.innerText
|
|
24603
|
+
function() { return window.location.href }`
|
|
24604
|
+
);
|
|
24605
|
+
}
|
|
24606
|
+
}
|
|
24607
|
+
async function handleEvaluateScript(args2) {
|
|
24608
|
+
try {
|
|
24609
|
+
const {
|
|
24610
|
+
function: fnString,
|
|
24611
|
+
args: fnArgs,
|
|
24612
|
+
timeout
|
|
24613
|
+
} = args2;
|
|
24614
|
+
validateFunction(fnString);
|
|
24615
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
24616
|
+
const firefox3 = await getFirefox2();
|
|
24617
|
+
const driver = firefox3.getDriver();
|
|
24618
|
+
if (!driver) {
|
|
24619
|
+
throw new Error("WebDriver not available");
|
|
24620
|
+
}
|
|
24621
|
+
const scriptTimeout = timeout ?? DEFAULT_TIMEOUT;
|
|
24622
|
+
const resolvedArgs = [];
|
|
24623
|
+
if (fnArgs && fnArgs.length > 0) {
|
|
24624
|
+
for (const arg of fnArgs) {
|
|
24625
|
+
try {
|
|
24626
|
+
const element = await firefox3.resolveUidToElement(arg.uid);
|
|
24627
|
+
resolvedArgs.push(element);
|
|
24628
|
+
} catch (error2) {
|
|
24629
|
+
const errorMsg = error2.message;
|
|
24630
|
+
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID")) {
|
|
24631
|
+
throw new Error(
|
|
24632
|
+
`UID "${arg.uid}" is invalid or from an old snapshot.
|
|
24633
|
+
|
|
24634
|
+
The page may have changed since the snapshot was taken.
|
|
24635
|
+
Please call take_snapshot to get fresh UIDs and try again.`
|
|
24636
|
+
);
|
|
24637
|
+
}
|
|
24638
|
+
throw new Error(`Failed to resolve UID "${arg.uid}": ${errorMsg}`);
|
|
24639
|
+
}
|
|
24640
|
+
}
|
|
24641
|
+
}
|
|
24642
|
+
const evalCode = `
|
|
24643
|
+
const fn = ${fnString};
|
|
24644
|
+
const args = Array.from(arguments);
|
|
24645
|
+
const result = fn(...args);
|
|
24646
|
+
return result instanceof Promise ? result : Promise.resolve(result);
|
|
24647
|
+
`;
|
|
24648
|
+
await driver.manage().setTimeouts({ script: scriptTimeout });
|
|
24649
|
+
const result = await driver.executeScript(evalCode, ...resolvedArgs);
|
|
24650
|
+
let output = "Script ran on page and returned:\n";
|
|
24651
|
+
output += "```json\n";
|
|
24652
|
+
output += JSON.stringify(result, null, 2);
|
|
24653
|
+
output += "\n```";
|
|
24654
|
+
return successResponse(output);
|
|
24655
|
+
} catch (error2) {
|
|
24656
|
+
const errorMsg = error2.message;
|
|
24657
|
+
if (errorMsg.includes("timeout") || errorMsg.includes("Timeout")) {
|
|
24658
|
+
const timeoutValue = args2?.timeout ?? DEFAULT_TIMEOUT;
|
|
24659
|
+
return errorResponse(
|
|
24660
|
+
new Error(
|
|
24661
|
+
`Script execution timed out (exceeded ${timeoutValue}ms).
|
|
24662
|
+
|
|
24663
|
+
The function may contain an infinite loop or be waiting for a slow operation.
|
|
24664
|
+
Try simplifying the script or increasing the timeout parameter.`
|
|
24665
|
+
)
|
|
24666
|
+
);
|
|
24667
|
+
}
|
|
24668
|
+
return errorResponse(error2);
|
|
24669
|
+
}
|
|
24670
|
+
}
|
|
24671
|
+
var evaluateScriptTool, MAX_FUNCTION_SIZE, DEFAULT_TIMEOUT;
|
|
24672
|
+
var init_script = __esm({
|
|
24673
|
+
"src/tools/script.ts"() {
|
|
24674
|
+
"use strict";
|
|
24675
|
+
init_response_helpers();
|
|
24676
|
+
evaluateScriptTool = {
|
|
24677
|
+
name: "evaluate_script",
|
|
24678
|
+
description: "Execute JS function in page. Prefer UID tools for interactions.",
|
|
24679
|
+
inputSchema: {
|
|
24680
|
+
type: "object",
|
|
24681
|
+
properties: {
|
|
24682
|
+
function: {
|
|
24683
|
+
type: "string",
|
|
24684
|
+
description: "JS function string, e.g. () => document.title"
|
|
24685
|
+
},
|
|
24686
|
+
args: {
|
|
24687
|
+
type: "array",
|
|
24688
|
+
description: "UIDs to pass as function arguments",
|
|
24689
|
+
items: {
|
|
24690
|
+
type: "object",
|
|
24691
|
+
properties: {
|
|
24692
|
+
uid: {
|
|
24693
|
+
type: "string",
|
|
24694
|
+
description: "Element UID from snapshot"
|
|
24695
|
+
}
|
|
24696
|
+
},
|
|
24697
|
+
required: ["uid"]
|
|
24698
|
+
}
|
|
24699
|
+
},
|
|
24700
|
+
timeout: {
|
|
24701
|
+
type: "number",
|
|
24702
|
+
description: "Timeout in ms (default: 5000)"
|
|
24703
|
+
}
|
|
24704
|
+
},
|
|
24705
|
+
required: ["function"]
|
|
24706
|
+
}
|
|
24707
|
+
};
|
|
24708
|
+
MAX_FUNCTION_SIZE = 16 * 1024;
|
|
24709
|
+
DEFAULT_TIMEOUT = 5e3;
|
|
24710
|
+
}
|
|
24711
|
+
});
|
|
24712
|
+
|
|
24375
24713
|
// src/tools/console.ts
|
|
24376
24714
|
async function handleListConsoleMessages(args2) {
|
|
24377
24715
|
try {
|
|
@@ -25533,17 +25871,909 @@ var init_utilities = __esm({
|
|
|
25533
25871
|
}
|
|
25534
25872
|
});
|
|
25535
25873
|
|
|
25874
|
+
// src/tools/firefox-management.ts
|
|
25875
|
+
import { readFileSync as readFileSync2, existsSync, statSync } from "fs";
|
|
25876
|
+
async function handleGetFirefoxLogs(input) {
|
|
25877
|
+
try {
|
|
25878
|
+
const {
|
|
25879
|
+
lines = 100,
|
|
25880
|
+
grep,
|
|
25881
|
+
since
|
|
25882
|
+
} = input;
|
|
25883
|
+
const firefox3 = await getFirefox();
|
|
25884
|
+
const logFilePath = firefox3.getLogFilePath();
|
|
25885
|
+
if (!logFilePath) {
|
|
25886
|
+
return successResponse(
|
|
25887
|
+
"No output capture configured. Use --env to set environment variables or --output-file to enable output capture."
|
|
25888
|
+
);
|
|
25889
|
+
}
|
|
25890
|
+
if (!existsSync(logFilePath)) {
|
|
25891
|
+
return successResponse(`Output file not found: ${logFilePath}`);
|
|
25892
|
+
}
|
|
25893
|
+
if (since !== void 0) {
|
|
25894
|
+
const stats = statSync(logFilePath);
|
|
25895
|
+
const ageSeconds = (Date.now() - stats.mtimeMs) / 1e3;
|
|
25896
|
+
if (ageSeconds > since) {
|
|
25897
|
+
return successResponse(
|
|
25898
|
+
`Output file is ${Math.floor(ageSeconds)}s old, but only output from last ${since}s was requested. File may not have recent entries.`
|
|
25899
|
+
);
|
|
25900
|
+
}
|
|
25901
|
+
}
|
|
25902
|
+
const content = readFileSync2(logFilePath, "utf-8");
|
|
25903
|
+
let allLines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
25904
|
+
if (grep) {
|
|
25905
|
+
const grepLower = grep.toLowerCase();
|
|
25906
|
+
allLines = allLines.filter((line) => line.toLowerCase().includes(grepLower));
|
|
25907
|
+
}
|
|
25908
|
+
const maxLines = Math.min(lines, 1e4);
|
|
25909
|
+
const recentLines = allLines.slice(-maxLines);
|
|
25910
|
+
const result = [
|
|
25911
|
+
`\u{1F4CB} Firefox Output File: ${logFilePath}`,
|
|
25912
|
+
`Total lines in file: ${allLines.length}`,
|
|
25913
|
+
grep ? `Lines matching "${grep}": ${allLines.length}` : "",
|
|
25914
|
+
`Showing last ${recentLines.length} lines:`,
|
|
25915
|
+
"",
|
|
25916
|
+
"\u2500".repeat(80),
|
|
25917
|
+
recentLines.join("\n")
|
|
25918
|
+
].filter(Boolean).join("\n");
|
|
25919
|
+
return successResponse(result);
|
|
25920
|
+
} catch (error2) {
|
|
25921
|
+
return errorResponse(error2);
|
|
25922
|
+
}
|
|
25923
|
+
}
|
|
25924
|
+
async function handleGetFirefoxInfo(_input) {
|
|
25925
|
+
try {
|
|
25926
|
+
const firefox3 = await getFirefox();
|
|
25927
|
+
const options = firefox3.getOptions();
|
|
25928
|
+
const logFilePath = firefox3.getLogFilePath();
|
|
25929
|
+
const info = [];
|
|
25930
|
+
info.push("\u{1F98A} Firefox Instance Configuration");
|
|
25931
|
+
info.push("");
|
|
25932
|
+
info.push(`Binary: ${options.firefoxPath ?? "System Firefox (default)"}`);
|
|
25933
|
+
info.push(`Headless: ${options.headless ? "Yes" : "No"}`);
|
|
25934
|
+
if (options.viewport) {
|
|
25935
|
+
info.push(`Viewport: ${options.viewport.width}x${options.viewport.height}`);
|
|
25936
|
+
}
|
|
25937
|
+
if (options.profilePath) {
|
|
25938
|
+
info.push(`Profile: ${options.profilePath}`);
|
|
25939
|
+
}
|
|
25940
|
+
if (options.startUrl) {
|
|
25941
|
+
info.push(`Start URL: ${options.startUrl}`);
|
|
25942
|
+
}
|
|
25943
|
+
if (options.args && options.args.length > 0) {
|
|
25944
|
+
info.push(`Arguments: ${options.args.join(" ")}`);
|
|
25945
|
+
}
|
|
25946
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
25947
|
+
info.push("");
|
|
25948
|
+
info.push("Environment Variables:");
|
|
25949
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
25950
|
+
info.push(` ${key}=${value}`);
|
|
25951
|
+
}
|
|
25952
|
+
}
|
|
25953
|
+
if (options.prefs && Object.keys(options.prefs).length > 0) {
|
|
25954
|
+
info.push("");
|
|
25955
|
+
info.push("Preferences:");
|
|
25956
|
+
for (const [key, value] of Object.entries(options.prefs)) {
|
|
25957
|
+
info.push(` ${key} = ${JSON.stringify(value)}`);
|
|
25958
|
+
}
|
|
25959
|
+
}
|
|
25960
|
+
if (logFilePath) {
|
|
25961
|
+
info.push("");
|
|
25962
|
+
info.push(`Output File: ${logFilePath}`);
|
|
25963
|
+
if (existsSync(logFilePath)) {
|
|
25964
|
+
const stats = statSync(logFilePath);
|
|
25965
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
25966
|
+
info.push(` Size: ${sizeMB} MB`);
|
|
25967
|
+
info.push(` Last Modified: ${stats.mtime.toISOString()}`);
|
|
25968
|
+
} else {
|
|
25969
|
+
info.push(" (file not created yet)");
|
|
25970
|
+
}
|
|
25971
|
+
}
|
|
25972
|
+
return successResponse(info.join("\n"));
|
|
25973
|
+
} catch (error2) {
|
|
25974
|
+
return errorResponse(error2);
|
|
25975
|
+
}
|
|
25976
|
+
}
|
|
25977
|
+
async function handleRestartFirefox(input) {
|
|
25978
|
+
try {
|
|
25979
|
+
const { firefoxPath, profilePath, env, headless, startUrl, prefs } = input;
|
|
25980
|
+
let newEnv;
|
|
25981
|
+
if (env && Array.isArray(env) && env.length > 0) {
|
|
25982
|
+
newEnv = {};
|
|
25983
|
+
for (const envStr of env) {
|
|
25984
|
+
const [key, ...valueParts] = envStr.split("=");
|
|
25985
|
+
if (key && valueParts.length > 0) {
|
|
25986
|
+
newEnv[key] = valueParts.join("=");
|
|
25987
|
+
}
|
|
25988
|
+
}
|
|
25989
|
+
}
|
|
25990
|
+
const currentFirefox = getFirefoxIfRunning();
|
|
25991
|
+
const isConnected = currentFirefox ? await currentFirefox.isConnected() : false;
|
|
25992
|
+
if (currentFirefox && isConnected) {
|
|
25993
|
+
const currentOptions = currentFirefox.getOptions();
|
|
25994
|
+
const mergedPrefs = prefs !== void 0 ? { ...currentOptions.prefs || {}, ...prefs } : currentOptions.prefs;
|
|
25995
|
+
const newOptions = {
|
|
25996
|
+
...currentOptions,
|
|
25997
|
+
firefoxPath: firefoxPath ?? currentOptions.firefoxPath,
|
|
25998
|
+
profilePath: profilePath ?? currentOptions.profilePath,
|
|
25999
|
+
env: newEnv !== void 0 ? newEnv : currentOptions.env,
|
|
26000
|
+
headless: headless !== void 0 ? headless : currentOptions.headless,
|
|
26001
|
+
startUrl: startUrl ?? currentOptions.startUrl ?? "about:home",
|
|
26002
|
+
prefs: mergedPrefs
|
|
26003
|
+
};
|
|
26004
|
+
setNextLaunchOptions(newOptions);
|
|
26005
|
+
try {
|
|
26006
|
+
await currentFirefox.close();
|
|
26007
|
+
} catch (error2) {
|
|
26008
|
+
}
|
|
26009
|
+
resetFirefox();
|
|
26010
|
+
const changes = [];
|
|
26011
|
+
if (firefoxPath && firefoxPath !== currentOptions.firefoxPath) {
|
|
26012
|
+
changes.push(`Binary: ${firefoxPath}`);
|
|
26013
|
+
}
|
|
26014
|
+
if (profilePath && profilePath !== currentOptions.profilePath) {
|
|
26015
|
+
changes.push(`Profile: ${profilePath}`);
|
|
26016
|
+
}
|
|
26017
|
+
if (newEnv !== void 0 && JSON.stringify(newEnv) !== JSON.stringify(currentOptions.env)) {
|
|
26018
|
+
changes.push(`Environment variables updated:`);
|
|
26019
|
+
for (const [key, value] of Object.entries(newEnv)) {
|
|
26020
|
+
changes.push(` ${key}=${value}`);
|
|
26021
|
+
}
|
|
26022
|
+
}
|
|
26023
|
+
if (headless !== void 0 && headless !== currentOptions.headless) {
|
|
26024
|
+
changes.push(`Headless: ${headless ? "enabled" : "disabled"}`);
|
|
26025
|
+
}
|
|
26026
|
+
if (startUrl && startUrl !== currentOptions.startUrl) {
|
|
26027
|
+
changes.push(`Start URL: ${startUrl}`);
|
|
26028
|
+
}
|
|
26029
|
+
if (changes.length === 0) {
|
|
26030
|
+
return successResponse(
|
|
26031
|
+
"\u2705 Firefox closed. Will restart with same configuration on next tool call."
|
|
26032
|
+
);
|
|
26033
|
+
}
|
|
26034
|
+
return successResponse(
|
|
26035
|
+
`\u2705 Firefox closed. Will restart with new configuration on next tool call:
|
|
26036
|
+
${changes.join("\n")}`
|
|
26037
|
+
);
|
|
26038
|
+
} else {
|
|
26039
|
+
if (currentFirefox) {
|
|
26040
|
+
resetFirefox();
|
|
26041
|
+
}
|
|
26042
|
+
const resolvedFirefoxPath = firefoxPath ?? args.firefoxPath ?? void 0;
|
|
26043
|
+
if (!resolvedFirefoxPath) {
|
|
26044
|
+
return errorResponse(
|
|
26045
|
+
new Error(
|
|
26046
|
+
"Firefox is not running and no firefoxPath provided. Please specify firefoxPath to start Firefox."
|
|
26047
|
+
)
|
|
26048
|
+
);
|
|
26049
|
+
}
|
|
26050
|
+
const newOptions = {
|
|
26051
|
+
firefoxPath: resolvedFirefoxPath,
|
|
26052
|
+
profilePath: profilePath ?? args.profilePath ?? void 0,
|
|
26053
|
+
env: newEnv,
|
|
26054
|
+
headless: headless ?? false,
|
|
26055
|
+
startUrl: startUrl ?? "about:home"
|
|
26056
|
+
};
|
|
26057
|
+
setNextLaunchOptions(newOptions);
|
|
26058
|
+
const config2 = [`Binary: ${resolvedFirefoxPath}`];
|
|
26059
|
+
const resolvedProfilePath = profilePath ?? args.profilePath;
|
|
26060
|
+
if (resolvedProfilePath) {
|
|
26061
|
+
config2.push(`Profile: ${resolvedProfilePath}`);
|
|
26062
|
+
}
|
|
26063
|
+
if (newEnv) {
|
|
26064
|
+
config2.push("Environment variables:");
|
|
26065
|
+
for (const [key, value] of Object.entries(newEnv)) {
|
|
26066
|
+
config2.push(` ${key}=${value}`);
|
|
26067
|
+
}
|
|
26068
|
+
}
|
|
26069
|
+
if (headless) {
|
|
26070
|
+
config2.push("Headless: enabled");
|
|
26071
|
+
}
|
|
26072
|
+
if (startUrl) {
|
|
26073
|
+
config2.push(`Start URL: ${startUrl}`);
|
|
26074
|
+
}
|
|
26075
|
+
return successResponse(
|
|
26076
|
+
`\u2705 Firefox configured. Will start on next tool call:
|
|
26077
|
+
${config2.join("\n")}`
|
|
26078
|
+
);
|
|
26079
|
+
}
|
|
26080
|
+
} catch (error2) {
|
|
26081
|
+
return errorResponse(error2);
|
|
26082
|
+
}
|
|
26083
|
+
}
|
|
26084
|
+
var getFirefoxLogsTool, getFirefoxInfoTool, restartFirefoxTool;
|
|
26085
|
+
var init_firefox_management = __esm({
|
|
26086
|
+
async "src/tools/firefox-management.ts"() {
|
|
26087
|
+
"use strict";
|
|
26088
|
+
await init_index();
|
|
26089
|
+
init_response_helpers();
|
|
26090
|
+
getFirefoxLogsTool = {
|
|
26091
|
+
name: "get_firefox_output",
|
|
26092
|
+
description: "Retrieve Firefox output (stdout/stderr including MOZ_LOG, warnings, crashes, stack traces). Returns recent output from the capture file. Use filters to focus on specific content.",
|
|
26093
|
+
inputSchema: {
|
|
26094
|
+
type: "object",
|
|
26095
|
+
properties: {
|
|
26096
|
+
lines: {
|
|
26097
|
+
type: "number",
|
|
26098
|
+
description: "Number of recent log lines to return (default: 100, max: 10000)"
|
|
26099
|
+
},
|
|
26100
|
+
grep: {
|
|
26101
|
+
type: "string",
|
|
26102
|
+
description: "Filter log lines containing this string (case-insensitive)"
|
|
26103
|
+
},
|
|
26104
|
+
since: {
|
|
26105
|
+
type: "number",
|
|
26106
|
+
description: "Only show logs written in the last N seconds"
|
|
26107
|
+
}
|
|
26108
|
+
}
|
|
26109
|
+
}
|
|
26110
|
+
};
|
|
26111
|
+
getFirefoxInfoTool = {
|
|
26112
|
+
name: "get_firefox_info",
|
|
26113
|
+
description: "Get information about the current Firefox instance configuration, including binary path, environment variables, and output file location.",
|
|
26114
|
+
inputSchema: {
|
|
26115
|
+
type: "object",
|
|
26116
|
+
properties: {}
|
|
26117
|
+
}
|
|
26118
|
+
};
|
|
26119
|
+
restartFirefoxTool = {
|
|
26120
|
+
name: "restart_firefox",
|
|
26121
|
+
description: "Restart Firefox with different configuration. Allows changing binary path, environment variables, and other options. All current tabs will be closed.",
|
|
26122
|
+
inputSchema: {
|
|
26123
|
+
type: "object",
|
|
26124
|
+
properties: {
|
|
26125
|
+
firefoxPath: {
|
|
26126
|
+
type: "string",
|
|
26127
|
+
description: "New Firefox binary path (optional, keeps current if not specified)"
|
|
26128
|
+
},
|
|
26129
|
+
profilePath: {
|
|
26130
|
+
type: "string",
|
|
26131
|
+
description: "Firefox profile path (optional, keeps current if not specified)"
|
|
26132
|
+
},
|
|
26133
|
+
env: {
|
|
26134
|
+
type: "array",
|
|
26135
|
+
items: {
|
|
26136
|
+
type: "string"
|
|
26137
|
+
},
|
|
26138
|
+
description: 'New environment variables in KEY=VALUE format (optional, e.g., ["MOZ_LOG=HTMLMediaElement:5", "MOZ_LOG_FILE=/tmp/ff.log"])'
|
|
26139
|
+
},
|
|
26140
|
+
headless: {
|
|
26141
|
+
type: "boolean",
|
|
26142
|
+
description: "Run in headless mode (optional, keeps current if not specified)"
|
|
26143
|
+
},
|
|
26144
|
+
startUrl: {
|
|
26145
|
+
type: "string",
|
|
26146
|
+
description: "URL to navigate to after restart (optional, uses about:home if not specified)"
|
|
26147
|
+
},
|
|
26148
|
+
prefs: {
|
|
26149
|
+
type: "object",
|
|
26150
|
+
description: "Firefox preferences to set at startup. Values are auto-typed: true/false become booleans, integers become numbers, everything else is a string. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1.",
|
|
26151
|
+
additionalProperties: {
|
|
26152
|
+
oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }]
|
|
26153
|
+
}
|
|
26154
|
+
}
|
|
26155
|
+
}
|
|
26156
|
+
}
|
|
26157
|
+
};
|
|
26158
|
+
}
|
|
26159
|
+
});
|
|
26160
|
+
|
|
26161
|
+
// src/tools/privileged-context.ts
|
|
26162
|
+
function formatContextList(contexts) {
|
|
26163
|
+
if (contexts.length === 0) {
|
|
26164
|
+
return "\u{1F527} No privileged contexts found";
|
|
26165
|
+
}
|
|
26166
|
+
const lines = [`\u{1F527} ${contexts.length} privileged contexts`];
|
|
26167
|
+
for (const ctx of contexts) {
|
|
26168
|
+
const id = ctx.context;
|
|
26169
|
+
const url2 = ctx.url || "(no url)";
|
|
26170
|
+
const children = ctx.children ? ` [${ctx.children.length} children]` : "";
|
|
26171
|
+
lines.push(` ${id}: ${url2}${children}`);
|
|
26172
|
+
}
|
|
26173
|
+
return lines.join("\n");
|
|
26174
|
+
}
|
|
26175
|
+
async function handleListPrivilegedContexts(_args) {
|
|
26176
|
+
try {
|
|
26177
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26178
|
+
const firefox3 = await getFirefox2();
|
|
26179
|
+
const result = await firefox3.sendBiDiCommand("browsingContext.getTree", {
|
|
26180
|
+
"moz:scope": "chrome"
|
|
26181
|
+
});
|
|
26182
|
+
const contexts = result.contexts || [];
|
|
26183
|
+
return successResponse(formatContextList(contexts));
|
|
26184
|
+
} catch (error2) {
|
|
26185
|
+
if (error2 instanceof Error && error2.message.includes("UnsupportedOperationError")) {
|
|
26186
|
+
return errorResponse(
|
|
26187
|
+
new Error(
|
|
26188
|
+
"Privileged context access not enabled. Set MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 environment variable and restart Firefox."
|
|
26189
|
+
)
|
|
26190
|
+
);
|
|
26191
|
+
}
|
|
26192
|
+
return errorResponse(error2);
|
|
26193
|
+
}
|
|
26194
|
+
}
|
|
26195
|
+
async function handleSelectPrivilegedContext(args2) {
|
|
26196
|
+
try {
|
|
26197
|
+
const { contextId } = args2;
|
|
26198
|
+
if (!contextId || typeof contextId !== "string") {
|
|
26199
|
+
throw new Error("contextId parameter is required and must be a string");
|
|
26200
|
+
}
|
|
26201
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26202
|
+
const firefox3 = await getFirefox2();
|
|
26203
|
+
const driver = firefox3.getDriver();
|
|
26204
|
+
await driver.switchTo().window(contextId);
|
|
26205
|
+
try {
|
|
26206
|
+
await driver.setContext("chrome");
|
|
26207
|
+
} catch (contextError) {
|
|
26208
|
+
return errorResponse(
|
|
26209
|
+
new Error(
|
|
26210
|
+
`Switched to context ${contextId} but failed to set Marionette privileged context. Your Firefox build may not support privileged context or MOZ_REMOTE_ALLOW_SYSTEM_ACCESS is not set.`
|
|
26211
|
+
)
|
|
26212
|
+
);
|
|
26213
|
+
}
|
|
26214
|
+
return successResponse(
|
|
26215
|
+
`\u2705 Switched to privileged context: ${contextId} (Marionette context set to privileged)`
|
|
26216
|
+
);
|
|
26217
|
+
} catch (error2) {
|
|
26218
|
+
return errorResponse(error2);
|
|
26219
|
+
}
|
|
26220
|
+
}
|
|
26221
|
+
async function handleEvaluatePrivilegedScript(args2) {
|
|
26222
|
+
try {
|
|
26223
|
+
const { expression } = args2;
|
|
26224
|
+
if (!expression || typeof expression !== "string") {
|
|
26225
|
+
throw new Error("expression parameter is required and must be a string");
|
|
26226
|
+
}
|
|
26227
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26228
|
+
const firefox3 = await getFirefox2();
|
|
26229
|
+
const driver = firefox3.getDriver();
|
|
26230
|
+
try {
|
|
26231
|
+
const result = await driver.executeScript(`return (${expression});`);
|
|
26232
|
+
const resultText = typeof result === "string" ? result : result === null ? "null" : result === void 0 ? "undefined" : JSON.stringify(result, null, 2);
|
|
26233
|
+
return successResponse(`\u{1F527} Result:
|
|
26234
|
+
${resultText}`);
|
|
26235
|
+
} catch (executeError) {
|
|
26236
|
+
return errorResponse(
|
|
26237
|
+
new Error(
|
|
26238
|
+
`Script execution failed: ${executeError instanceof Error ? executeError.message : String(executeError)}`
|
|
26239
|
+
)
|
|
26240
|
+
);
|
|
26241
|
+
}
|
|
26242
|
+
} catch (error2) {
|
|
26243
|
+
return errorResponse(error2);
|
|
26244
|
+
}
|
|
26245
|
+
}
|
|
26246
|
+
var listPrivilegedContextsTool, selectPrivilegedContextTool, evaluatePrivilegedScriptTool;
|
|
26247
|
+
var init_privileged_context = __esm({
|
|
26248
|
+
"src/tools/privileged-context.ts"() {
|
|
26249
|
+
"use strict";
|
|
26250
|
+
init_response_helpers();
|
|
26251
|
+
listPrivilegedContextsTool = {
|
|
26252
|
+
name: "list_privileged_contexts",
|
|
26253
|
+
description: "List privileged (privileged) browsing contexts. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Use restart_firefox with env parameter to enable.",
|
|
26254
|
+
inputSchema: {
|
|
26255
|
+
type: "object",
|
|
26256
|
+
properties: {}
|
|
26257
|
+
}
|
|
26258
|
+
};
|
|
26259
|
+
selectPrivilegedContextTool = {
|
|
26260
|
+
name: "select_privileged_context",
|
|
26261
|
+
description: 'Select a privileged browsing context by ID and set WebDriver Classic context to "chrome" . Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var.',
|
|
26262
|
+
inputSchema: {
|
|
26263
|
+
type: "object",
|
|
26264
|
+
properties: {
|
|
26265
|
+
contextId: {
|
|
26266
|
+
type: "string",
|
|
26267
|
+
description: "Privileged browsing context ID from list_privileged_contexts"
|
|
26268
|
+
}
|
|
26269
|
+
},
|
|
26270
|
+
required: ["contextId"]
|
|
26271
|
+
}
|
|
26272
|
+
};
|
|
26273
|
+
evaluatePrivilegedScriptTool = {
|
|
26274
|
+
name: "evaluate_privileged_script",
|
|
26275
|
+
description: "Evaluate JavaScript in the current privileged context. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Returns the result of the expression.",
|
|
26276
|
+
inputSchema: {
|
|
26277
|
+
type: "object",
|
|
26278
|
+
properties: {
|
|
26279
|
+
expression: {
|
|
26280
|
+
type: "string",
|
|
26281
|
+
description: "JavaScript expression to evaluate in the privileged context"
|
|
26282
|
+
}
|
|
26283
|
+
},
|
|
26284
|
+
required: ["expression"]
|
|
26285
|
+
}
|
|
26286
|
+
};
|
|
26287
|
+
}
|
|
26288
|
+
});
|
|
26289
|
+
|
|
26290
|
+
// src/firefox/pref-utils.ts
|
|
26291
|
+
function generatePrefScript(name, value) {
|
|
26292
|
+
const escapedName = JSON.stringify(name);
|
|
26293
|
+
if (typeof value === "boolean") {
|
|
26294
|
+
return `Services.prefs.setBoolPref(${escapedName}, ${value})`;
|
|
26295
|
+
} else if (typeof value === "number") {
|
|
26296
|
+
return `Services.prefs.setIntPref(${escapedName}, ${value})`;
|
|
26297
|
+
} else {
|
|
26298
|
+
return `Services.prefs.setStringPref(${escapedName}, ${JSON.stringify(value)})`;
|
|
26299
|
+
}
|
|
26300
|
+
}
|
|
26301
|
+
var init_pref_utils = __esm({
|
|
26302
|
+
"src/firefox/pref-utils.ts"() {
|
|
26303
|
+
"use strict";
|
|
26304
|
+
}
|
|
26305
|
+
});
|
|
26306
|
+
|
|
26307
|
+
// src/tools/firefox-prefs.ts
|
|
26308
|
+
async function handleSetFirefoxPrefs(args2) {
|
|
26309
|
+
try {
|
|
26310
|
+
const { prefs } = args2;
|
|
26311
|
+
if (!prefs || typeof prefs !== "object") {
|
|
26312
|
+
throw new Error("prefs parameter is required and must be an object");
|
|
26313
|
+
}
|
|
26314
|
+
const prefEntries = Object.entries(prefs);
|
|
26315
|
+
if (prefEntries.length === 0) {
|
|
26316
|
+
return successResponse("No preferences to set");
|
|
26317
|
+
}
|
|
26318
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26319
|
+
const firefox3 = await getFirefox2();
|
|
26320
|
+
const result = await firefox3.sendBiDiCommand("browsingContext.getTree", {
|
|
26321
|
+
"moz:scope": "chrome"
|
|
26322
|
+
});
|
|
26323
|
+
const contexts = result.contexts || [];
|
|
26324
|
+
if (contexts.length === 0) {
|
|
26325
|
+
throw new Error(
|
|
26326
|
+
"No privileged contexts available. Ensure MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 is set."
|
|
26327
|
+
);
|
|
26328
|
+
}
|
|
26329
|
+
const driver = firefox3.getDriver();
|
|
26330
|
+
const chromeContextId = contexts[0].context;
|
|
26331
|
+
const originalContextId = firefox3.getCurrentContextId();
|
|
26332
|
+
try {
|
|
26333
|
+
await driver.switchTo().window(chromeContextId);
|
|
26334
|
+
await driver.setContext("chrome");
|
|
26335
|
+
const results = [];
|
|
26336
|
+
const errors = [];
|
|
26337
|
+
for (const [name, value] of prefEntries) {
|
|
26338
|
+
try {
|
|
26339
|
+
const script = generatePrefScript(name, value);
|
|
26340
|
+
await driver.executeScript(script);
|
|
26341
|
+
results.push(` ${name} = ${JSON.stringify(value)}`);
|
|
26342
|
+
} catch (error2) {
|
|
26343
|
+
errors.push(` ${name}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
26344
|
+
}
|
|
26345
|
+
}
|
|
26346
|
+
const output = [];
|
|
26347
|
+
if (results.length > 0) {
|
|
26348
|
+
output.push(`\u2705 Set ${results.length} preference(s):`);
|
|
26349
|
+
output.push(...results);
|
|
26350
|
+
}
|
|
26351
|
+
if (errors.length > 0) {
|
|
26352
|
+
output.push(`
|
|
26353
|
+
\u26A0\uFE0F Failed to set ${errors.length} preference(s):`);
|
|
26354
|
+
output.push(...errors);
|
|
26355
|
+
}
|
|
26356
|
+
return successResponse(output.join("\n"));
|
|
26357
|
+
} finally {
|
|
26358
|
+
try {
|
|
26359
|
+
await driver.setContext("content");
|
|
26360
|
+
if (originalContextId) {
|
|
26361
|
+
await driver.switchTo().window(originalContextId);
|
|
26362
|
+
}
|
|
26363
|
+
} catch {
|
|
26364
|
+
}
|
|
26365
|
+
}
|
|
26366
|
+
} catch (error2) {
|
|
26367
|
+
if (error2 instanceof Error && error2.message.includes("UnsupportedOperationError")) {
|
|
26368
|
+
return errorResponse(
|
|
26369
|
+
new Error(
|
|
26370
|
+
"Chrome context access not enabled. Set MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 environment variable and restart Firefox."
|
|
26371
|
+
)
|
|
26372
|
+
);
|
|
26373
|
+
}
|
|
26374
|
+
return errorResponse(error2);
|
|
26375
|
+
}
|
|
26376
|
+
}
|
|
26377
|
+
async function handleGetFirefoxPrefs(args2) {
|
|
26378
|
+
try {
|
|
26379
|
+
const { names } = args2;
|
|
26380
|
+
if (!names || !Array.isArray(names) || names.length === 0) {
|
|
26381
|
+
throw new Error("names parameter is required and must be a non-empty array");
|
|
26382
|
+
}
|
|
26383
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26384
|
+
const firefox3 = await getFirefox2();
|
|
26385
|
+
const result = await firefox3.sendBiDiCommand("browsingContext.getTree", {
|
|
26386
|
+
"moz:scope": "chrome"
|
|
26387
|
+
});
|
|
26388
|
+
const contexts = result.contexts || [];
|
|
26389
|
+
if (contexts.length === 0) {
|
|
26390
|
+
throw new Error(
|
|
26391
|
+
"No privileged contexts available. Ensure MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 is set."
|
|
26392
|
+
);
|
|
26393
|
+
}
|
|
26394
|
+
const driver = firefox3.getDriver();
|
|
26395
|
+
const chromeContextId = contexts[0].context;
|
|
26396
|
+
const originalContextId = firefox3.getCurrentContextId();
|
|
26397
|
+
try {
|
|
26398
|
+
await driver.switchTo().window(chromeContextId);
|
|
26399
|
+
await driver.setContext("chrome");
|
|
26400
|
+
const results = [];
|
|
26401
|
+
const errors = [];
|
|
26402
|
+
for (const name of names) {
|
|
26403
|
+
try {
|
|
26404
|
+
const script = `
|
|
26405
|
+
(function() {
|
|
26406
|
+
const type = Services.prefs.getPrefType(${JSON.stringify(name)});
|
|
26407
|
+
if (type === Services.prefs.PREF_INVALID) {
|
|
26408
|
+
return { exists: false };
|
|
26409
|
+
} else if (type === Services.prefs.PREF_BOOL) {
|
|
26410
|
+
return { exists: true, value: Services.prefs.getBoolPref(${JSON.stringify(name)}) };
|
|
26411
|
+
} else if (type === Services.prefs.PREF_INT) {
|
|
26412
|
+
return { exists: true, value: Services.prefs.getIntPref(${JSON.stringify(name)}) };
|
|
26413
|
+
} else {
|
|
26414
|
+
return { exists: true, value: Services.prefs.getStringPref(${JSON.stringify(name)}) };
|
|
26415
|
+
}
|
|
26416
|
+
})()
|
|
26417
|
+
`;
|
|
26418
|
+
const prefResult = await driver.executeScript(`return ${script}`);
|
|
26419
|
+
if (prefResult.exists) {
|
|
26420
|
+
results.push(` ${name} = ${JSON.stringify(prefResult.value)}`);
|
|
26421
|
+
} else {
|
|
26422
|
+
results.push(` ${name} = (not set)`);
|
|
26423
|
+
}
|
|
26424
|
+
} catch (error2) {
|
|
26425
|
+
errors.push(` ${name}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
26426
|
+
}
|
|
26427
|
+
}
|
|
26428
|
+
const output = [];
|
|
26429
|
+
if (results.length > 0) {
|
|
26430
|
+
output.push(`\u{1F4CB} Firefox Preferences:`);
|
|
26431
|
+
output.push(...results);
|
|
26432
|
+
}
|
|
26433
|
+
if (errors.length > 0) {
|
|
26434
|
+
output.push(`
|
|
26435
|
+
\u26A0\uFE0F Failed to read ${errors.length} preference(s):`);
|
|
26436
|
+
output.push(...errors);
|
|
26437
|
+
}
|
|
26438
|
+
return successResponse(output.join("\n"));
|
|
26439
|
+
} finally {
|
|
26440
|
+
try {
|
|
26441
|
+
await driver.setContext("content");
|
|
26442
|
+
if (originalContextId) {
|
|
26443
|
+
await driver.switchTo().window(originalContextId);
|
|
26444
|
+
}
|
|
26445
|
+
} catch {
|
|
26446
|
+
}
|
|
26447
|
+
}
|
|
26448
|
+
} catch (error2) {
|
|
26449
|
+
if (error2 instanceof Error && error2.message.includes("UnsupportedOperationError")) {
|
|
26450
|
+
return errorResponse(
|
|
26451
|
+
new Error(
|
|
26452
|
+
"Chrome context access not enabled. Set MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 environment variable and restart Firefox."
|
|
26453
|
+
)
|
|
26454
|
+
);
|
|
26455
|
+
}
|
|
26456
|
+
return errorResponse(error2);
|
|
26457
|
+
}
|
|
26458
|
+
}
|
|
26459
|
+
var setFirefoxPrefsTool, getFirefoxPrefsTool;
|
|
26460
|
+
var init_firefox_prefs = __esm({
|
|
26461
|
+
"src/tools/firefox-prefs.ts"() {
|
|
26462
|
+
"use strict";
|
|
26463
|
+
init_response_helpers();
|
|
26464
|
+
init_pref_utils();
|
|
26465
|
+
setFirefoxPrefsTool = {
|
|
26466
|
+
name: "set_firefox_prefs",
|
|
26467
|
+
description: "Set Firefox preferences at runtime a privileged API. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var.",
|
|
26468
|
+
inputSchema: {
|
|
26469
|
+
type: "object",
|
|
26470
|
+
properties: {
|
|
26471
|
+
prefs: {
|
|
26472
|
+
type: "object",
|
|
26473
|
+
description: "Object mapping preference names to values. Values are auto-typed: true/false become booleans, integers become numbers, everything else is a string.",
|
|
26474
|
+
additionalProperties: {
|
|
26475
|
+
oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }]
|
|
26476
|
+
}
|
|
26477
|
+
}
|
|
26478
|
+
},
|
|
26479
|
+
required: ["prefs"]
|
|
26480
|
+
}
|
|
26481
|
+
};
|
|
26482
|
+
getFirefoxPrefsTool = {
|
|
26483
|
+
name: "get_firefox_prefs",
|
|
26484
|
+
description: "Get Firefox preference values via a privileged API. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var.",
|
|
26485
|
+
inputSchema: {
|
|
26486
|
+
type: "object",
|
|
26487
|
+
properties: {
|
|
26488
|
+
names: {
|
|
26489
|
+
type: "array",
|
|
26490
|
+
items: { type: "string" },
|
|
26491
|
+
description: "Array of preference names to read"
|
|
26492
|
+
}
|
|
26493
|
+
},
|
|
26494
|
+
required: ["names"]
|
|
26495
|
+
}
|
|
26496
|
+
};
|
|
26497
|
+
}
|
|
26498
|
+
});
|
|
26499
|
+
|
|
26500
|
+
// src/tools/webextension.ts
|
|
26501
|
+
async function handleInstallExtension(args2) {
|
|
26502
|
+
try {
|
|
26503
|
+
const { type, path, value, permanent } = args2;
|
|
26504
|
+
if (!type) {
|
|
26505
|
+
throw new Error("type parameter is required");
|
|
26506
|
+
}
|
|
26507
|
+
if ((type === "archivePath" || type === "path") && !path) {
|
|
26508
|
+
throw new Error(`path parameter is required for type "${type}"`);
|
|
26509
|
+
}
|
|
26510
|
+
if (type === "base64" && !value) {
|
|
26511
|
+
throw new Error('value parameter is required for type "base64"');
|
|
26512
|
+
}
|
|
26513
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26514
|
+
const firefox3 = await getFirefox2();
|
|
26515
|
+
const extensionData = { type };
|
|
26516
|
+
if (path) {
|
|
26517
|
+
extensionData.path = path;
|
|
26518
|
+
}
|
|
26519
|
+
if (value) {
|
|
26520
|
+
extensionData.value = value;
|
|
26521
|
+
}
|
|
26522
|
+
const params = { extensionData };
|
|
26523
|
+
if (permanent !== void 0) {
|
|
26524
|
+
params["moz:permanent"] = permanent;
|
|
26525
|
+
}
|
|
26526
|
+
const result = await firefox3.sendBiDiCommand("webExtension.install", params);
|
|
26527
|
+
const extensionId = result?.extension || "unknown";
|
|
26528
|
+
const installType = permanent ? "permanent" : "temporary";
|
|
26529
|
+
return successResponse(
|
|
26530
|
+
`\u2705 Extension installed (${installType}):
|
|
26531
|
+
ID: ${extensionId}
|
|
26532
|
+
Type: ${type}${path ? `
|
|
26533
|
+
Path: ${path}` : ""}`
|
|
26534
|
+
);
|
|
26535
|
+
} catch (error2) {
|
|
26536
|
+
return errorResponse(error2);
|
|
26537
|
+
}
|
|
26538
|
+
}
|
|
26539
|
+
async function handleUninstallExtension(args2) {
|
|
26540
|
+
try {
|
|
26541
|
+
const { id } = args2;
|
|
26542
|
+
if (!id || typeof id !== "string") {
|
|
26543
|
+
throw new Error("id parameter is required and must be a string");
|
|
26544
|
+
}
|
|
26545
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26546
|
+
const firefox3 = await getFirefox2();
|
|
26547
|
+
await firefox3.sendBiDiCommand("webExtension.uninstall", { extension: id });
|
|
26548
|
+
return successResponse(`\u2705 Extension uninstalled:
|
|
26549
|
+
ID: ${id}`);
|
|
26550
|
+
} catch (error2) {
|
|
26551
|
+
return errorResponse(error2);
|
|
26552
|
+
}
|
|
26553
|
+
}
|
|
26554
|
+
function formatExtensionList(extensions, filterId) {
|
|
26555
|
+
if (extensions.length === 0) {
|
|
26556
|
+
return filterId ? `\u{1F50D} Extension not found: ${filterId}` : "\u{1F4E6} No extensions installed";
|
|
26557
|
+
}
|
|
26558
|
+
const lines = [
|
|
26559
|
+
`\u{1F4E6} ${extensions.length} extension(s)${filterId ? ` (filtered by: ${filterId})` : ""}`
|
|
26560
|
+
];
|
|
26561
|
+
for (const ext of extensions) {
|
|
26562
|
+
lines.push("");
|
|
26563
|
+
lines.push(` \u{1F4CC} ${ext.name} (v${ext.version})`);
|
|
26564
|
+
lines.push(` ID: ${ext.id}`);
|
|
26565
|
+
lines.push(` Type: ${ext.isSystem ? "\u{1F527} System/Built-in" : "\u{1F464} User-installed"}`);
|
|
26566
|
+
lines.push(` UUID: ${ext.uuid}`);
|
|
26567
|
+
lines.push(` Base URL: ${ext.baseURL}`);
|
|
26568
|
+
lines.push(` Manifest: v${ext.manifestVersion || "unknown"}`);
|
|
26569
|
+
lines.push(` Active: ${ext.isActive ? "\u2705" : "\u274C"}`);
|
|
26570
|
+
if (ext.backgroundScripts.length > 0) {
|
|
26571
|
+
lines.push(` Background scripts:`);
|
|
26572
|
+
for (const script of ext.backgroundScripts) {
|
|
26573
|
+
const scriptName = script.split("/").pop();
|
|
26574
|
+
lines.push(` \u2022 ${scriptName}`);
|
|
26575
|
+
}
|
|
26576
|
+
} else {
|
|
26577
|
+
lines.push(` Background scripts: (none)`);
|
|
26578
|
+
}
|
|
26579
|
+
}
|
|
26580
|
+
return lines.join("\n");
|
|
26581
|
+
}
|
|
26582
|
+
async function handleListExtensions(args2) {
|
|
26583
|
+
try {
|
|
26584
|
+
const { ids, name, isActive, isSystem } = args2 || {};
|
|
26585
|
+
const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
|
|
26586
|
+
const firefox3 = await getFirefox2();
|
|
26587
|
+
const result = await firefox3.sendBiDiCommand("browsingContext.getTree", {
|
|
26588
|
+
"moz:scope": "chrome"
|
|
26589
|
+
});
|
|
26590
|
+
const contexts = result.contexts || [];
|
|
26591
|
+
if (contexts.length === 0) {
|
|
26592
|
+
throw new Error(
|
|
26593
|
+
"No privileged contexts available. Ensure MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 is set."
|
|
26594
|
+
);
|
|
26595
|
+
}
|
|
26596
|
+
const driver = firefox3.getDriver();
|
|
26597
|
+
const chromeContextId = contexts[0].context;
|
|
26598
|
+
const originalContextId = firefox3.getCurrentContextId();
|
|
26599
|
+
try {
|
|
26600
|
+
await driver.switchTo().window(chromeContextId);
|
|
26601
|
+
await driver.setContext("chrome");
|
|
26602
|
+
const filterParams = { ids, name, isActive, isSystem };
|
|
26603
|
+
const script = `
|
|
26604
|
+
const callback = arguments[arguments.length - 1];
|
|
26605
|
+
const filter = ${JSON.stringify(filterParams)};
|
|
26606
|
+
(async () => {
|
|
26607
|
+
try {
|
|
26608
|
+
const { AddonManager } = ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs");
|
|
26609
|
+
let addons = await AddonManager.getAllAddons();
|
|
26610
|
+
|
|
26611
|
+
// Filter to only extensions (not themes, plugins, etc.)
|
|
26612
|
+
addons = addons.filter(addon => addon.type === "extension");
|
|
26613
|
+
|
|
26614
|
+
// Apply filters
|
|
26615
|
+
if (filter.ids && filter.ids.length > 0) {
|
|
26616
|
+
addons = addons.filter(addon => filter.ids.includes(addon.id));
|
|
26617
|
+
}
|
|
26618
|
+
if (filter.name) {
|
|
26619
|
+
const search = filter.name.toLowerCase();
|
|
26620
|
+
addons = addons.filter(addon => addon.name.toLowerCase().includes(search));
|
|
26621
|
+
}
|
|
26622
|
+
if (typeof filter.isActive === 'boolean') {
|
|
26623
|
+
addons = addons.filter(addon => addon.isActive === filter.isActive);
|
|
26624
|
+
}
|
|
26625
|
+
if (typeof filter.isSystem === 'boolean') {
|
|
26626
|
+
addons = addons.filter(addon => addon.isSystem === filter.isSystem);
|
|
26627
|
+
}
|
|
26628
|
+
|
|
26629
|
+
const extensions = [];
|
|
26630
|
+
for (const addon of addons) {
|
|
26631
|
+
const policy = WebExtensionPolicy.getByID(addon.id);
|
|
26632
|
+
if (!policy) continue; // Skip if no policy (addon not loaded)
|
|
26633
|
+
|
|
26634
|
+
extensions.push({
|
|
26635
|
+
id: addon.id,
|
|
26636
|
+
name: addon.name,
|
|
26637
|
+
version: addon.version,
|
|
26638
|
+
isActive: addon.isActive,
|
|
26639
|
+
isSystem: addon.isSystem,
|
|
26640
|
+
uuid: policy.mozExtensionHostname,
|
|
26641
|
+
baseURL: policy.baseURL,
|
|
26642
|
+
backgroundScripts: policy.extension?.backgroundScripts || [],
|
|
26643
|
+
manifestVersion: policy.extension?.manifest?.manifest_version || null
|
|
26644
|
+
});
|
|
26645
|
+
}
|
|
26646
|
+
|
|
26647
|
+
callback(extensions);
|
|
26648
|
+
} catch (error) {
|
|
26649
|
+
callback([]);
|
|
26650
|
+
}
|
|
26651
|
+
})();
|
|
26652
|
+
`;
|
|
26653
|
+
const extensions = await driver.executeAsyncScript(script);
|
|
26654
|
+
const filterDesc = [
|
|
26655
|
+
ids && ids.length > 0 ? `ids: [${ids.join(", ")}]` : null,
|
|
26656
|
+
name ? `name: "${name}"` : null,
|
|
26657
|
+
typeof isActive === "boolean" ? `active: ${isActive}` : null,
|
|
26658
|
+
typeof isSystem === "boolean" ? `system: ${isSystem}` : null
|
|
26659
|
+
].filter(Boolean).join(", ");
|
|
26660
|
+
return successResponse(formatExtensionList(extensions, filterDesc || void 0));
|
|
26661
|
+
} finally {
|
|
26662
|
+
try {
|
|
26663
|
+
await driver.setContext("content");
|
|
26664
|
+
if (originalContextId) {
|
|
26665
|
+
await driver.switchTo().window(originalContextId);
|
|
26666
|
+
}
|
|
26667
|
+
} catch {
|
|
26668
|
+
}
|
|
26669
|
+
}
|
|
26670
|
+
} catch (error2) {
|
|
26671
|
+
if (error2 instanceof Error && error2.message.includes("UnsupportedOperationError")) {
|
|
26672
|
+
return errorResponse(
|
|
26673
|
+
new Error(
|
|
26674
|
+
"Chrome context access not enabled. Set MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 environment variable and restart Firefox."
|
|
26675
|
+
)
|
|
26676
|
+
);
|
|
26677
|
+
}
|
|
26678
|
+
return errorResponse(error2);
|
|
26679
|
+
}
|
|
26680
|
+
}
|
|
26681
|
+
var installExtensionTool, uninstallExtensionTool, listExtensionsTool;
|
|
26682
|
+
var init_webextension = __esm({
|
|
26683
|
+
"src/tools/webextension.ts"() {
|
|
26684
|
+
"use strict";
|
|
26685
|
+
init_response_helpers();
|
|
26686
|
+
installExtensionTool = {
|
|
26687
|
+
name: "install_extension",
|
|
26688
|
+
description: "Install a Firefox extension using WebDriver BiDi webExtension.install command. Supports installing from archive (.xpi/.zip), base64-encoded data, or unpacked directory.",
|
|
26689
|
+
inputSchema: {
|
|
26690
|
+
type: "object",
|
|
26691
|
+
properties: {
|
|
26692
|
+
type: {
|
|
26693
|
+
type: "string",
|
|
26694
|
+
enum: ["archivePath", "base64", "path"],
|
|
26695
|
+
description: 'Extension data type: "archivePath" for .xpi/.zip, "base64" for encoded data, "path" for unpacked directory'
|
|
26696
|
+
},
|
|
26697
|
+
path: {
|
|
26698
|
+
type: "string",
|
|
26699
|
+
description: "File path (for archivePath or path types)"
|
|
26700
|
+
},
|
|
26701
|
+
value: {
|
|
26702
|
+
type: "string",
|
|
26703
|
+
description: "Base64-encoded extension data (for base64 type)"
|
|
26704
|
+
},
|
|
26705
|
+
permanent: {
|
|
26706
|
+
type: "boolean",
|
|
26707
|
+
description: "Firefox-specific: Install permanently (requires signed extension). Default: false (temporary install)"
|
|
26708
|
+
}
|
|
26709
|
+
},
|
|
26710
|
+
required: ["type"]
|
|
26711
|
+
}
|
|
26712
|
+
};
|
|
26713
|
+
uninstallExtensionTool = {
|
|
26714
|
+
name: "uninstall_extension",
|
|
26715
|
+
description: "Uninstall a Firefox extension using WebDriver BiDi webExtension.uninstall command. Requires the extension ID returned by install_extension or obtained from list_extensions.",
|
|
26716
|
+
inputSchema: {
|
|
26717
|
+
type: "object",
|
|
26718
|
+
properties: {
|
|
26719
|
+
id: {
|
|
26720
|
+
type: "string",
|
|
26721
|
+
description: 'Extension ID (e.g., "addon@example.com")'
|
|
26722
|
+
}
|
|
26723
|
+
},
|
|
26724
|
+
required: ["id"]
|
|
26725
|
+
}
|
|
26726
|
+
};
|
|
26727
|
+
listExtensionsTool = {
|
|
26728
|
+
name: "list_extensions",
|
|
26729
|
+
description: (
|
|
26730
|
+
// MOZ_REMOTE_ALLOW_SYSTEM_ACCESS is required because the tool relies on the
|
|
26731
|
+
// privileged AddonManager API as a workaround for the currently missing
|
|
26732
|
+
// webExtension.getExtensions WebDriver BiDi command.
|
|
26733
|
+
"List installed Firefox extensions with UUIDs and background scripts. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var."
|
|
26734
|
+
),
|
|
26735
|
+
inputSchema: {
|
|
26736
|
+
type: "object",
|
|
26737
|
+
properties: {
|
|
26738
|
+
ids: {
|
|
26739
|
+
type: "array",
|
|
26740
|
+
items: { type: "string" },
|
|
26741
|
+
description: 'Optional: Filter by exact extension IDs (e.g., ["addon@example.com"])'
|
|
26742
|
+
},
|
|
26743
|
+
name: {
|
|
26744
|
+
type: "string",
|
|
26745
|
+
description: 'Optional: Filter by partial name match (case-insensitive, e.g., "shopify")'
|
|
26746
|
+
},
|
|
26747
|
+
isActive: {
|
|
26748
|
+
type: "boolean",
|
|
26749
|
+
description: "Optional: Filter by enabled (true) or disabled (false) status"
|
|
26750
|
+
},
|
|
26751
|
+
isSystem: {
|
|
26752
|
+
type: "boolean",
|
|
26753
|
+
description: "Optional: Filter by system/built-in (true) or user-installed (false) extensions"
|
|
26754
|
+
}
|
|
26755
|
+
}
|
|
26756
|
+
}
|
|
26757
|
+
};
|
|
26758
|
+
}
|
|
26759
|
+
});
|
|
26760
|
+
|
|
25536
26761
|
// src/tools/index.ts
|
|
25537
26762
|
var init_tools = __esm({
|
|
25538
|
-
"src/tools/index.ts"() {
|
|
26763
|
+
async "src/tools/index.ts"() {
|
|
25539
26764
|
"use strict";
|
|
25540
26765
|
init_pages2();
|
|
26766
|
+
init_script();
|
|
25541
26767
|
init_console2();
|
|
25542
26768
|
init_network2();
|
|
25543
26769
|
init_snapshot2();
|
|
25544
26770
|
init_input();
|
|
25545
26771
|
init_screenshot();
|
|
25546
26772
|
init_utilities();
|
|
26773
|
+
await init_firefox_management();
|
|
26774
|
+
init_privileged_context();
|
|
26775
|
+
init_firefox_prefs();
|
|
26776
|
+
init_webextension();
|
|
25547
26777
|
}
|
|
25548
26778
|
});
|
|
25549
26779
|
|
|
@@ -25565,7 +26795,7 @@ var init_errors4 = __esm({
|
|
|
25565
26795
|
FirefoxDisconnectedError = class extends Error {
|
|
25566
26796
|
constructor(reason) {
|
|
25567
26797
|
const baseMessage = "Firefox browser is not connected";
|
|
25568
|
-
const instruction =
|
|
26798
|
+
const instruction = 'The Firefox browser window was closed. Use the restart_firefox tool with firefoxPath parameter to start a new Firefox instance. Example: restart_firefox with firefoxPath="/usr/bin/firefox"';
|
|
25569
26799
|
const fullMessage = reason ? `${baseMessage}: ${reason}. ${instruction}` : `${baseMessage}. ${instruction}`;
|
|
25570
26800
|
super(fullMessage);
|
|
25571
26801
|
this.name = "FirefoxDisconnectedError";
|
|
@@ -25979,8 +27209,11 @@ __export(index_exports, {
|
|
|
25979
27209
|
FirefoxDisconnectedError: () => FirefoxDisconnectedError,
|
|
25980
27210
|
args: () => args,
|
|
25981
27211
|
getFirefox: () => getFirefox,
|
|
27212
|
+
getFirefoxIfRunning: () => getFirefoxIfRunning,
|
|
25982
27213
|
isDisconnectionError: () => isDisconnectionError,
|
|
25983
|
-
|
|
27214
|
+
isFirefoxRunning: () => isFirefoxRunning,
|
|
27215
|
+
resetFirefox: () => resetFirefox,
|
|
27216
|
+
setNextLaunchOptions: () => setNextLaunchOptions
|
|
25984
27217
|
});
|
|
25985
27218
|
import { version as version2 } from "process";
|
|
25986
27219
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -25993,6 +27226,16 @@ function resetFirefox() {
|
|
|
25993
27226
|
}
|
|
25994
27227
|
log("Firefox instance reset - will reconnect on next tool call");
|
|
25995
27228
|
}
|
|
27229
|
+
function setNextLaunchOptions(options) {
|
|
27230
|
+
nextLaunchOptions = options;
|
|
27231
|
+
log("Next launch options updated");
|
|
27232
|
+
}
|
|
27233
|
+
function isFirefoxRunning() {
|
|
27234
|
+
return firefox2 !== null;
|
|
27235
|
+
}
|
|
27236
|
+
function getFirefoxIfRunning() {
|
|
27237
|
+
return firefox2;
|
|
27238
|
+
}
|
|
25996
27239
|
async function getFirefox() {
|
|
25997
27240
|
if (firefox2) {
|
|
25998
27241
|
const isConnected = await firefox2.isConnected();
|
|
@@ -26004,21 +27247,48 @@ async function getFirefox() {
|
|
|
26004
27247
|
return firefox2;
|
|
26005
27248
|
}
|
|
26006
27249
|
log("Initializing Firefox DevTools connection...");
|
|
26007
|
-
|
|
26008
|
-
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
26013
|
-
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
26017
|
-
|
|
27250
|
+
let options;
|
|
27251
|
+
if (nextLaunchOptions) {
|
|
27252
|
+
options = nextLaunchOptions;
|
|
27253
|
+
nextLaunchOptions = null;
|
|
27254
|
+
log("Using custom launch options from restart_firefox");
|
|
27255
|
+
} else {
|
|
27256
|
+
let envVars;
|
|
27257
|
+
if (args.env && Array.isArray(args.env) && args.env.length > 0) {
|
|
27258
|
+
envVars = {};
|
|
27259
|
+
for (const envStr of args.env) {
|
|
27260
|
+
const [key, ...valueParts] = envStr.split("=");
|
|
27261
|
+
if (key && valueParts.length > 0) {
|
|
27262
|
+
envVars[key] = valueParts.join("=");
|
|
27263
|
+
}
|
|
27264
|
+
}
|
|
27265
|
+
}
|
|
27266
|
+
const prefValues = parsePrefs(args.pref);
|
|
27267
|
+
const prefs = Object.keys(prefValues).length > 0 ? prefValues : void 0;
|
|
27268
|
+
options = {
|
|
27269
|
+
firefoxPath: args.firefoxPath ?? void 0,
|
|
27270
|
+
headless: args.headless,
|
|
27271
|
+
profilePath: args.profilePath ?? void 0,
|
|
27272
|
+
viewport: args.viewport ?? void 0,
|
|
27273
|
+
args: args.firefoxArg ?? void 0,
|
|
27274
|
+
startUrl: args.startUrl ?? void 0,
|
|
27275
|
+
acceptInsecureCerts: args.acceptInsecureCerts,
|
|
27276
|
+
connectExisting: args.connectExisting,
|
|
27277
|
+
marionettePort: args.marionettePort,
|
|
27278
|
+
env: envVars,
|
|
27279
|
+
logFile: args.outputFile ?? void 0,
|
|
27280
|
+
prefs
|
|
27281
|
+
};
|
|
27282
|
+
}
|
|
26018
27283
|
firefox2 = new FirefoxClient(options);
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
|
|
27284
|
+
try {
|
|
27285
|
+
await firefox2.connect();
|
|
27286
|
+
log("Firefox DevTools connection established");
|
|
27287
|
+
return firefox2;
|
|
27288
|
+
} catch (error2) {
|
|
27289
|
+
firefox2 = null;
|
|
27290
|
+
throw error2;
|
|
27291
|
+
}
|
|
26022
27292
|
}
|
|
26023
27293
|
async function main() {
|
|
26024
27294
|
log(`Starting ${SERVER_NAME} v${SERVER_VERSION}`);
|
|
@@ -26074,7 +27344,7 @@ async function main() {
|
|
|
26074
27344
|
log("Firefox DevTools MCP server running on stdio");
|
|
26075
27345
|
log("Ready to accept tool requests");
|
|
26076
27346
|
}
|
|
26077
|
-
var major, args, firefox2, toolHandlers, allTools, modulePath, scriptPath, isMainModule;
|
|
27347
|
+
var major, args, firefox2, nextLaunchOptions, toolHandlers, allTools, modulePath, scriptPath, isMainModule;
|
|
26078
27348
|
var init_index = __esm({
|
|
26079
27349
|
async "src/index.ts"() {
|
|
26080
27350
|
init_server2();
|
|
@@ -26084,7 +27354,7 @@ var init_index = __esm({
|
|
|
26084
27354
|
init_logger();
|
|
26085
27355
|
init_cli();
|
|
26086
27356
|
init_firefox();
|
|
26087
|
-
init_tools();
|
|
27357
|
+
await init_tools();
|
|
26088
27358
|
init_errors4();
|
|
26089
27359
|
init_firefox();
|
|
26090
27360
|
init_errors4();
|
|
@@ -26105,15 +27375,14 @@ var init_index = __esm({
|
|
|
26105
27375
|
}
|
|
26106
27376
|
args = parseArguments(SERVER_VERSION);
|
|
26107
27377
|
firefox2 = null;
|
|
26108
|
-
|
|
27378
|
+
nextLaunchOptions = null;
|
|
27379
|
+
toolHandlers = new Map([
|
|
26109
27380
|
// Pages
|
|
26110
27381
|
["list_pages", handleListPages],
|
|
26111
27382
|
["new_page", handleNewPage],
|
|
26112
27383
|
["navigate_page", handleNavigatePage],
|
|
26113
27384
|
["select_page", handleSelectPage],
|
|
26114
27385
|
["close_page", handleClosePage],
|
|
26115
|
-
// Script evaluation - DISABLED (see docs/future-features.md)
|
|
26116
|
-
// ['evaluate_script', tools.handleEvaluateScript],
|
|
26117
27386
|
// Console
|
|
26118
27387
|
["list_console_messages", handleListConsoleMessages],
|
|
26119
27388
|
["clear_console_messages", handleClearConsoleMessages],
|
|
@@ -26138,7 +27407,25 @@ var init_index = __esm({
|
|
|
26138
27407
|
["accept_dialog", handleAcceptDialog],
|
|
26139
27408
|
["dismiss_dialog", handleDismissDialog],
|
|
26140
27409
|
["navigate_history", handleNavigateHistory],
|
|
26141
|
-
["set_viewport_size", handleSetViewportSize]
|
|
27410
|
+
["set_viewport_size", handleSetViewportSize],
|
|
27411
|
+
// Firefox Management
|
|
27412
|
+
["get_firefox_output", handleGetFirefoxLogs],
|
|
27413
|
+
["get_firefox_info", handleGetFirefoxInfo],
|
|
27414
|
+
["restart_firefox", handleRestartFirefox],
|
|
27415
|
+
// WebExtensions (install/uninstall use standard BiDi, no privileged context required)
|
|
27416
|
+
["install_extension", handleInstallExtension],
|
|
27417
|
+
["uninstall_extension", handleUninstallExtension],
|
|
27418
|
+
// Script evaluation — requires --enable-script
|
|
27419
|
+
...args.enableScript ? [["evaluate_script", handleEvaluateScript]] : [],
|
|
27420
|
+
// Privileged context tools — requires --enable-privileged-context
|
|
27421
|
+
...args.enablePrivilegedContext ? [
|
|
27422
|
+
["list_privileged_contexts", handleListPrivilegedContexts],
|
|
27423
|
+
["select_privileged_context", handleSelectPrivilegedContext],
|
|
27424
|
+
["evaluate_privileged_script", handleEvaluatePrivilegedScript],
|
|
27425
|
+
["set_firefox_prefs", handleSetFirefoxPrefs],
|
|
27426
|
+
["get_firefox_prefs", handleGetFirefoxPrefs],
|
|
27427
|
+
["list_extensions", handleListExtensions]
|
|
27428
|
+
] : []
|
|
26142
27429
|
]);
|
|
26143
27430
|
allTools = [
|
|
26144
27431
|
listPagesTool,
|
|
@@ -26164,7 +27451,23 @@ var init_index = __esm({
|
|
|
26164
27451
|
acceptDialogTool,
|
|
26165
27452
|
dismissDialogTool,
|
|
26166
27453
|
navigateHistoryTool,
|
|
26167
|
-
setViewportSizeTool
|
|
27454
|
+
setViewportSizeTool,
|
|
27455
|
+
getFirefoxLogsTool,
|
|
27456
|
+
getFirefoxInfoTool,
|
|
27457
|
+
restartFirefoxTool,
|
|
27458
|
+
installExtensionTool,
|
|
27459
|
+
uninstallExtensionTool,
|
|
27460
|
+
// Script evaluation — requires --enable-script
|
|
27461
|
+
...args.enableScript ? [evaluateScriptTool] : [],
|
|
27462
|
+
// Privileged context tools — requires --enable-privileged-context
|
|
27463
|
+
...args.enablePrivilegedContext ? [
|
|
27464
|
+
listPrivilegedContextsTool,
|
|
27465
|
+
selectPrivilegedContextTool,
|
|
27466
|
+
evaluatePrivilegedScriptTool,
|
|
27467
|
+
setFirefoxPrefsTool,
|
|
27468
|
+
getFirefoxPrefsTool,
|
|
27469
|
+
listExtensionsTool
|
|
27470
|
+
] : []
|
|
26168
27471
|
];
|
|
26169
27472
|
modulePath = fileURLToPath2(import.meta.url);
|
|
26170
27473
|
scriptPath = process.argv[1] ? resolve3(process.argv[1]) : "";
|
|
@@ -26190,6 +27493,9 @@ export {
|
|
|
26190
27493
|
FirefoxDisconnectedError,
|
|
26191
27494
|
args,
|
|
26192
27495
|
getFirefox,
|
|
27496
|
+
getFirefoxIfRunning,
|
|
26193
27497
|
isDisconnectionError,
|
|
26194
|
-
|
|
27498
|
+
isFirefoxRunning,
|
|
27499
|
+
resetFirefox,
|
|
27500
|
+
setNextLaunchOptions
|
|
26195
27501
|
};
|