Browser MCP Tools
stygian-browser exposes browser automation tools including a runner-first acquisition path plus pool stats.
Enabling
[dependencies]
stygian-browser = { version = "*", features = ["mcp"] }
To run as a standalone MCP server:
cargo run --example mcp_server -p stygian-browser --features mcp
When using the aggregator, tools keep their browser_ prefix.
Session lifecycle
Browser sessions are identified by a session_id (ULID string). The lifecycle is:
browser_acquire → browser_navigate → browser_eval / browser_screenshot / browser_content → browser_release
Always call browser_release when done; unreleased sessions hold a browser process open
against the pool's max limit.
Runner-first alternative:
browser_acquire_and_extract
Use this tool when you want acquisition strategy escalation and extraction in one call.
The mode field accepts fast, resilient, hostile, and investigate.
Tools
browser_acquire
Acquire a browser session from the warm pool. Returns within ~100 ms for warm pools, ~2 s for cold launch.
| Parameter | Type | Required | Description |
|---|---|---|---|
stealth_level | string | none | basic | advanced (default: advanced) | |
tls_profile | string | TLS fingerprint profile name — e.g. chrome131, firefox133, safari18, edge131 | |
webrtc_policy | string | allow_all | disable_non_proxied | block_all (default from pool config) | |
cdp_fix_mode | string | CDP leak mitigation mode: addBinding | isolatedWorld | enableDisable | none | |
proxy | string | Proxy URL for this session — e.g. http://user:pass@proxy:8080 |
Returns:
{
"session_id": "01HV4...",
"requested_metadata": {
"stealth_level": "advanced",
"tls_profile": "chrome131"
}
}
browser_navigate
Navigate the browser to a URL and wait for the page to load.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID from browser_acquire |
url | string | ✓ | Target URL |
timeout_secs | integer | Navigation timeout in seconds (default: 30) |
Returns:
{
"title": "Example Domain",
"url": "https://example.com"
}
browser_eval
Evaluate arbitrary JavaScript in the page context and return the result.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
script | string | ✓ | JavaScript expression to evaluate |
Returns:
{ "result": 42 }
Example — extracting all links:
{
"script": "Array.from(document.querySelectorAll('a')).map(a => a.href)"
}
browser_screenshot
Capture a full-page screenshot as a base64-encoded PNG.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
Returns:
{ "data": "iVBORw0KGgoAAAANSUhEUgAA..." }
The returned data field is a standard base64 PNG suitable for embedding in an <img> tag or
writing directly to a .png file.
browser_content
Retrieve the current page's full outer HTML.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
Returns:
{ "html": "<!DOCTYPE html><html>...</html>" }
browser_attach (requires mcp-attach feature)
Attach an MCP session to an existing DevTools websocket endpoint (cdp_ws) or
use the extension bridge contract path (extension_bridge, currently not implemented).
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | string | ✓ | cdp_ws | extension_bridge |
endpoint | string | Required for cdp_ws; DevTools websocket URL | |
profile_hint | string | Optional label for external profile identity | |
target_profile | string | Optional tuning profile: default | reddit |
cdp_ws returns a new MCP session_id that can be used with the normal
browser lifecycle tools (browser_navigate, browser_eval, browser_content,
browser_screenshot, browser_release).
Example (cdp_ws):
{
"mode": "cdp_ws",
"endpoint": "ws://127.0.0.1:9222/devtools/browser/abcd1234",
"target_profile": "default"
}
browser_auth_session
High-level auth/session wrapper for common login workflows. This tool orchestrates
browser_session_save and browser_session_restore, with optional post-step
human-like interaction via browser_humanize.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
mode | string | ✓ | capture | resume |
file_path | string | Optional snapshot file path | |
ttl_secs | integer | Optional TTL in seconds when mode = capture | |
navigate_to_origin | boolean | When resuming, navigate to snapshot origin first (default: true) | |
interaction_level | string | Optional interaction pass: none | low | medium | high |
Capture example:
{
"session_id": "01HV4...",
"mode": "capture",
"file_path": "/tmp/reddit-session.json",
"ttl_secs": 86400,
"interaction_level": "none"
}
Resume example:
{
"session_id": "01HV5...",
"mode": "resume",
"file_path": "/tmp/reddit-session.json",
"navigate_to_origin": true
}
browser_session_save
Capture the current page auth/session state (cookies + localStorage) and store it in memory and optionally on disk.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
ttl_secs | integer | Optional snapshot TTL | |
file_path | string | Optional output path for snapshot JSON | |
include_snapshot | boolean | Include full snapshot payload in response (default: false) |
Returns:
{
"session_id": "01HV4...",
"origin": "https://example.com",
"cookie_count": 5,
"local_storage_keys": 3,
"ttl_secs": 3600,
"saved_to_file": "/tmp/session.json"
}
browser_session_restore
Restore session state from one of three sources: inline snapshot payload, snapshot file, or the in-memory snapshot saved previously for the same session.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
snapshot | object | Inline SessionSnapshot JSON | |
file_path | string | Path to snapshot JSON file | |
use_saved | boolean | Use in-memory saved snapshot if no inline/file source provided (default: true) | |
navigate_to_origin | boolean | Navigate to snapshot origin before applying state (default: true) |
Returns:
{
"session_id": "01HV4...",
"source": "saved",
"origin": "https://example.com",
"cookie_count": 5,
"local_storage_keys": 3,
"snapshot_expired": false
}
browser_humanize
Run a human-like interaction sequence on the current page (scroll, mouse, key activity) to reduce robotic behavior patterns.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
level | string | none | low | medium | high (default: low) | |
viewport_width | number | Viewport width used for interaction simulation (default: 1366) | |
viewport_height | number | Viewport height used for interaction simulation (default: 768) |
Returns:
{
"session_id": "01HV4...",
"level": "low",
"viewport_width": 1366,
"viewport_height": 768,
"applied": true
}
browser_query
Query all elements matching a CSS selector and return their text content (and optionally named attributes) as a structured list. Does not require deserialising the full page HTML.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID from browser_acquire |
url | string | ✓ | URL to navigate to before querying |
selector | string | ✓ | CSS selector — e.g. "article.post h2" |
fields | object | Map of { "name": "attr_name" } pairs — extra attribute values to include per node |
Returns:
[
{ "text": "Post title", "href": "https://example.com/post-1" },
{ "text": "Another title", "href": "https://example.com/post-2" }
]
When fields is omitted, each item contains only "text" (the element's textContent).
browser_extract
Extract structured records from a page using a root selector + per-field schema. Equivalent
to calling page.extract_all::<T>() with an inline schema definition.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
url | string | ✓ | URL to navigate to before extracting |
root_selector | string | ✓ | CSS selector for the repeating container element |
schema | object | ✓ | Map of field name to { selector, attr?, required? } field descriptor |
Schema field descriptor:
| Key | Type | Required | Description |
|---|---|---|---|
selector | string | ✓ | CSS selector scoped to the root element |
attr | string | If present, captures this attribute instead of textContent | |
required | boolean | true (default) — omit or set to false for optional fields |
Returns:
[
{
"title": "Example post",
"url": "https://example.com/post-1",
"author": "Alice",
"date": "2025-01-15"
}
]
Example call:
{
"session_id": "01HV4...",
"url": "https://news.example.com",
"root_selector": "article.story",
"schema": {
"title": { "selector": "h2" },
"url": { "selector": "h2 a", "attr": "href" },
"author": { "selector": "span.author" },
"date": { "selector": "time", "attr": "datetime", "required": false }
}
}
browser_extract_with_fallback
Structured extraction with multiple root selectors. Selectors are tried in order, and the first selector that yields results is used.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
url | string | ✓ | URL to navigate to before extracting |
root_selectors | array[string] | ✓ | Candidate root selectors in priority order |
schema | object | ✓ | Same schema object used by browser_extract |
timeout_secs | number | Navigation/extraction timeout (default: 30) |
Returns: same structured results array as browser_extract, plus the selected root selector.
browser_extract_resilient
Structured extraction mode that tolerates partial records by skipping invalid root nodes instead of failing the full extraction call.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
url | string | ✓ | URL to navigate to before extracting |
root_selector | string | ✓ | Root selector for repeated record containers |
schema | object | ✓ | Same schema object used by browser_extract |
timeout_secs | number | Navigation/extraction timeout (default: 30) |
Returns:
{
"url": "https://news.example.com",
"root_selector": "article.story",
"count": 42,
"skipped": 3,
"results": [
{ "title": "...", "url": "..." }
]
}
browser_find_similar
Find elements that are structurally similar to a reference fingerprint, even when class names or depth differ across page versions. Uses a weighted Jaccard similarity score (tag 40 %, classes 35 %, attribute names 15 %, depth 10 %).
Note: Requires the
similarityfeature to be enabled onstygian-browser.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
url | string | ✓ | URL to navigate to before searching |
fingerprint | object | ✓ | ElementFingerprint JSON — capture with node.fingerprint() in the Rust API |
threshold | number | Minimum similarity score 0–1 (default: 0.7) | |
max_results | integer | Maximum number of matches to return (default: 10) |
Returns:
[
{ "score": 0.92, "outer_html": "<div class=\"post post-featured\">...</div>" },
{ "score": 0.81, "outer_html": "<div class=\"post\">...</div>" }
]
browser_verify_stealth
Run a full stealth diagnostic: navigate to a detection-test URL and return a structured report of all signals checked (WebDriver flag, CDP artefacts, navigator properties, WebRTC leaks, TLS fingerprint, etc.).
Note: Requires the
stealthfeature to be enabled onstygian-browser.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID |
url | string | ✓ | URL to navigate to before running diagnostics (e.g. https://bot.sannysoft.com) |
timeout_secs | integer | Navigation timeout (default: 15) |
Returns: A DiagnosticReport JSON object:
{
"checks": [
{ "id": "WebDriverFlag", "passed": true, "details": "undefined" },
{ "id": "ChromeObject", "passed": true, "details": "present" },
{ "id": "PluginCount", "passed": true, "details": "5" },
{ "id": "LanguagesPresent", "passed": true, "details": "en-US,en" },
{ "id": "CanvasConsistency", "passed": true, "details": "data:image/png;..." },
{ "id": "WebGlVendor", "passed": true, "details": "Intel Inc. -- ANGLE" },
{ "id": "AutomationGlobals", "passed": true, "details": "none" },
{ "id": "OuterWindowSize", "passed": true, "details": "1920x1080" },
{ "id": "HeadlessUserAgent", "passed": true, "details": "Mozilla/5.0..." },
{ "id": "NotificationPermission", "passed": true, "details": "default" },
{ "id": "MatchMediaPresent", "passed": true, "details": "function" },
{ "id": "ElementFromPointPresent", "passed": true, "details": "function" },
{ "id": "RequestAnimationFramePresent", "passed": true, "details": "function" },
{ "id": "GetComputedStylePresent", "passed": true, "details": "function" },
{ "id": "CssSupportsPresent", "passed": true, "details": "function" },
{ "id": "SendBeaconPresent", "passed": true, "details": "function" },
{ "id": "ExecCommandPresent", "passed": true, "details": "function" },
{ "id": "NodeJsAbsent", "passed": true, "details": "absent" }
],
"passed_count": 18,
"failed_count": 0,
"transport": null
}
browser_release
Release a browser session back to the pool.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | ✓ | Session ID to release |
Returns:
{ "released": true }
pool_stats
Return current browser pool statistics. No parameters required.
Returns:
{
"active": 2,
"max": 8,
"available": 6
}
browser_acquire_and_extract
Run the opinionated acquisition ladder and return extraction/content output from one call.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | ✓ | Target URL |
mode | string | ✓ | fast | resilient | hostile | investigate |
wait_for_selector | string | Optional selector gate for browser-stage success | |
selector_wait | string | Alias for wait_for_selector | |
extraction_js | string | Optional JavaScript extraction expression | |
total_timeout_secs | number | Optional wall-clock timeout for full run (must be > 0) | |
browserbase_enabled | boolean | Optional Browserbase stage opt-in (requires stygian-browser feature browserbase) | |
use_browserbase | boolean | Alias for browserbase_enabled |
browserbase_enabled/use_browserbase require runtime environment variables
BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID.
Browserbase session warmup and rate-limit resilience (T114)
Starting with 0.17, the Browserbase-managed acquisition stage implements two resilience behaviors on by default:
- Session warmup — a settled navigation before the real one. Browserbase spins up the session, lands on a known landing page, then performs the real navigation. The warmup lets the session's fingerprint cookies, TLS session resumption state, and Browserbase internal monitoring surface attach before the target sees any request, so the first real navigation carries a fully-warm fingerprint rather than a cold one.
- Rate-limit retry with exponential backoff (default 3 attempts,
500 ms base) — on a
429response from Browserbase's session creation endpoint, the stage retries with exponential backoff. If the response includes aRetry-Afterheader, its value is honored in place of the computed backoff. New error variantsBrowserError::RateLimitedandStageFailureKind::RateLimiteddistinguish exhausted rate-limit retries from generic transport failures, so the runner can decide whether to back off the whole pipeline or just retry the Browserbase stage.
Both behaviors are configurable via
AcquisitionRequest::browserbase_session: Option<BrowserbaseSessionConfig>:
BrowserbaseSessionConfig {
/// Disable session warmup (default: warmup enabled).
warmup_enabled: false,
/// Override the retry policy (default: 3 attempts, 500 ms base,
/// exponential backoff capped at 30 s).
retry: BrowserbaseRetryPolicy::new(/* max_attempts */ 5, /* base */ 250),
/// Reuse an existing session instead of minting a new one per call.
/// Equivalent to setting the BROWSERBASE_SESSION_ID environment
/// variable but scoped to a single acquire request. A reused
/// session is never deleted by the stage — the caller owns its
/// lifecycle.
session_id: Some("sess_existing_123"),
}
Setting BROWSERBASE_SESSION_ID in the environment has the same
effect as BrowserbaseSessionConfig::session_id = Some(...): it
reuses the named session instead of creating a new one. Use the
explicit config form when you want the session reuse scoped to a
single browser_acquire_and_extract call rather than the whole
process.
Returns:
{
"success": true,
"strategy_used": "browser_light_stealth",
"final_url": "https://example.com",
"status_code": 200,
"extracted": {
"title": "Example Domain"
},
"html_excerpt": "<!doctype html><html>...",
"diagnostics": {
"attempted": ["direct_http", "browser_light_stealth"],
"timed_out": false,
"failure_count": 0,
"failures": []
}
}
Mode examples:
fast
{
"name": "browser_acquire_and_extract",
"arguments": {
"url": "https://example.com",
"mode": "fast"
}
}
resilient
{
"name": "browser_acquire_and_extract",
"arguments": {
"url": "https://example.com/catalog",
"mode": "resilient",
"wait_for_selector": "article.item"
}
}
hostile
{
"name": "browser_acquire_and_extract",
"arguments": {
"url": "https://example.com/challenge",
"mode": "hostile",
"wait_for_selector": "main",
"total_timeout_secs": 60,
"browserbase_enabled": true
}
}
investigate
{
"name": "browser_acquire_and_extract",
"arguments": {
"url": "https://example.com",
"mode": "investigate",
"extraction_js": "({ title: document.title, url: location.href })"
}
}
Migration note:
- Old low-level path:
browser_acquire->browser_navigate->browser_eval/browser_extract->browser_release. - New runner path: one
browser_acquire_and_extractcall with explicitmodeand optionalbrowserbase_enabled.
Live Attach Test (End-to-End)
Run a local Chrome with remote debugging enabled:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/stygian-attach-profile
Resolve the websocket endpoint and run the ignored attach integration test:
export STYGIAN_ATTACH_WS_ENDPOINT=$(curl -s http://127.0.0.1:9222/json/version | jq -r .webSocketDebuggerUrl)
cargo test -p stygian-browser \
--features "mcp,mcp-attach" \
--test mcp_integration \
-- --ignored --nocapture
If STYGIAN_ATTACH_WS_ENDPOINT is not set, the live attach test is skipped.
Resources
The browser MCP exposes active sessions as MCP resources, readable via resources/read.
| URI pattern | Description |
|---|---|
browser://session/{session_id} | State of a specific browser session |
Example resources/read request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "resources/read",
"params": { "uri": "browser://session/01HV4..." }
}
Returns:
{
"uri": "browser://session/01HV4...",
"mimeType": "application/json",
"text": "{ \"session_id\": \"01HV4...\", \"config\": { \"stealth_level\": \"advanced\", \"proxy\": null }, \"pool_active\": 1, \"pool_max\": 8 }"
}