chromewright 0.8.0

Browser automation MCP server via Chrome DevTools Protocol (CDP)
Documentation

chromewright

Crates.io Version Build Status License

Chromewright is a local-first browser automation MCP server built on Chrome DevTools Protocol (CDP). It exposes a real Chrome or Chromium browser to MCP clients over stdio or loopback streamable HTTP, with high-level tools for navigation, page reading, tab management, screenshots, viewport emulation, and bounded interaction. The default build also includes a semantic terminal browser and a co-hosted loopback MCP companion for its live document state.

Use Chromewright when an agent needs browser state from a real browser without embedding a Node.js automation stack or writing raw CDP calls. Chromewright is not an end-to-end test runner; it is a browser control layer for AI agents and MCP clients.

When to use Chromewright

  • Attach an MCP client to an existing Chrome or Chromium profile on a DevTools endpoint.
  • Launch a dedicated local browser session for agent work.
  • Read pages through snapshots, markdown extraction, targeted inspection, and link inventory.
  • Browse a page's semantic DOM locally in the terminal, with a co-hosted MCP companion for its active document and selection state.
  • Drive bounded interactions such as click, input, select, hover, key press, scroll, and wait.
  • Capture managed PNG screenshots without letting callers choose arbitrary output paths.
  • Reuse revision-scoped cursor handles from snapshots instead of relying only on CSS selectors.

Installation

Chromewright requires Rust 1.88 or newer when you install or build it with Cargo.

Install from crates.io:

cargo install chromewright

Install with Homebrew:

brew install bnomei/chromewright/chromewright

Install from source:

git clone https://github.com/bnomei/chromewright.git
cd chromewright
cargo install --path .

You can also download prebuilt archives from GitHub Releases and place the chromewright binary on your PATH.

Verify the binary is available:

chromewright --version

Expected output:

chromewright <version>

Quickstart

This path starts a visible Chrome profile with DevTools enabled, then serves Chromewright over loopback HTTP at http://127.0.0.1:3000/mcp.

Prerequisites

  • chromewright on your PATH
  • Chrome, Chromium, or another CDP-compatible browser
  • An MCP client that supports streamable HTTP or stdio servers

1. Start a dedicated browser profile

On macOS, run:

open -na "Google Chrome" --args \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.chromewright-agent-profile"

Use a dedicated profile when you do not want agent automation attached to your personal browser session. The default Chromewright attach mode expects DevTools at http://127.0.0.1:9222.

2. Start Chromewright

Run the streamable HTTP server:

chromewright serve

Expected log line:

Ready to accept MCP connections at http://127.0.0.1:3000/mcp

3. Connect an MCP client

For JSON-configured clients that support streamable HTTP:

{
  "mcpServers": {
    "chromewright": {
      "transport": "streamable_http",
      "url": "http://127.0.0.1:3000/mcp"
    }
  }
}

For Codex over stdio, let the client start the server:

[mcp_servers.chromewright]
command = "/absolute/path/to/chromewright"
enabled = true

For Codex against the long-lived HTTP server from step 2:

[mcp_servers.chromewright]
url = "http://127.0.0.1:3000/mcp"
enabled = true

4. Verify with the client

Use your MCP client to call tab_list. A connected session should return at least one tab with a stable tab_id. If no active tab is useful, call new_tab before calling snapshot.

Browser modes

Chromewright has two browser modes:

Mode How to start What it does
Attach Run chromewright or chromewright serve with no launch flags. Connects to http://127.0.0.1:9222 by default.
Attach to another endpoint Pass --ws-endpoint <URL>. Connects to a browser WebSocket URL or a DevTools HTTP origin such as http://127.0.0.1:9333.
Launch Pass any launch flag such as --user-data-dir, --headless, --executable-path, or --debug-port. Starts a local browser session. Launch mode is headed unless you pass --headless.

Examples:

