browser-control 0.2.0

CLI that manages browsers and exposes them over CDP/BiDi for agent-driven development. Includes an optional MCP server.
Documentation
# ADR 001: Rust CLI rewrite

## Status

Accepted (2026-05-12).

## Context

The original project (`@anthropic-community/browser-coordinator-mcp`) was an
MCP-first TypeScript server. It bundled three concerns into a single Node
process: browser lifecycle management, a CDP reverse proxy for stable child-MCP
ports, and a grab-bag of in-browser helper tools (DOM, Markdown, screenshot,
fetch). In practice this caused several recurring problems:

- **Lifecycle thrash.** Agents called `coordinator_launch_browser` /
  `coordinator_stop_browser` / `coordinator_restart_browser` aggressively
  whenever a CDP call failed, killing the user's browser session.
- **One-process coupling.** A crash in any helper tool took down the proxy and
  every child MCP attached to it.
- **Two MCP entries in `.mcp.json`** (coordinator + Playwright) was awkward and
  the reverse-proxy state file was a frequent source of confusion.
- **Distribution.** npm-as-the-only-channel meant Node.js was always on the
  critical path, even for users whose only goal was "open a browser and let an
  agent drive it".
- **Multi-session coordination.** State was per-process and ephemeral, so two
  shells (or two agents) could not see each other's browsers.

## Decision

1. **Rewrite the tool in Rust as a CLI named `browser-control`.** A single
   statically-linkable binary, distributable without Node. The CLI is the
   primary surface; MCP is one of several ways to use it.

2. **Demote the MCP server to a `mcp` subcommand.** Hosts add
   `browser-control mcp` (or `browser-control mcp --playwright`) to their
   `.mcp.json` exactly like any other stdio server. No reverse proxy, no
   separate coordinator entry.

3. **Persist browser state in SQLite under the OS app-data dir.** A small
   registry of `(name, kind, pid, engine, endpoint, profile, started_at)` is
   shared across shells and agents, with lazy liveness checks on read. This
   replaces the in-memory and state-file approaches of the TS version.

4. **CLI does not stop or restart browsers.** Agents are trigger-happy and
   will kill the user's session at the first sign of trouble. Lifecycle is a
   user concern; the CLI only `start`s (idempotent by kind) and lists. Stale
   registry rows are pruned lazily, never via an explicit `stop` from an
   agent.

5. **Distribute via crates.io and a Homebrew tap.** `cargo install
   browser-control` for Rust users; `brew install
   rickardp/browser-control/browser-control` for everyone else. The legacy npm
   package is deprecated in place.

## Consequences

The MCP server becomes a much smaller piece of the project, which is a
deliberate downsizing: the tools that survived (`navigate`, `dom`,
`screenshot`, `fetch`, `select_element`) are the ones that genuinely benefit
from running inside the controller. Lifecycle tools are gone because they
caused more harm than good, and `get_markdown` is gone because we don't want
to port Turndown to Rust for v1.

Removing the CDP reverse proxy means child MCPs (like Playwright MCP) connect
directly to the browser's CDP endpoint. To make that ergonomic we added the
`mcp --playwright` stdio passthrough, which spawns `@playwright/mcp` with the
right `--cdp-endpoint` injected. Hosts see one entry in `.mcp.json`, not two.

Persistent SQLite state means two agents in two shells can coordinate without
file-watching tricks. It also means we own a small data file on disk; we mitigate
that with a strict schema, lazy cleanup, and no migrations across the 0.x line.

The hard cut from TypeScript to Rust is disruptive for existing users. We
preserve the old code on the `legacy-ts` branch (tagged `v0-final-ts`) and
deprecate the npm package, but there is no compatibility shim. The CLI is the
contract going forward; the `BROWSER_CONTROL` environment variable is the only
piece of cross-tool surface we commit to keeping stable through 1.0.

The VS Code extension is retained but heavily simplified: the Unix-socket IPC
is gone, replaced by injecting `BROWSER_CONTROL` into the extension-launched
terminal. This eliminates an entire class of platform-specific bugs.