voidcrawl-mcp is a stdio Model Context Protocol server that exposes VoidCrawl’s browser primitives as tools. Point Claude Code, Cursor, or any MCP-aware agent at it and the model can navigate, click, type, screenshot, and scrape through the same Rust core the Python SDK uses.
The server is written in Rust (part of the VoidCrawl workspace), ships as a single binary, and reuses the full BrowserPool, so tab recycling, stealth, and concurrent sessions all work automatically.
After install, voidcrawl-mcp --help prints the full CLI. By default the server launches its own headless Chrome pool using the same environment variables as PoolConfig.from_env. Two knobs worth knowing up front:
CHROME_HEADLESS=0 runs the pool headful — required to clear managed Cloudflare Turnstile and tough WAFs (headless is gated). See Captcha handling.
CHROME_WS_URLS=http://host:9222,... makes the server attach to an already-running Chromium farm over CDP instead of launching its own — e.g. a Docker container with a reproducible GPU/driver stack. Per-page stealth (UA/Client-Hints) still applies over the attached session.
Wire it into your AI host
voidcrawl-mcp is a single stdio binary; each host just needs a config entry that launches it. Let the binary wire every host for you, or do one host at a time.
One command for every host
voidcrawl-mcpinstall# wire Claude Code, Codex, and opencode at once
voidcrawl-mcpinstall--dry-run# print what it would change, write nothing
voidcrawl-mcpinstall--status# report where it's already wired
install uses each host’s own mcp add where one exists and writes the config file directly otherwise. If a host isn’t installed, it prints the exact block to paste in once you do. Use --scope project to write committed, in-repo config instead of your user config, and --tool claude|codex|opencode to wire just one.
Launch the host in that directory and the tools below are available immediately.
Tools
Stateless (recycled pool tabs)
Tool
Purpose
fetch / fetch_many
One URL, or many in parallel. The default when you need rendered HTML. fetch_many also returns a PoolMeta summary — max_tabs, how many requests queued, and the worst queue wait — so you can tell when you oversubscribed and cap the next batch at max_tabs.
fetch_snapshot
One URL, compact rendered-page snapshot: headings, text blocks, links, controls, forms, metadata, and truncation stats. Prefer this for first-pass page perception.
Click by accessibility role + accessible name, e.g. role="button", name="Load more". Durable across redesigns.
click_visual_coords
Click at (x, y) CSS pixels — the escape hatch for React pages where selector clicks are swallowed.
type_text
Type into a selector, or into the focused element.
eval_js
Evaluate a JavaScript expression; return the JSON result.
eval_js_in_frame
Evaluate JavaScript inside a specific (possibly cross-origin) iframe, selected by a substring of its URL — the way to reach a frame whose contentDocument is null from the parent.
wait_for_network_idle
Wait until no in-flight requests for the idle window (event-driven).
Captcha (non-default, external-solver pipelines only): capture_captcha, solve_captcha, inject_captcha_token exist but aren’t the normal path — VoidCrawl passes or surfaces captchas, it doesn’t solve them. See Captcha handling.
Challenge escalation: capture_challenge, mark_challenge_resolved, mark_challenge_failed, and wait_for_challenge_resolution hand an active wall to an operator or resolver without opening a new browser. See Challenge escalation.
Download (opt-in). Hidden unless the server runs with VOIDCRAWL_ALLOW_DOWNLOADS=1. Each tool fetches a file through stealth Chrome into a quarantine dir, runs it through the built-in antivirus gate, and moves it into output_dir only if it comes back clean.
Tool
Purpose
download
Download a file by URL and scan it. Use instead of fetch when you need the bytes, not rendered HTML. Returns {ok, verdict, path?, reason?, detected_mime, size}.
download_arm / download_wait
Capture a download started by a page action with no stable URL (e.g. Google Drive’s “Download”): arm the session, click the button, then wait.
A clean verdict means the file passed the size + content-type + bundled-signature checks, not that it is guaranteed malware-free. See the File Downloads guide for the quarantine model and the Python API.
When to reach for click_visual_coords
React and similar frameworks install event listeners that only fire on real trusted browser input. A CDP selector-based click calls HTMLElement.click(), which bypasses React’s synthetic event system, and nothing happens. click_visual_coords dispatches a real mousePressed / mouseReleased pair at the given coordinates, so the page sees it as a genuine user click.
Rule of thumb: if click succeeds but the page does not react, screenshot, eyeball the coords, and switch to click_visual_coords.
Navigating by accessibility role
session_ax_tree and click_by_role let an agent drive a page through its semantic structure rather than its markup. Call session_ax_tree to read the page as an indented role "name" outline, then click_by_role to act on a control by what it is:
This survives redesigns that rewrite class names and wrappers, which is where CSS selectors break. It is flakier when accessible names are ambiguous, localized, or duplicated, so the response includes named_count against node_count: a low ratio means a thin AX tree, and the agent should fall back to extract (CSS), a screenshot, or click_visual_coords. When several controls share a role and name, pass nth (zero-based) to pick one. The Accessibility Tree guide covers the model in full.
Profiles
By default the server launches fresh headless Chrome with ephemeral state. You have two persistent-state options:
Pinned native Chrome profile: the launcher grants one existing daily Chrome profile to the server with --profile or VOIDCRAWL_PROFILE.
VoidCrawl-managed profiles: the agent or operator creates standalone profiles under VOIDCRAWL_PROFILE_ROOT and leases them per session_open.
Pinning to a native Chrome profile
Point the server at an existing warm profile (logged into LinkedIn, Reddit, or similar) with either a flag or an env var:
voidcrawl-mcp--profile"Profile 1"
# or
VOIDCRAWL_PROFILE="Profile 1"voidcrawl-mcp
Managed profile registry
Managed profiles are VoidCrawl-owned Chrome user_data_dir roots. They live under VOIDCRAWL_PROFILE_ROOT, or the platform data-dir default such as ~/.local/share/voidcrawl/profiles on Linux. They are not subprofiles inside your daily Chrome directory.
Tool
Purpose
profile_create
Create a standalone managed profile.
profile_clone
Copy a managed profile id or explicit source path into a new managed profile.
The response includes the selected profile_id. Profile tools never return cookies, local storage, or saved passwords, but they can start browsers with those profiles, so only enable them for agents you trust with that managed root.
When a tool call returns CaptchaDetected or a captured challenge cannot be cleared, rotate a managed profile, go headful with CHROME_HEADLESS=0, swap proxy, or fail with evidence. VoidCrawl is the detector; your pipeline is the recovery mechanism.
Typed errors
Tool failures come back as MCP error responses with a structured data envelope so your agent middleware can dispatch cleanly:
{
"code": -32001,
"message": "captcha detected: recaptcha",
"data": {
"exception": "CaptchaDetected",
"kind": "recaptcha"
}
}
Known exception values: CaptchaDetected, ProfileBusy, ProfileLeaseExpired, ProfileNotFound, plus the standard VoidCrawlError for everything else. See Captcha handling for the rotation pattern.
Next steps
Skill file for Claude Code
Drop .claude/skills/voidcrawl.md into your project for recipes the model can reference: when to use wait_for_network_idle vs. wait_for, the click_visual_coords flow, and captcha hygiene. Shipped with the voidcrawl-mcp wheel.
FAQs
What is voidcrawl-mcp?
A stdio Model Context Protocol server that exposes VoidCrawl’s browser automation primitives (navigate, click, screenshot, eval_js, and more) as tools an AI agent can call. It reuses the same Rust core as the Python SDK, so tab recycling, stealth, and concurrent pooling all work automatically.
How do I install voidcrawl-mcp?
Pick one: uv tool install voidcrawl-mcp, pipx install voidcrawl-mcp, or cargo install --git https://github.com/CascadingLabs/VoidCrawl voidcrawl-mcp. After install, wire it into .mcp.json as a command entry.
Why does the MCP server expose click_visual_coords as a first-class tool?
React and similar frameworks install event listeners that only fire on real trusted browser input. A CDP selector-based click calls HTMLElement.click(), which bypasses React’s synthetic event system and silently does nothing. click_visual_coords dispatches a real mousePressed / mouseReleased pair at given (x, y) coordinates so the page sees a genuine user click.
Can an AI agent list or switch Chrome profiles at runtime?
MCP clients cannot enumerate or switch your daily Chrome profiles. A native Chrome profile is a live authenticated session (cookies, OAuth tokens, saved logins), so --profile "Profile 1" and VOIDCRAWL_PROFILE=... stay launcher decisions.
Agents can use VoidCrawl-managed profiles when you expose that managed root. Those profiles are standalone user_data_dir directories under VOIDCRAWL_PROFILE_ROOT; tools return metadata only, but a session launched with profile_id or profile_pool can use the profile’s authenticated state. Treat the managed root as an explicit trust boundary.
How does the MCP server report a captcha wall?
Every tool call that hits a captcha returns an MCP error with code -32001, a human-readable message, and a structured data envelope: { "exception": "CaptchaDetected", "kind": "recaptcha" }. Client middleware can dispatch on data.exception without string-matching the message.
Does voidcrawl-mcp work with Cursor or custom MCP clients?
Yes. It is a standard stdio MCP server. Any client that speaks the Model Context Protocol can launch it. Claude Code is the reference target because the recipes in the voidcrawl Skill file are tuned for it.
When should an agent use click_by_role instead of click?
When the page has clear accessibility semantics but volatile markup. A role plus accessible name like button "Load more" survives redesigns that rewrite class names and break CSS selectors. Call session_ax_tree first to see the available roles and names, and fall back to click (CSS) or click_visual_coords when a name is ambiguous or the AX tree is thin. See the Accessibility Tree guide.
What do node_count and named_count in session_ax_tree mean?
node_count is the total accessibility nodes the browser returned; named_count is how many carry a non-empty accessible name. A low named_count relative to node_count signals a thin AX tree, so an agent should fall back to HTML extract, a screenshot, or CSS selectors rather than rely on click_by_role.