browser-control
browser-control is a Rust CLI that manages browser processes and exposes them
over CDP (Chromium) or WebDriver BiDi (Firefox) for agent-driven development.
It keeps a small persistent registry of the browsers it has started so multiple
agents, shells, or editor sessions can coordinate on the same browser. An
optional MCP server is available as a subcommand.
Install
Homebrew (recommended) — tap this repo, then install:
The formula is rendered into Formula/browser-control.rb by CI on every release, so the tap URL above is all you need.
From crates.io:
Prebuilt binaries for macOS (x86_64/aarch64), Linux (x86_64/aarch64) and Windows (x86_64) are attached to every GitHub Release.
Requires Rust 1.80 or newer when building from source. Node.js (for npx) is
only required if you intend to use mcp --playwright.
Usage
The CLI exposes four subcommands. All of them accept --json (where listed
below) for machine-readable output.
list-installed
Detect every supported browser installed on this machine.
Supported kinds: chrome, edge, chromium, brave (CDP), and firefox
(BiDi).
list-running
List the browsers currently registered and alive. Stale entries (dead PIDs or unreachable endpoints) are pruned lazily before printing.
Columns: NAME, KIND, PID, ENGINE, ENDPOINT, PROFILE, STARTED.
--json adds engine-specific endpoint details for tooling integration:
cdp_port and cdp_ws_url for CDP browsers, bidi_ws_url for Firefox.
Stale rows are re-probed before the WS URLs are printed; fields are omitted
when the probe fails.
start [BROWSER]
Start a browser and register it. Idempotent by kind: if a browser of the requested kind is already alive, it is reused.
BROWSER may be a kind (chrome, edge, chromium, brave, firefox) or a
friendly instance name printed by a previous start (e.g. firefox-pikachu).
When omitted, the first available Chromium-based browser is used.
start blocks until the browser's debugging endpoint is reachable (up to
--wait-timeout seconds, default 30) so the next command in a chain can
attach immediately. Pass --no-wait to return as soon as the process is
spawned.
start always uses a stable per-kind profile directory under the OS app-data
dir (macOS: ~/Library/Application Support/browser-control/profiles/<kind>/default/;
Linux: ~/.config/browser-control/profiles/<kind>/default/;
Windows: %APPDATA%\browser-control\profiles\<kind>\default\), so subsequent
starts of the same kind reuse the same browser state across reboots. This is
intentional: it avoids re-authenticating in every new browser session.
mcp [BROWSER] [--playwright]
Start an MCP server on stdio that targets a running browser.
Browser resolution order:
- The positional
BROWSERargument (orBROWSER_CONTROLenv, merged by clap; the argument wins when both are present) - The persisted default from
browser-control set default <value> - The most-recently-started running browser in the registry
- Otherwise, exit with an error suggesting
browser-control start
With --playwright, the CLI spawns the official @playwright/mcp via npx,
hands it the resolved CDP endpoint, and forwards stdio bidirectionally. The
host sees only Playwright MCP's tools; browser-control's own MCP tools are
not exposed in that mode.
set | get | unset <KEY> [VALUE]
Manage persistent settings. The only key today is default, which selects the
browser used by mcp when no positional argument and no BROWSER_CONTROL env
var is present. Values accept the full BROWSER_CONTROL grammar (URL / kind /
friendly name / absolute path) and are validated at set-time.
The setting is stored as TOML at:
- macOS:
~/Library/Application Support/browser-control/config.toml - Linux:
~/.config/browser-control/config.toml - Windows:
%APPDATA%\browser-control\config.toml
Override the directory with BROWSER_CONTROL_CONFIG_DIR.
The BROWSER_CONTROL environment variable
A single environment variable selects which browser the current shell session should talk to. The syntax of the value decides how it is interpreted:
| Value form | Behavior |
|---|---|
http(s)://… or ws(s)://… URL |
External CDP/BiDi endpoint. Used as-is; not registered and not managed by browser-control. |
Friendly name (e.g. firefox-pikachu) |
Exact match against the registry. |
Kind (chrome, firefox, …) |
First running instance of that kind in the registry. |
| Absolute path to a browser executable | Matched against list-installed to derive the kind, then resolved as a kind. |
Engine (CDP vs BiDi) is auto-detected for URL forms by probing.
HTTP, cookies, and storage
A small set of session subcommands lets agents and shell scripts use a real
browser session — with its cookies, headers, TLS stack, ad-blockers, and geo —
without scraping cookies.sqlite, re-implementing OAuth flows, or driving a
second headless browser. They attach to a browser already registered by
start, work over both CDP and BiDi (Firefox), and accept the same
BROWSER_CONTROL resolution rules as every other subcommand. None of them
launch a browser; run start first.
targets
List open page targets (and optionally filter by URL regex).
cookies
Export cookies from the live browser, normalised across CDP and BiDi.
--domain and --name are unanchored Rust regexes. Without --reveal,
values printed to a TTY are redacted; file output via -o always contains
full values and is chmod 0600 on Unix. --format netscape produces a file
byte-compatible with the Mozilla cookies.txt format (see
docs/session-ops.md).
fetch
Run an HTTP request from inside the page's JavaScript context. Cookies,
Origin, CORS, and the browser's TLS stack apply — handy for hitting an API
that requires the user's session.
-i prepends status line + response headers (like curl -i). -o FILE
writes the body to FILE (0600 on Unix). --target URLREGEX picks which page
to evaluate in when several are open.
storage
Read and write localStorage (default) or sessionStorage (--namespace session). Storage is origin-scoped, so most uses want --target.
eval
Evaluate a JavaScript expression in the active page. Returns the result as
plain text by default; --json emits the full evaluation envelope.
--await-promise is on by default, so async expressions just work.
wait
Block until the browser's CDP / BiDi endpoint is up. Useful right after
start in scripts.
&&
wait-for-cookie
Block until a cookie matching --domain REGEX --name REGEX exists in the
browser. Optional --validate-url URL follows up with a fetch() from the
page and requires a 2xx response before exiting — the typical pattern for
"wait until the user has finished logging in".
Exit status is 0 on match, non-zero on timeout.
Migrating from hand-rolled helpers
A typical "launch browser, wait for login, call API" shell flow collapses to:
SESSION_JSON=
And any Python write_netscape_cookie_jar() helper that reads
cookies.sqlite directly is replaced by:
# then: curl --cookie cookies.txt https://… or yt-dlp --cookies cookies.txt …
MCP integration
browser-control is itself an MCP server when invoked as mcp. Add it to
your host's .mcp.json like any other stdio server.
Default tools (exposed by the Rust server) include navigate, get_dom,
screenshot, fetch, select_element, plus the session ops introduced in
this release: list_targets, cookies, storage_get, storage_set, and
wait_for_cookie. See docs/session-ops.md for the
underlying model.
With --playwright passthrough (Playwright MCP's tool surface, but driving
the browser that browser-control manages):
You can scope a single host invocation to a specific browser by setting
BROWSER_CONTROL:
Architecture
browser-control is a thin CLI in front of a SQLite registry of browser
processes. The CLI starts and tracks browsers; agents talk to those browsers
directly over CDP or BiDi. The MCP server is just another way to reach the
same registry.
┌───────────────────────────────────────┐
│ SQLite registry (OS app-data dir) │
└───────────────────────────────────────┘
▲
│ read / write
│
user ──► browser-control start ─┴─► spawns ──► Browser (Chrome/Edge/Firefox/…)
▲
│ CDP / BiDi
│
MCP host ──► browser-control mcp [--playwright] ┘
(resolves browser via registry / BROWSER_CONTROL)
The CLI does not stop or restart browsers; the user owns lifecycle. Stale registry entries are pruned lazily on read.
Status
Pre-1.0. The CLI surface and the BROWSER_CONTROL environment variable are
the intended stable contracts; everything else may shift.
The previous TypeScript MCP server (@anthropic-community/browser-coordinator-mcp)
is preserved on the legacy-ts branch and tagged v0-final-ts. Its npm
package is deprecated.
License
MIT. See LICENSE.