crw-browse: Browser Automation MCP Server
crw-browse is a separate MCP server (since v0.4.0) that drives a real Chrome-family browser over the Chrome DevTools Protocol (CDP) for stateful, multi-step interaction. It complements crw-mcp's one-shot scraping tools with an interactive session that persists state across tool calls.
When to use crw-browse
crw_scrape (from crw-mcp) is a one-shot tool: navigate, render, return content, done. That covers the vast majority of agent web tasks. Use crw-browse when you need something crw_scrape cannot do:
| Situation | crw_scrape | crw-browse |
|---|---|---|
| Read article / extract text | Yes | Unnecessary |
| JS-gated SPA that returns empty on HTTP request | renderJs: true usually works |
Use when renderJs still returns empty |
| Multi-step flow (login → fill form → submit) | No — one-shot only | Yes |
| Click a button and observe the result | No | Yes |
| Read the DOM or accessibility tree after interaction | No | Yes |
| Maintain session state (cookies, auth) across requests | No | Yes |
| Take a screenshot of the current state | No | Yes (requires Chrome; see below) |
The short rule: if your agent needs to interact (click, type, fill) or read the DOM after interaction, use crw-browse. If it only needs to read a URL — even a JS-heavy one — try crw_scrape with renderJs: true first.
How it differs from crw-mcp
| crw-mcp | crw-browse | |
|---|---|---|
| Purpose | Bulk scrape / crawl / search | Stateful browser interaction |
| Session | Stateless (each call independent) | Persistent session across calls |
| State | None carried between calls | Cookies, auth, DOM state, scroll position all persist |
| Tools | crw_scrape, crw_crawl, crw_map, crw_search, crw_parse_file, crw_check_crawl_status |
goto, tree, click, fill, type_text, evaluate, text, html, wait, console, network, storage, screenshot, script |
| Browser required? | Optional (HTTP-only mode works without CDP) | Required (must have Chrome or Lightpanda running separately) |
| Cloud option | fastcrw.com | Self-hosted only |
| Availability | All modes (embedded + proxy + HTTP) | Self-hosted binary only |
Architecture
crw-browse builds on crw-renderer's persistent CdpConnection primitive — the same CDP machinery the scrape pipeline uses — so it inherits tested session management, event routing, and browser-pool code.
Agent (MCP client)
→ stdin JSON-RPC 2.0
→ crw-browse process
→ CDP WebSocket (ws://localhost:9222)
→ Chrome / Lightpanda
A SessionRegistry holds named BrowserSession objects keyed by UUID, with 4-char base62 short_id tokens returned to the agent. Session state is automatically swept after an idle TTL.
Prerequisites: start a CDP browser first
crw-browse does not launch a browser. You must start one yourself and point crw-browse at its CDP endpoint.
# Chrome/Chromium — macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--headless=new \
--user-data-dir=/tmp/crw-chrome
# Chrome/Chromium — Linux
google-chrome \
--remote-debugging-port=9222 \
--headless=new \
--user-data-dir=/tmp/crw-chrome
# Lightpanda (faster and lighter — no screenshots)
lightpanda serve --host 127.0.0.1 --port 9222
Install
# From crates.io
cargo install crw-browse
# Or build from the workspace
cargo build -p crw-browse --release
# Binary: target/release/crw-browse
Prebuilt binaries are attached to the v0.4.0 GitHub release and later.
Start crw-browse
# Default: CDP at ws://localhost:9222
crw-browse
# Explicit endpoint
crw-browse --ws-url ws://localhost:9222
# Lightpanda as interactive browser + separate Chrome for screenshots
crw-browse \
--ws-url ws://lightpanda:9222 \
--chrome-ws-url ws://chrome:9223
Environment variables
| Variable | Default | Meaning |
|---|---|---|
CRW_BROWSE_WS_URL |
ws://localhost:9222 |
CDP WebSocket endpoint for interactive tools |
CRW_BROWSE_PAGE_TIMEOUT_MS |
30000 |
Per-page load timeout (ms) |
CRW_BROWSE_CHROME_WS_URL |
unset | Chrome-only fallback for screenshot; when unset crw-browse reuses CRW_BROWSE_WS_URL |
CRW_BROWSE_SCREENSHOT_DIR |
unset | Directory for screenshot disk output; inline base64 when unset |
RUST_LOG |
unset | Tracing filter, e.g. crw_browse=debug |
--ws-url vs --chrome-ws-url
--ws-urlbacks every interactive tool (goto,tree,click,type_text,fill,wait, …). It accepts Chrome or Lightpanda. Lightpanda is faster and lighter — use it when you do not need screenshots.--chrome-ws-urlis a screenshot-only fallback that must point at real Chrome/Chromium. Lightpanda'sPage.captureScreenshotreturns a stub, so screenshot calls are routed to a separate Chrome connection when one is configured.
Three common setups:
| Setup | --ws-url |
--chrome-ws-url |
Tradeoff |
|---|---|---|---|
| Lightpanda + Chrome screenshots | ws://lightpanda:9222 |
ws://chrome:9223 |
Fast interactive, working screenshots |
| Chrome only (simplest) | ws://chrome:9222 |
ws://chrome:9222 |
Single browser, screenshots work |
| Lightpanda only (no screenshots) | ws://lightpanda:9222 |
(unset) | Lightest; screenshot returns NOT_IMPLEMENTED |
Both flags may point at the same Chrome instance — there is no harm in doing so.
Wire into your MCP client
crw-browse uses stdio transport, same as crw-mcp. The JSON config is the same shape:
Claude Code
claude mcp add crw-browse -- crw-browse --ws-url ws://localhost:9222
Or via config file (~/.claude/mcp.json / .mcp.json):
{
"mcpServers": {
"crw-browse": {
"command": "crw-browse",
"args": ["--ws-url", "ws://localhost:9222"]
}
}
}
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"crw-browse": {
"command": "crw-browse",
"args": ["--ws-url", "ws://localhost:9222"]
}
}
}
Available tools
| Tool | Description |
|---|---|
goto |
Navigate to an http/https URL and wait for Page.loadEventFired. Creates a default session on first call. Returns session token, URL, HTTP status, and elapsed_ms. |
tree |
Snapshot the current page as an indented accessibility tree. Each line is @e<N> role: name; @e<N> ref tokens are accepted by interaction tools until the next tree call. |
click |
Click an element by CSS selector or @e<N> ref from tree. Dispatches a synthetic click event so framework handlers (React, Vue) fire. |
fill |
Set an input's value and dispatch input + change events. Pass selector or ref. |
type_text |
Type characters into the focused element via Input.dispatchKeyEvent. Use click first to focus. |
wait |
Block until a CSS selector is visible/present, or until load/networkidle. |
evaluate |
Run a JavaScript expression on the current page (Runtime.evaluate, awaitPromise: true). |
text |
Read visible text: document.body.innerText or a specific selector. Capped at 50 KB. |
html |
Read raw HTML: full outerHTML or a specific selector. Capped at 200 KB. |
console |
Drain the session's console-message ring buffer (up to 200 entries). Filter by level; set clear: true to wipe. |
network |
Drain the session's network-event ring buffer (up to 500 entries). Filter by all/failed/requests/responses. |
storage |
Read or write browser storage: action ∈ {get, set, clear}, kind ∈ {cookie, local, session}. |
screenshot |
Capture the page as PNG/JPEG. Requires --chrome-ws-url (or Chrome at --ws-url). Pass path to write to disk or omit for inline base64. |
script |
Run up to 50 tool calls in one request; steps execute sequentially, first error aborts remaining. |
gotoonly acceptshttpandhttpsschemes. Attempts to navigate tofile://,javascript:,data:, or internal network addresses are rejected withINVALID_ARGS.
Basic example: login flow
A complete agent flow navigating a login page:
// 1. Navigate to login page
{"method": "tools/call", "params": {"name": "goto", "arguments": {"url": "https://example.com/login"}}}
// → {"ok": true, "session": "a3f1", "url": "https://example.com/login", "data": {"status": 200}}
// 2. Snapshot accessibility tree to find input refs
{"method": "tools/call", "params": {"name": "tree", "arguments": {"max_nodes": 50}}}
// → data.tree = "[1] WebArea: Login\n [2] textbox: Email\n [3] textbox: Password\n [4] button: Sign in\n..."
// 3. Fill the email field by CSS selector
{"method": "tools/call", "params": {"name": "fill", "arguments": {"selector": "input[type=email]", "value": "user@example.com"}}}
// 4. Fill the password field
{"method": "tools/call", "params": {"name": "fill", "arguments": {"selector": "input[type=password]", "value": "secret"}}}
// 5. Click Sign in
{"method": "tools/call", "params": {"name": "click", "arguments": {"selector": "button[type=submit]"}}}
// 6. Wait for the dashboard to appear
{"method": "tools/call", "params": {"name": "wait", "arguments": {"selector": "#dashboard", "condition": "visible"}}}
// 7. Read the authenticated content
{"method": "tools/call", "params": {"name": "text", "arguments": {}}}
The same flow using script in a single round-trip:
{
"method": "tools/call",
"params": {
"name": "script",
"arguments": {
"actions": [
{"act": "goto", "url": "https://example.com/login"},
{"act": "fill", "selector": "input[type=email]", "value": "user@example.com"},
{"act": "fill", "selector": "input[type=password]", "value": "secret"},
{"act": "click", "selector": "button[type=submit]"},
{"act": "wait", "selector": "#dashboard", "condition": "visible"},
{"act": "text"}
]
}
}
}
Verify installation
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"test"},"protocolVersion":"2024-11-05"}}' \
| crw-browse 2>/dev/null
Expected:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "crw-browse", "version": "<current>"},
"instructions": "Interactive browser automation over CDP. Call `goto` to navigate, then `tree` to inspect the rendered accessibility tree."
}
}
Current limitations
- Single default session per process. If two MCP clients connect to the same
crw-browseprocess, they share browser state. Multi-session isolation (viasession.new/session.close) is on the roadmap. - No cloud option.
crw-browseis self-hosted only — you manage the browser process. - Screenshots require Chrome. Lightpanda returns a stub from
Page.captureScreenshot;crw-browseroutes screenshot calls to a separate Chrome connection (--chrome-ws-url) when configured.
See also
- MCP Server —
crw-mcpand its 6 read-only scraping tools - MCP Client Setup — host-by-host config for Claude Code, Cursor, Windsurf, Cline, Continue
- JS Rendering — when
crw_scrapewithrenderJs: trueis enough