Chromewright
Browser automation via Chrome DevTools Protocol
chromewright
Chromewright is a local-first browser automation MCP server built on Chrome DevTools Protocol (CDP). It can attach to an existing Chrome or Chromium session or launch its own browser, then expose a bounded, agent-oriented tool surface for navigation, reading, tab management, and interaction without a Node.js runtime.
It is built for the moment when an MCP client needs a real browser, but not an unbounded automation stack. Under the hood, Chromewright combines CDP session control, revision-scoped DOM extraction, cursor-based targeting, and consistent tool-result metadata. It is not a general-purpose end-to-end test runner. It is a browser control layer for AI agents.
What To Use Chromewright For
Use Chromewright when you need browser-aware automation with a stable high-level surface instead of handwritten CDP calls.
- attaching to a running Chrome or Chromium session or launching a disposable browser
- exposing a real browser to MCP clients over streamable HTTP or stdio
- reading pages through
snapshot,inspect_node,get_markdown,extract, andread_links - driving bounded interactions through
navigate,click,input,select,hover,press_key,scroll,wait, and the tab tools - targeting follow-up actions with revision-scoped
cursorornode_refhandles instead of relying only on fragile selectors
Installation
Cargo
Install the binary from crates.io:
cargo install chromewright
Building from source or installing with Cargo requires Rust 1.88 or newer.
Homebrew
brew install bnomei/chromewright/chromewright
GitHub Releases
Download a prebuilt archive or source package from GitHub Releases, extract it, and place chromewright on your PATH.
From source
The published package and binary are named chromewright. The repository is currently hosted as browser-use-rs.
git clone https://github.com/bnomei/browser-use-rs.git
cd browser-use-rs
cargo install --path .
If you only want a local release build:
cargo build --release
Quickstart
1) Prepare Chrome or Chromium
The default attach mode expects a browser exposing DevTools on http://127.0.0.1:9222.
Recommended macOS launch command for a dedicated visible Chrome profile:
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 browsing session. If you prefer Chromewright to launch its own browser instead, skip this and pass any launch-mode flag in the next step. Launch mode is headed by default; add --headless only when you want a hidden browser.
2) Start Chromewright
cargo run --bin chromewright
This default mode attaches to Chrome on 127.0.0.1:9222 and serves MCP over stdio.
Other common startup modes:
# release build, same defaults
cargo run --release --bin chromewright
# serve streamable HTTP on 127.0.0.1:3000/mcp
cargo run --bin chromewright -- serve
# connect to a different DevTools endpoint
cargo run --bin chromewright -- \
--ws-endpoint http://127.0.0.1:9333
# launch a new visible browser instead of attaching to an existing one
cargo run --bin chromewright -- \
--user-data-dir /tmp/chromewright-profile
# launch a new visible browser and serve streamable HTTP
cargo run --bin chromewright -- \
--user-data-dir /tmp/chromewright-profile serve
# launch headless instead of headed
cargo run --bin chromewright -- \
--headless --user-data-dir /tmp/chromewright-profile
3) Add Chromewright to your MCP client
Codex
Recommended stdio configuration:
[mcp_servers.chromewright]
command = "/absolute/path/to/chromewright"
enabled = true
If you want a long-lived loopback HTTP service instead, start chromewright serve separately and point Codex at the running endpoint:
[mcp_servers.chromewright]
url = "http://127.0.0.1:3000/mcp"
enabled = true
If you need a non-default attach target, add --ws-endpoint explicitly. If you want Chromewright to launch its own browser from the client command, add a launch-mode flag such as --user-data-dir /tmp/chromewright-profile, and add --headless only when you do not want a visible browser.
Other JSON-configured clients
{
"mcpServers": {
"chromewright": {
"transport": "streamable_http",
"url": "http://127.0.0.1:3000/mcp"
}
}
}
The exact file name and field names vary by client. The important part is that the client connects to a running Chromewright service at that URL.
How Chromewright Uses Your Browser
- attach mode connects to an existing Chrome or Chromium session and can see the tabs, cookies, and authenticated state already present in that profile
- launch mode starts a dedicated browser session and tracks the tabs created under that session
- in attach mode,
closedefaults to session-managed cleanup andclose_tabrequiresconfirm_destructive = truebefore closing an unmanaged active tab - most high-level tools read and interact through CDP only;
screenshotis the bounded exception and stores a managed PNG artifact for the caller screenshotis part of the default surface and usesmode, optionaltab_id, optionaltarget, andregioninstead of caller-chosenpathorconfirm_unsafe
Use Cases
Standard MCP browser automation
Once Chromewright is running, the normal workflow is:
- Use
new_tabortab_listto establish an active tab. On a fresh session with no active tab, do not callsnapshotfirst. - Use
snapshotto get document metadata plus actionable nodes.mode = "viewport"is the default local reread,mode = "delta"reuses the prior session base when available, andmode = "full"keeps the exhaustive escape hatch. Inline[index=...]markers only appear for nodes that still expose a public follow-up handle in that returned scope. - Use
inspect_nodefor targeted bounded reads, including selector-based inspection of non-actionable nodes such as headings, images, and overlays. Prefercursorwhen one is available; stale cursors may selector-rebound, and a successful inspection may still legitimately returncursor = nullwith a selector-onlytarget. - Use
screenshotwhen you need a managed PNG artifact.mode = "viewport"is the default,mode = "full_page"captures the whole page,mode = "element"requirestarget, andmode = "region"requiresregion.scale = "device"preserves raw device pixels by default, whilescale = "css"normalizes output dimensions to CSS pixels. Passtab_idwhen the capture should target a specific tab without activating it first. - Use
set_viewportwhen you need responsive breakpoint simulation on the active tab or a specifictab_id; successful calls return canonicalviewport_metrics_after, and latersnapshotcalls surface the live metrics again atscope.viewport. - Use
click,input,select,hover,press_key,scroll,wait, or the tab tools withcursorpreferred for follow-up targeting inside a page and stabletab_idpreferred for multi-tab flows. - Refresh
snapshotafter revision-changing actions.cursorandnode_refare revision-scoped, so rereads are the normal recovery path.
Workflow Conventions
- Fresh sessions: use
new_tabortab_listbeforesnapshotif you do not already have an active tab. - Revision-scoped targets:
cursorandnode_refbelong to a specific document revision. After navigation or DOM-changing actions, rerunsnapshot; stalecursorreplay may selector-rebound, but treat rebound as a signal to reread before more precise chained work. - Snapshot modes: default
viewportis the fast local reread,deltareports the changed local surface when a compatible prior base exists and falls back toviewportwhen it does not, andfullkeeps the exhaustive page-wide tree for deep inspection or regression work. - Viewport locality:
viewportanddeltanow demote unchanged sticky/fixed header or footer chrome when stronger local anchors are present. If persistent chrome still wins because nothing stronger exists,scope.locality_fallback_reasonexplains that fallback. - Snapshot inline handles: rendered
[index=...]markers follow the same revision scope as the exposed actionablenodesand only advertise follow-up-capable nodes in that returned scope; use them as reread-local hints, not as durable cross-revision IDs. target_status = same: the tool still proved the same target, even if the post-action handle downgraded to selector-only because actionability disappeared.target_status = rebound: the tool recovered after a revision change;target_aftermay downgrade to selector-only when the same element still exists but no longer has a verified actionable handle, so reread withsnapshotbefore more precise chained work.target_status = detached: the old target no longer exists, often after navigation; reacquire state from the new page before continuing.target_status = unknown: post-action identity stayed ambiguous, usually because multiple matches remained or the selector could not prove the same element.- Attach-mode recovery: if a connected session returns
code = attach_page_target_lost, usetab_listto confirm inventory,switch_tabto reacquire an active page target, and reconnect the session if DOM-backed tools still fail. - Attach-mode safety: use a disposable browser profile for debugging and treat destructive tab tools as explicit actions, especially on connected sessions.
Chromewright also carries a few small but important contract details:
- DOM-targeted tools take one public
targetobject:{ "kind": "selector", "selector": "..." }or{ "kind": "cursor", "cursor": ... }. - Canonical target examples:
inspect_node:{ "target": { "kind": "selector", "selector": "h1" } }click:{ "target": { "kind": "cursor", "cursor": <snapshot cursor> } }
screenshotis part of the default surface and usesmodeplus optionalscaleinstead of legacyfull_page = true; successful calls return managed artifact metadata includingartifact_uri,artifact_path,mime_type,byte_count, image dimensions, CSS dimensions, DPR metadata,revealed_from_offscreen, and optionalclip.set_viewportis part of the default surface and uses CDP emulation instead of resizing the OS window; width and height must be positive bounded CSS pixels,device_scale_factormust be greater than zero,orientationuses snake_case values such asportrait_primaryandlandscape_primary, andreset = trueonly acceptstab_id. Read the live scoped metrics back fromviewport_metrics_afterorsnapshot.scope.viewport;viewport_afterremains a compatibility alias.switch_tabaccepts stabletab_idonly on the public MCP surface.- Structured tool-local failures use one top-level family:
code,error, optionaldocument, optionaltarget, optionalrecovery, and optionaldetails. extractusescode = element_not_foundfor selector misses and reservescode = invalid_extract_payloadfor malformed extraction results.read_linksreturns both the rawhrefattribute and an absoluteresolved_url.
Default Tool Surface
The default Chromewright MCP server exposes 22 high-level 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
The default surface intentionally excludes the raw-JavaScript operator tool evaluate.
High-level action tools return compact follow-up metadata by default. Use snapshot when you need the scoped YAML snapshot plus actionable-node list, with viewport as the default, delta for session-local changes, and full for exhaustive rereads. For targeted reads, use snapshot to choose a node and reuse its cursor, then call inspect_node; when you need to inspect a non-actionable DOM node such as a heading or image, inspect_node also accepts selector-based reads with an optional cursor, and stale cursor replay may selector-rebound before the final target settles. After revision-changing actions, rerun snapshot before more precise target reuse. Public DOM follow-up calls should use target.kind = "cursor" whenever a fresh cursor is available and fall back to target.kind = "selector" when only selector continuity remains.
Use set_viewport before snapshot when you want the DOM reread scoped to a simulated breakpoint. Successful set_viewport responses include viewport_metrics_after, and snapshot rereads expose the same live metrics under scope.viewport without widening unrelated tool outputs. scroll reports canonical scroll position under scroll_after; legacy viewport_after aliases remain for compatibility.
Use screenshot when you need a bounded visual artifact from the browser. The public contract is mode plus optional scale, tab_id, target, and region; callers do not provide path, full_page, or confirm_unsafe. Successful results include artifact_uri, artifact_path, mime_type, byte_count, width, height, css_width, css_height, device_pixel_ratio, pixel_scale, revealed_from_offscreen, and optional clip.
Read-oriented tools are intentionally distinct: get_markdown is the broad reading tool, extract is for targeted text or HTML, and read_links is for link inventory and planning. For multi-tab work, prefer stable tab_id handles from tab_list, new_tab, switch_tab, and close_tab instead of relying only on tab indices.
Operation Metrics
Finished tool results include operation_metrics metadata. Every finished result includes serialized output size, and measured hot paths add the relevant counters below:
- browser evaluation count
- poll iterations
- DOM extraction count and extraction time
- snapshot render time
- handoff rebuild count and time
- serialized output size
The lightweight validation surface for these metrics is in the normal test suite:
cargo test --locked --all-features operation_metrics
Safety And Boundaries
- Chromewright drives a real Chrome or Chromium instance through CDP. In attach mode, it sees the tabs, cookies, and authenticated state of the browser profile you give it.
- Use a dedicated browser profile for agent work when you do not want automation attached to your personal session.
- The default tool surface excludes raw JavaScript evaluation;
screenshotremains part of the bounded default surface and returns managed artifact metadata. screenshotdoes not accept caller-chosen output paths orconfirm_unsafe; usemode = "full_page"instead of a legacyfull_page = trueflag, and usescale = "css"only when you want CSS-pixel-normalized output instead of raw device pixels.navigateandnew_tabreject unsafe schemes such asdata:andfile:unless the caller passesallow_unsafe = true.cursorandnode_reftargets are revision-scoped. After a DOM-changing action, stalecursorreplay may selector-rebound, but precise follow-up work should still be refreshed from a newsnapshot.
License
MIT
Máy chủ liên quan
Alpha Vantage MCP Server
nhà tài trợAccess financial market data: realtime & historical stock, ETF, options, forex, crypto, commodities, fundamentals, technical indicators, & more
MCP DevTools
A development tools server for Git management, file operations, AI-assisted editing, and terminal execution, integrable with AI assistants and code editors.
ServeMyAPI
A personal server for securely storing and accessing API keys using the macOS Keychain.
Unstructured API MCP Server
Interact with the Unstructured API to manage data sources, destinations, workflows, and jobs.
Claude Google Apps Script MCP Guide
Integrate Claude AI with Google Apps Script to automate tasks in Google Sheets and Gmail.
OpenZipline MCP
Provides secure, standards-compliant smart contract templates for tokens, access control, finance, and governance.
BlenderMCP
Connects Blender to AI models via MCP for prompt-assisted 3D modeling, scene creation, and manipulation.
MCP-Compose
Orchestration tool for managing multiple MCP servers with a Docker Compose-style interface and a unified HTTP proxy.
PHP MCP Server
Provides semantic PHP code analysis and refactoring tools, enabling AI assistants to perform safe, intelligent code transformations at the AST level.
BlenderMCP
Integrates with Blender to enable text and image-based 3D model editing using the Model Context Protocol.
drawdb-mcp
DrawDB + MCP server