Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Browser MCP servers carry inherent risks. A few key practices:

- **Use a dedicated Firefox profile.** Never run the server against your regular profile — the agent has access to whatever the browser can reach, including cookies and saved sessions.
- **Be cautious about which sites you visit.** Pages can return content designed to manipulate the agent (prompt injection). Stick to sites you control or trust.
- **Enable only the tool modules you need.** The default `basic` preset already includes `evaluate_script`; `--tool-preset slim` drops it. Higher presets such as `--tool-preset developer` (debugging, network, console, profiler) and `--tool-preset mozilla` (privileged context) expand what the agent can do further.
- **Enable only the tool modules you need.** The default `basic` preset already includes `evaluate_script`; `--tool-preset slim` drops it. Higher presets such as `--tool-preset developer` (debugging, network, console, profiler) expand what the agent can do further. The `mozilla` preset also selects privileged tools, which require separate consent through `--allow-system-access`.

See [SECURITY.md](SECURITY.md) for a full breakdown of risks and how to report vulnerabilities.

Expand Down Expand Up @@ -132,10 +132,11 @@ You can pass flags or environment variables (names on the right):
- `--connect-existing` — attach to an already-running Firefox instead of launching a new one (`CONNECT_EXISTING=true`)
- `--marionette-port` — Marionette port for connect-existing mode, default 2828 (`MARIONETTE_PORT`)
- `--pref name=value` — set Firefox preference at startup via `moz:firefoxOptions` (repeatable)
- `--allow-system-access` — explicitly allow privileged Firefox access. This permits privileged modules to be exposed when selected and launches Firefox with the required system access. It does not select any tools by itself.
- `--tool-preset` — select which tool modules to enable: `slim`, `basic` (default), `developer`, `mozilla`, or `all`. See [Tool modules and presets](#tool-modules-and-presets). (`TOOL_PRESET`)
- `--tools` — explicit list of tool modules to enable, overriding `--tool-preset` entirely (e.g. `--tools pages network script`). See [Tool modules and presets](#tool-modules-and-presets).
- `--enable-script` — _deprecated, use `--tool-preset developer` or `--tools ... script debugging`._ Selects the `developer` tool preset. (`ENABLE_SCRIPT=true`)
- `--enable-privileged-context` — _deprecated, use `--tool-preset mozilla` or `--tools ... privileged prefs`._ Selects the `mozilla` tool preset. Requires `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1` (`ENABLE_PRIVILEGED_CONTEXT=true`)
- `--enable-privileged-context` — _deprecated, use `--tool-preset mozilla` or `--tools ... privileged prefs`._ Selects the `mozilla` tool preset. Requires `--allow-system-access`. (`ENABLE_PRIVILEGED_CONTEXT=true`)
- `--android-device` — enable Firefox for Android mode; value is the ADB device serial (e.g. `emulator-5554`). Run `adb devices` to list connected devices. Omit the value or use `auto` to select the single connected device automatically.
- `--android-wipe-app-data` — confirm that Android mode wipes all data of the target app. Required together with `--android-device`. (`ANDROID_WIPE_APP_DATA=true`)
- `--android-package` — Android app package name, default `org.mozilla.firefox`. Other packages: `org.mozilla.firefox_beta` for Firefox Beta, `org.mozilla.fenix` for Firefox Nightly, `org.mozilla.fenix.debug` for Firefox Nightly Debug, `org.mozilla.geckoview_example` for geckoview (`ANDROID_PACKAGE`)
Expand Down Expand Up @@ -171,11 +172,24 @@ npx @mozilla/firefox-devtools-mcp --tool-preset developer

# Enable only the modules you need
npx @mozilla/firefox-devtools-mcp --tools pages network console

# Explicitly allow and select privileged tools
npx @mozilla/firefox-devtools-mcp@latest --tool-preset mozilla --allow-system-access
```

The `prefs` and `privileged` modules require `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1` and are only
available in the Mozilla-internal build. The public package skips them even if requested and
logs a warning naming the modules it dropped.
The `prefs` and `privileged` modules require `--allow-system-access`. The flag grants permission
and launches Firefox with the required environment, while `--tool-preset mozilla` or `--tools`
selects which modules to expose. Without the flag, privileged modules are skipped even if selected.
Passing `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS` through `--env` or `restart_firefox` does not grant access.

For Codex:

```bash
codex mcp add firefox-devtools -- \
npx -y @mozilla/firefox-devtools-mcp@latest \
--tool-preset mozilla \
--allow-system-access
```

### Useful preferences (`--pref`)

Expand Down Expand Up @@ -242,10 +256,10 @@ Both flags are required because the MCP uses both WebDriver Classic (`--marionet
- Console: list/clear (list supports optional `saveTo`)
- Screenshot: page/by uid (with optional `saveTo` for CLI environments)
- Script: evaluate_script (optional `sandbox` for an isolated realm; optional `saveTo` for bulky results)
- Privileged Context: list/select privileged ("chrome") contexts, evaluate_privileged_script (requires `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`)
- WebExtension: install_extension, uninstall_extension, list_extensions (list requires `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`)
- Privileged Context: list/select privileged ("chrome") contexts, evaluate_privileged_script (requires `--allow-system-access`)
- WebExtension: install_extension, uninstall_extension, list_extensions (list requires `--allow-system-access`)
- Firefox Management: get_firefox_info, get_firefox_output, restart_firefox
- Firefox Preferences: get_firefox_prefs, set_firefox_prefs (requires `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`)
- Firefox Preferences: get_firefox_prefs, set_firefox_prefs (requires `--allow-system-access`)
- Profiler: profiler_is_active, profiler_start (preset or explicit config), profiler_stop (saves profile to downloads directory)
- Screencast: screencast_start (records the page viewport to a video file in the downloads directory), screencast_stop (requires Firefox 154+)
- Utilities: accept/dismiss dialog, history back/forward, set viewport
Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ Lets the agent execute arbitrary JavaScript in any page context. If the agent is

### Privileged context tools (modules `privileged` and `prefs`)

Tools that operate in Firefox's privileged (chrome) context: listing and selecting privileged contexts, evaluating privileged scripts, reading and writing Firefox preferences, and listing extensions. These tools require the `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1` environment variable to be set, which is checked by the WebDriver implementation in Firefox to allow using any command that targets privileged contexts. They are only available in the Mozilla-internal build; the public package skips them even if requested.
Tools that operate in Firefox's privileged (chrome) context include listing and selecting privileged contexts, evaluating privileged scripts, reading and writing Firefox preferences, and listing extensions. They are exposed only when the MCP starts with `--allow-system-access` and the user also selects the `mozilla` preset or the privileged modules explicitly. The flag launches Firefox with the system access required by WebDriver. Neither `--env` nor later tool calls can substitute for this startup consent.

Unless you are developing or modifying Firefox itself, you likely do not need these modules. To set preferences at startup, you can always use the `--pref name=value` command-line argument instead. If you are missing commands or features to debug web content, please file a bug on [Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?format=__default__&blocked=2026717&product=Developer%20Infrastructure&component=Firefox%20MCP) or reach out in the [#firefox-devtools-mcp Matrix room](https://chat.mozilla.org/#/room/#firefox-devtools-mcp:mozilla.org).

> **Warning:** When the privileged modules are used together with `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`, the agent gains access to privileged Firefox APIs with no web-content sandbox boundary. Depending on what the agent does with that access, this can extend to operating-system–level actions. Only use this combination in fully isolated environments.
> **Warning:** When privileged modules are used with `--allow-system-access`, the agent gains access to privileged Firefox APIs with no web-content sandbox boundary. Depending on what the agent does with that access, this can extend to operating-system–level actions. Only use this combination in fully isolated environments.

The deprecated `--enable-script` and `--enable-privileged-context` flags select the `developer` and `mozilla` presets respectively. They still work, but `--tool-preset` and `--tools` describe what is actually enabled.
The deprecated `--enable-script` and `--enable-privileged-context` flags select the `developer` and `mozilla` presets respectively. They do not grant system access. `--tool-preset` and `--tools` describe what is selected, while `--allow-system-access` is the separate consent step for privileged modules.

## Risky Flags

Expand Down
10 changes: 8 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ export const cliOptions = {
description:
'Path to a file where MCP server logs will be written. Set DEBUG=* to also enable verbose debug logs.',
},
allowSystemAccess: {
type: 'boolean',
description:
'Allow privileged Firefox access. Use with --tool-preset mozilla or an explicit privileged tool selection.',
default: false,
},
tools: {
type: 'array',
string: true,
Expand All @@ -205,7 +211,7 @@ export const cliOptions = {
type: 'string',
description:
`Preset selecting which tool modules to enable: ${PRESET_NAMES.join(' < ')}. ` +
'Ignored when --tools is given. Privileged modules (mozilla/all) require MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1.',
'Ignored when --tools is given. Privileged modules (mozilla/all) require --allow-system-access.',
default: process.env.TOOL_PRESET || 'basic',
},
enableScript: {
Expand All @@ -217,7 +223,7 @@ export const cliOptions = {
enablePrivilegedContext: {
type: 'boolean',
description:
'Deprecated: use --tools/--tool-preset. Enable privileged context tools and Firefox prefs tools. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1.',
'Deprecated: use --tools/--tool-preset. Enable privileged context tools and Firefox prefs tools. Requires --allow-system-access.',
default: (process.env.ENABLE_PRIVILEGED_CONTEXT ?? 'false') === 'true',
},
unrestrictedSavePaths: {
Expand Down
7 changes: 4 additions & 3 deletions src/index.moz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ if (process.env.NODE_ENV !== 'production') {
}
}

// Moz entry point: accepts all CLI arguments including --enable-privileged-context.
// The public entry point (index.public.ts) strips that flag to prevent privileged tool exposure.
// Moz entry point: accepts all CLI arguments including the deprecated
// --enable-privileged-context selector. Privileged access still requires
// --allow-system-access.
import { parseArguments } from './cli.js';
import { run } from './index.js';

export { FirefoxDevTools } from './firefox/index.js';
export { FirefoxDisconnectedError, isDisconnectionError } from './utils/errors.js';

run(parseArguments, import.meta.url, true).catch((error) => {
run(parseArguments, import.meta.url).catch((error) => {
console.error('Fatal error in main', error);
process.exit(1);
});
7 changes: 3 additions & 4 deletions src/index.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ if (process.env.NODE_ENV !== 'production') {
}
}

// Public entry point: --enable-privileged-context is excluded from the CLI so
// privileged tools can never be enabled in the public package.
// The moz entry point (index.moz.ts) accepts all CLI arguments.
// Public entry point: the deprecated --enable-privileged-context selector is
// excluded from the CLI. Privileged access requires --allow-system-access.
import { parseArguments } from './cli.js';
import { run } from './index.js';

export { FirefoxDevTools } from './firefox/index.js';
export { FirefoxDisconnectedError, isDisconnectionError } from './utils/errors.js';

run((v) => parseArguments(v, process.argv, false), import.meta.url, false).catch((error) => {
run((v) => parseArguments(v, process.argv, false), import.meta.url).catch((error) => {
console.error('Fatal error in main', error);
process.exit(1);
});
15 changes: 8 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FirefoxDevTools } from './firefox/index.js';
import type { FirefoxLaunchOptions } from './firefox/types.js';
import { buildToolset } from './tools/registry.js';
import { errorResponse } from './utils/response-helpers.js';
import { applySystemAccessPolicy, clearSystemAccessEnvironment } from './system-access.js';

type Args = ReturnType<typeof parseArguments>;

Expand All @@ -29,7 +30,7 @@ if (!major || major < 20) {
}

// Set by run() before the server starts; initialized to satisfy the type checker.
export let args = {} as Args;
export let args = {} as Readonly<Args>;

// Global context (lazy initialized on first tool call)
let firefox: FirefoxDevTools | null = null;
Expand Down Expand Up @@ -135,6 +136,7 @@ export async function getFirefox(): Promise<FirefoxDevTools> {
};
}

options = applySystemAccessPolicy(options, args.allowSystemAccess === true);
firefox = new FirefoxDevTools(options);
try {
await firefox.connect();
Expand All @@ -156,10 +158,7 @@ export async function getFirefox(): Promise<FirefoxDevTools> {

export async function run(
parseArgsFn: (version: string) => Args,
importMetaUrl: string,
// Fail closed: privileged tools must be opted into explicitly (index.moz.ts
// passes true). A caller that omits this never exposes privileged modules.
allowPrivileged = false
importMetaUrl: string
): Promise<void> {
// Only run if this entry file is executed directly (not imported as a library).
// We need to normalize both paths to handle different execution contexts (npx, node, etc.)
Expand All @@ -177,7 +176,9 @@ export async function run(
return;
}

args = parseArgsFn(SERVER_VERSION);
args = Object.freeze(parseArgsFn(SERVER_VERSION));
const allowSystemAccess = args.allowSystemAccess === true;
clearSystemAccessEnvironment(process.env);

if (args.logFile) {
setupLogFile(args.logFile);
Expand All @@ -196,7 +197,7 @@ export async function run(
preset: args.toolPreset,
enableScript: Boolean(args.enableScript),
enablePrivilegedContext: Boolean(args.enablePrivilegedContext),
allowPrivileged,
allowPrivileged: allowSystemAccess,
});
for (const warning of warnings) {
log(warning);
Expand Down
35 changes: 35 additions & 0 deletions src/system-access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { FirefoxLaunchOptions } from './firefox/types.js';

export const SYSTEM_ACCESS_ENV = 'MOZ_REMOTE_ALLOW_SYSTEM_ACCESS';

export function isSystemAccessEnvironmentVariable(name: string): boolean {
return name.toUpperCase() === SYSTEM_ACCESS_ENV;
}

export function applySystemAccessPolicy(
options: FirefoxLaunchOptions,
allowSystemAccess: boolean
): FirefoxLaunchOptions {
const env = Object.fromEntries(
Object.entries(options.env ?? {}).filter(([name]) => !isSystemAccessEnvironmentVariable(name))
);

if (allowSystemAccess) {
env[SYSTEM_ACCESS_ENV] = '1';
}

return {
...options,
env: Object.keys(env).length > 0 ? env : undefined,
};
}

export function clearSystemAccessEnvironment(
environment: Record<string, string | undefined>
): void {
for (const name of Object.keys(environment)) {
if (isSystemAccessEnvironmentVariable(name)) {
delete environment[name];
}
}
}
10 changes: 8 additions & 2 deletions src/tools/firefox-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { readFileSync, existsSync, statSync } from 'node:fs';
import { errorResponse, successResponse } from '../utils/response-helpers.js';
import { isSystemAccessEnvironmentVariable, SYSTEM_ACCESS_ENV } from '../system-access.js';
import { defineModule, defineToolHandler, type ToolDefinition } from './module.js';

// ============================================================================
Expand Down Expand Up @@ -210,7 +211,7 @@ export const restartFirefoxTool = {
type: 'string',
},
description:
'New environment variables in KEY=VALUE format (optional, e.g., ["MOZ_LOG=HTMLMediaElement:5", "MOZ_LOG_FILE=/tmp/ff.log"])',
'New environment variables in KEY=VALUE format (optional, e.g., ["MOZ_LOG=HTMLMediaElement:5", "MOZ_LOG_FILE=/tmp/ff.log"]). System access is controlled only by the MCP startup flag.',
},
headless: {
type: 'boolean',
Expand All @@ -224,7 +225,7 @@ export const restartFirefoxTool = {
prefs: {
type: 'object',
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.',
'Firefox preferences to set at startup. Values are auto-typed: true/false become booleans, integers become numbers, everything else is a string. Requires --allow-system-access.',
additionalProperties: {
oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }],
},
Expand Down Expand Up @@ -260,6 +261,11 @@ export const handleRestartFirefox = defineToolHandler(async (input: unknown) =>
for (const envStr of env) {
const [key, ...valueParts] = envStr.split('=');
if (key && valueParts.length > 0) {
if (isSystemAccessEnvironmentVariable(key)) {
throw new Error(
`${SYSTEM_ACCESS_ENV} is controlled by --allow-system-access and cannot be changed by restart_firefox`
);
}
newEnv[key] = valueParts.join('=');
}
}
Expand Down
Loading