# Default: attach to http://127.0.0.1:9222 and serve MCP over stdio.
chromewright

# Serve streamable HTTP on the default loopback endpoint.
chromewright serve

# Serve streamable HTTP on a custom port and path.
chromewright serve --port 3333 --http-path /browser

# Attach to a different DevTools endpoint.
chromewright --ws-endpoint http://127.0.0.1:9333

# Launch a visible browser with a dedicated profile.
chromewright --user-data-dir /tmp/chromewright-profile

# Launch a headless browser and serve streamable HTTP.
chromewright --headless --user-data-dir /tmp/chromewright-profile serve

# Seed two managed tabs at startup. This works with attach, launch, serve, and tui modes.
chromewright --url https://example.com --url https://example.org serve

--url is repeatable and seeds one separate managed tab for every value, in the order supplied; the final seeded tab becomes active. It accepts absolute http:, https:, and about: URLs. Relative, unsafe, and protocol-relative URLs are rejected before any startup tab is opened. In attach mode, existing tabs are untouched; only seeded tabs become managed by chromewright.

CLI reference

Option or command Default Description
chromewright stdio transport Starts the MCP server over stdio.
chromewright serve 127.0.0.1:3000/mcp Starts the MCP server over loopback streamable HTTP.
serve --port <PORT>, serve -p <PORT> 3000 Sets the HTTP port.
serve --http-path <PATH> /mcp Sets the HTTP endpoint path.
--ws-endpoint <URL> http://127.0.0.1:9222 when no launch flags are present Connects to an existing browser WebSocket URL or DevTools HTTP origin. This conflicts with launch flags.
--headless false Launches a new browser in headless mode.
--executable-path <PATH> auto-detected by the browser backend Uses a specific browser executable in launch mode.
--user-data-dir <DIR> backend default Uses a persistent browser profile directory in launch mode.
--debug-port <PORT> auto-selected Uses a specific DevTools port for a locally launched browser.
--url <URL> none; repeatable Seeds each supplied safe URL in its own managed startup tab. The final seeded tab is active.
chromewright tui (feature tui, default-on) Starts the semantic terminal browser against the same browser session. Always co-hosts a loopback MCP companion.
tui --config <PATH> $XDG_CONFIG_HOME/chromewright/tui.toml or ~/.config/chromewright/tui.toml TOML keymap plus optional [theme] and [layout] overlays. An explicit path must exist and parse; a missing default file keeps built-in bindings.
tui --companion-port <PORT> 0 (ephemeral) Loopback port for the co-hosted streamable-HTTP MCP companion.
tui --companion-path <PATH> /mcp HTTP path for the co-hosted companion.
--browser-session <reuse|restart> reuse with --headless tui Reuse or replace Chromewright's owned managed headless browser. Only valid with --headless tui.

Source: src/bin/mcp_server.rs.

Terminal browser (TUI)

The default build includes a semantic terminal browser. It attaches to the same Chromewright browser session and renders semantic DOM content only (not pixels, CSS, or browser layout).

# Attach to an existing DevTools endpoint (default http://127.0.0.1:9222).
chromewright tui

# Managed private headless Chrome for the normal terminal-browser flow.
chromewright --headless tui

To connect an MCP client to the co-hosted companion, choose a fixed loopback port; the default 0 selects an ephemeral port that the TUI does not print.

chromewright --headless tui --companion-port 3334
[mcp_servers.chromewright_tui]
url = "http://127.0.0.1:3334/mcp"
enabled = true

The companion exposes eight tui_* coordination tools and bounded semantic resources. semantic_ref values are tied to one document revision; the active capture plus the previous eight revisions are retained, while stale or evicted references fail closed. Attention messages are limited to 512 characters. Semantic Markdown resources paginate at 32,000 characters by default and cap at 200,000; JSON resources fail instead of returning a truncated document.

