Chromewright

Tự động hóa trình duyệt qua Chrome DevTools Protocol

Tài liệu

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:

ModeHow to startWhat it does
AttachRun chromewright or chromewright serve with no launch flags.Connects to http://127.0.0.1:9222 by default.
Attach to another endpointPass --ws-endpoint <URL>.Connects to a browser WebSocket URL or a DevTools HTTP origin such as http://127.0.0.1:9333.
LaunchPass 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 commandDefaultDescription
chromewrightstdio transportStarts the MCP server over stdio.
chromewright serve127.0.0.1:3000/mcpStarts the MCP server over loopback streamable HTTP.
serve --port <PORT>, serve -p <PORT>3000Sets the HTTP port.
serve --http-path <PATH>/mcpSets the HTTP endpoint path.
--ws-endpoint <URL>http://127.0.0.1:9222 when no launch flags are presentConnects to an existing browser WebSocket URL or DevTools HTTP origin. This conflicts with launch flags.
--headlessfalseLaunches a new browser in headless mode.
--executable-path <PATH>auto-detected by the browser backendUses a specific browser executable in launch mode.
--user-data-dir <DIR>backend defaultUses a persistent browser profile directory in launch mode.
--debug-port <PORT>auto-selectedUses a specific DevTools port for a locally launched browser.
--url <URL>none; repeatableSeeds 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.tomlTOML 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>/mcpHTTP path for the co-hosted companion.
--browser-session <reuse|restart>reuse with --headless tuiReuse 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).

KeyAction nameBehavior
f / slink_hints_followEnter 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 / Slink_hints_new_tabSame 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_downScroll or move selection down one block.
k / scroll_upScroll or move selection up one block.
hscroll_leftHorizontal scroll left when content overflows. (Not md-tui half-page; browser tables/code need pan.)
lscroll_rightHorizontal scroll right when content overflows.
u / half_page_upPan the view half a page up (selection unchanged).
d / half_page_downPan the view half a page down (selection unchanged).
Ctrl-upage_select_upMove selection up by about half a page.
Ctrl-dpage_select_downMove selection down by about half a page.
gggo_topJump to the document top. (Single g is not bound so gi stays available.)
Ggo_bottomJump to the document bottom.
gifocus_first_inputFocus the first form control and start editing.
H / bhistory_backBrowser history back. (b = md-tui back.)
Lhistory_forwardBrowser history forward.
rreloadReload the active page.
[prev_tabSwitch to the previous browser tab.
]next_tabSwitch to the next browser tab.
xclose_tabClose the current tab.
tnew_tabOpen a new tab.
oopen_urlOpen 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).
Oedit_urlOpen the URL entry prompt prefilled with the current address (edit from the end). Same Tab history completion as o.
/searchStart forward search by exact semantic content. (md-tui also binds f to search; we keep f for link hints.)
nsearch_nextRepeat the last search forward.
Nsearch_previousRepeat the last search backward.
SpacecollapseCollapse or expand the selected block.
zwtoggle_wrapToggle soft word-wrap (on by default). When on, long lines wrap to the viewport and h/l horizontal pan is disabled.
zstoggle_structureToggle 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.
iinspectOpen 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.
ycopy_blockCopy selection: link/image URL (resolved), otherwise rendered block text (OSC 52).
Ycopy_refCopy the opaque semantic_ref (OSC 52).
Tabtab_nextNext focusable control; writes the leaving field to the live DOM + same-doc patch, then moves focus.
Shift-Tabtab_prevPrevious focusable control; same write-on-leave behavior as Tab.
EnterconfirmText 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.
EscescapeLeave 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.
wtoggle_full_widthToggle full-width content vs the configured content_max_width column (default 100, capped by default).
eedit_externalOpen the current page’s semantic markdown in $VISUAL, then $EDITOR, then vi (read-only; TUI suspends like Nereid).
q / Ctrl-cquitQuit 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:

ModeUse it when
viewportYou want the default local reread of the current visible scope.
deltaYou want the changed local surface when a compatible prior snapshot base exists.
fullYou 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.

CategoryTools
Navigationnavigate, go_back, go_forward, wait
Interaction and viewportclick, input, select, hover, press_key, scroll, set_viewport
Tabs and lifecyclenew_tab, tab_list, switch_tab, close_tab, close
Reading and inspectionsnapshot, inspect_node, get_markdown, extract, read_links
Managed artifactsscreenshot
Operator diagnosticsevaluate

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