The header is a single browser-like bar: tab ordinal (2/5) left of the history arrows, then location/title, with a lifecycle glyph on the right. Keyboard bindings are not shown in the terminal chrome. Defaults are Vimari-compatible. Multi-key sequences such as gg and gi wait for the full chord; an unbound prefix is rejected rather than re-firing the last key.

Default keymap

Browser-first (Vimari) defaults, with md-tui-style aliases where they do not collide. Overlaying an action replaces all of its sequences (primary + aliases).

Key Action name Behavior
f / s link_hints_follow Enter hint mode over viewport-visible links and form controls; type the two-key label. Links follow in the current tab; text inputs start edit mode; other controls are selected. One label per target (first line only if wrapped). (s = md-tui select-link.)
F / S link_hints_new_tab Same targets as f; links open in a new tab (hint mode stays open for chaining until Esc). Form targets still select/edit in the current tab. (S = md-tui select-link alt.)
j / scroll_down Scroll or move selection down one block.
k / scroll_up Scroll or move selection up one block.
h scroll_left Horizontal scroll left when content overflows. (Not md-tui half-page; browser tables/code need pan.)
l scroll_right Horizontal scroll right when content overflows.
u / half_page_up Pan the view half a page up (selection unchanged).
d / half_page_down Pan the view half a page down (selection unchanged).
Ctrl-u page_select_up Move selection up by about half a page.
Ctrl-d page_select_down Move selection down by about half a page.
gg go_top Jump to the document top. (Single g is not bound so gi stays available.)
G go_bottom Jump to the document bottom.
gi focus_first_input Focus the first form control and start editing.
H / b history_back Browser history back. (b = md-tui back.)
L history_forward Browser history forward.
r reload Reload the active page.
[ prev_tab Switch to the previous browser tab.
] next_tab Switch to the next browser tab.
x close_tab Close the current tab.
t new_tab Open a new tab.
o open_url Open the URL entry prompt with an empty buffer. While editing, Tab / Shift-Tab accept or cycle local URL history (ghost suffix shows the best match).
O edit_url Open the URL entry prompt prefilled with the current address (edit from the end). Same Tab history completion as o.
/ search Start forward search by exact semantic content. (md-tui also binds f to search; we keep f for link hints.)
n search_next Repeat the last search forward.
N search_previous Repeat the last search backward.
Space collapse Collapse or expand the selected block.
zw toggle_wrap Toggle soft word-wrap (on by default). When on, long lines wrap to the viewport and h/l horizontal pan is disabled.
zs toggle_structure Toggle prose (default) vs structure projection. Prose hides landmark/list/group chrome and flattens indent; structure shows DOM-like containers. Collapse (Space) only works in structure mode.
i inspect Open a compact inspect panel under the selected block; title is the full DOM path (main > … > tag#id), body has action fields and ref/rev; follows selection until Esc.
y copy_block Copy selection: link/image URL (resolved), otherwise rendered block text (OSC 52).
Y copy_ref Copy the opaque semantic_ref (OSC 52).
Tab tab_next Next focusable control; writes the leaving field to the live DOM + same-doc patch, then moves focus.
Shift-Tab tab_prev Previous focusable control; same write-on-leave behavior as Tab.
Enter confirm Text input: apply value to the live DOM + same-doc patch (no submit needed; live search works). Checkbox/radio: toggle. Select: cycle options. Submit button: write staged fields + click + recapture. Link/other: activate.
Esc escape Leave prompt, hint, or inspect mode. In Normal mode, also clears sticky /search footer (/{query} n/m). After a failed page action, dismiss Error back to Ready (retained page stays) so keys work again.
w toggle_full_width Toggle full-width content vs the configured content_max_width column (default 100, capped by default).
e edit_external Open the current page’s semantic markdown in $VISUAL, then $EDITOR, then vi (read-only; TUI suspends like Nereid).
q / Ctrl-c quit Quit the TUI.

Hints use deterministic two-key labels from the alphabet asdfgqwertzxcvb (for example aa, as), assigned to viewport-visible links and form controls (one label per target; painted on the first line only when a target wraps).

After navigation or a link follow settles, a URL fragment such as #section moves the TUI selection to the matching component (id, then named anchor), expands collapsed ancestors, and scrolls it into view. Unmatched fragments keep the prior selection.

The content pane uses a terminal-native ANSI-16 role palette (clearer heading ladder H1–H6, blue links, light-cyan forms, yellow hints) with reverse-video selection applied last. Colors inherit the terminal light/dark theme; override individual roles under [theme] in tui.toml. By default the markdown/content area has 1 column of left/right padding and is capped at 100 columns (centered; header and footer stay full width); press w for full width. A one-column Amp-style block scrollbar sits on the far right of the content band. Override layout under [layout] in tui.toml.

Default reading mode is prose (markdown-like): no ▾ [main] / ol / group chrome, fully flat lines. Press zs for structure (DOM-like outline). Wrap (zw) and structure (zs) are not shown in the header bar; toggle feedback appears in the status line.

Search follows Vim semantics: a new /pattern starts after the current selection and wraps at the end; n repeats forward, N repeats backward, and submitting an empty / prompt repeats the previous pattern. The footer shows the cmdline while typing (/…) and keeps /{pattern} n/m while a search is active — press Esc in Normal mode to clear it (Esc while typing /… only cancels the prompt and keeps the prior pattern for n/N). Link hints (f / F) also use the footer (f as) rather than the header. Bracketed paste is accepted only in URL, search, and form input modes and is bounded to 4096 characters.

Custom keymap

Bindings are replaceable by action name. --config PATH takes precedence; if omitted, Chromewright reads $XDG_CONFIG_HOME/chromewright/tui.toml, falling back to ~/.config/chromewright/tui.toml. A missing default file keeps the built-ins; an explicitly requested file must parse successfully.

# Only list keys you want to change. Unknown names or conflicting
# bindings abort startup rather than partially applying the overlay.
[keymap]
reload = "ctrl-r"
quit = "ctrl-q"
tab_prev = "shift-tab"

# Optional content-pane padding + column width (header/footer stay full width).
# Defaults: 1 col L/R, 0 row T/B, content_max_width = 100 (0 = always full).
# Press w to toggle full width vs the capped column.
[layout]
# content_padding_x = 1
# content_padding_y = 0
# content_max_width = 100

[theme]
# Optional ANSI names, reset, or #rrggbb — defaults already use a clear ladder.
# link = "blue"
# h1 = "lightblue"
# h2 = "green"
# h3 = "magenta"
# h4 = "cyan"
# h5 = "yellow"
# h6 = "lightred"
# form_control = "lightcyan"
# hint_label = "yellow"

Binding specs accept single keys (r, space, esc, enter, tab), multi-key letter sequences (gg, gi), and chords with -, +, or space separators (ctrl-c, C-c, shift-tab). Supported named keys include esc, enter, tab, backtab / shift-tab, backspace, arrow keys, home, end, pageup / pgup, pagedown / pgdn, space, and function keys such as f1.

Theme roles: link, h1h6, landmark, group, list, image, form_control, hint_label, muted, chrome_ready, chrome_loading, chrome_error, chrome_mode, attention_fg, attention_bg.

Layout keys: content_padding_x, content_padding_y (symmetric inset around the markdown/content pane only), content_max_width (default 100; 0 disables the cap; w toggles full vs capped).

The source of truth for defaults is src/tui/keymap.rs and src/tui/action.rs.

Tool workflow

A typical agent workflow is:

  1. Call tab_list or new_tab to establish an active tab.
  2. Call snapshot to read the current page and collect actionable nodes.
  3. Prefer a fresh cursor from snapshot or inspect_node when targeting follow-up actions.
  4. Use inspect_node, get_markdown, extract, or read_links for more focused reads.
  5. Use click, input, select, hover, press_key, scroll, wait, or tab tools for bounded interaction.
  6. Call snapshot again after navigation, DOM-changing actions, viewport changes, or ambiguous target recovery.

snapshot supports these modes:

Mode Use it when
viewport You want the default local reread of the current visible scope.
delta You want the changed local surface when a compatible prior snapshot base exists.
full You need an exhaustive page-wide read.

DOM-targeted tools accept a public target object:

{
  "target": {
    "kind": "selector",
    "selector": "h1"
  }
}

or:

{
  "target": {
    "kind": "cursor",
    "cursor": "<cursor from snapshot or inspect_node>"
  }
}

Selector strings are still accepted for compatibility by tools that use the public target type, but the object form is the canonical contract.

Production MCP tool surface

Production MCP sessions register the default high-level tools plus the guarded operator tool evaluate.

Category Tools
Navigation navigate, go_back, go_forward, wait
Interaction and viewport click, input, select, hover, press_key, scroll, set_viewport
Tabs and lifecycle new_tab, tab_list, switch_tab, close_tab, close
Reading and inspection snapshot, inspect_node, get_markdown, extract, read_links
Managed artifacts screenshot
Operator diagnostics evaluate

evaluate executes JavaScript in the active page and requires confirm_unsafe = true on each call. It is available for diagnostics and escape-hatch inspection when bounded tools cannot answer a page-specific question.

Source: src/tools/core/mod.rs and src/browser/session.rs.

Screenshots and viewport emulation

Use screenshot when a caller needs a managed PNG artifact. The tool accepts:

  • mode: viewport, full_page, element, or region
  • scale: device or css
  • optional tab_id
  • optional target for element captures
  • optional region for region captures

Successful calls return managed artifact metadata, including artifact_uri, artifact_path, mime_type, byte_count, image dimensions, CSS dimensions, device pixel ratio, pixel scale, revealed_from_offscreen, and optional clip data. Callers do not provide output paths.

Use set_viewport to emulate responsive breakpoints through CDP. Successful calls return viewport_metrics_after; later snapshot calls expose the live metrics under scope.viewport.

Safety boundaries

  • Attach mode can see the tabs, cookies, and authenticated state in the browser profile you connect to.
  • Use a dedicated browser profile for agent work when you do not want automation attached to a personal browser session.
  • evaluate requires confirm_unsafe = true because it runs arbitrary JavaScript in the active page.
  • navigate and new_tab allow http:, https:, about:, and same-origin relative paths by default. data:, file:, other absolute schemes, and protocol-relative URLs require allow_unsafe = true.
  • go_back and go_forward apply the same unsafe-scheme gate and revert rejected history moves.
  • close_tab requires confirm_destructive = true before closing an unmanaged active tab in a connected session.
  • close requires confirm_destructive = true before expanding connected-session cleanup from managed tabs to all tabs.
  • cursor and node_ref targets are revision-scoped. After navigation or DOM-changing actions, refresh with snapshot.

Operation metrics

Finished tool results include operation_metrics metadata when a tool records non-zero metrics. operation_metrics.output_bytes is optional; it appears only when a tool path measures the exact serialized output size.

Measured paths may include:

  • browser evaluation count
  • poll iterations
  • DOM extraction count and extraction time
  • last DOM node count
  • snapshot render time
  • handoff rebuild count and time
  • exact serialized output size when measured

Run the focused operation metrics tests:

cargo test --locked --all-features operation_metrics

Local development

Build from source:

cargo build

Run the normal test suite:

cargo test

Run the browser smoke suite from the repository root:

scripts/browser-smoke.sh

The smoke script runs:

cargo test --test browser_smoke -- --nocapture

Browser smoke checks launch a local browser and are intended for maintainer workstations. CI covers formatting, clippy, MSRV, cargo check, tests, and packaging without requiring a live browser attach target.

Source anchors

License

MIT