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.

ParameterTypeRequiredDescription
stealth_levelstringnone | basic | advanced (default: advanced)
tls_profilestringTLS fingerprint profile name — e.g. chrome131, firefox133, safari18, edge131
webrtc_policystringallow_all | disable_non_proxied | block_all (default from pool config)
cdp_fix_modestringCDP leak mitigation mode: addBinding | isolatedWorld | enableDisable | none
proxystringProxy 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID from browser_acquire
urlstring✓Target URL
timeout_secsintegerNavigation 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
scriptstring✓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.

ParameterTypeRequiredDescription
session_idstring✓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.

ParameterTypeRequiredDescription
session_idstring✓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).

ParameterTypeRequiredDescription
modestring✓cdp_ws | extension_bridge
endpointstringRequired for cdp_ws; DevTools websocket URL
profile_hintstringOptional label for external profile identity
target_profilestringOptional 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
modestring✓capture | resume
file_pathstringOptional snapshot file path
ttl_secsintegerOptional TTL in seconds when mode = capture
navigate_to_originbooleanWhen resuming, navigate to snapshot origin first (default: true)
interaction_levelstringOptional 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
ttl_secsintegerOptional snapshot TTL
file_pathstringOptional output path for snapshot JSON
include_snapshotbooleanInclude 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
snapshotobjectInline SessionSnapshot JSON
file_pathstringPath to snapshot JSON file
use_savedbooleanUse in-memory saved snapshot if no inline/file source provided (default: true)
navigate_to_originbooleanNavigate 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
levelstringnone | low | medium | high (default: low)
viewport_widthnumberViewport width used for interaction simulation (default: 1366)
viewport_heightnumberViewport 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID from browser_acquire
urlstring✓URL to navigate to before querying
selectorstring✓CSS selector — e.g. "article.post h2"
fieldsobjectMap 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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
urlstring✓URL to navigate to before extracting
root_selectorstring✓CSS selector for the repeating container element
schemaobject✓Map of field name to { selector, attr?, required? } field descriptor

Schema field descriptor:

KeyTypeRequiredDescription
selectorstring✓CSS selector scoped to the root element
attrstringIf present, captures this attribute instead of textContent
requiredbooleantrue (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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
urlstring✓URL to navigate to before extracting
root_selectorsarray[string]✓Candidate root selectors in priority order
schemaobject✓Same schema object used by browser_extract
timeout_secsnumberNavigation/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.

ParameterTypeRequiredDescription
session_idstring✓Session ID
urlstring✓URL to navigate to before extracting
root_selectorstring✓Root selector for repeated record containers
schemaobject✓Same schema object used by browser_extract
timeout_secsnumberNavigation/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 similarity feature to be enabled on stygian-browser.

ParameterTypeRequiredDescription
session_idstring✓Session ID
urlstring✓URL to navigate to before searching
fingerprintobject✓ElementFingerprint JSON — capture with node.fingerprint() in the Rust API
thresholdnumberMinimum similarity score 0–1 (default: 0.7)
max_resultsintegerMaximum 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 stealth feature to be enabled on stygian-browser.

ParameterTypeRequiredDescription
session_idstring✓Session ID
urlstring✓URL to navigate to before running diagnostics (e.g. https://bot.sannysoft.com)
timeout_secsintegerNavigation 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.

ParameterTypeRequiredDescription
session_idstring✓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.

ParameterTypeRequiredDescription
urlstring✓Target URL
modestring✓fast | resilient | hostile | investigate
wait_for_selectorstringOptional selector gate for browser-stage success
selector_waitstringAlias for wait_for_selector
extraction_jsstringOptional JavaScript extraction expression
total_timeout_secsnumberOptional wall-clock timeout for full run (must be > 0)
browserbase_enabledbooleanOptional Browserbase stage opt-in (requires stygian-browser feature browserbase)
use_browserbasebooleanAlias 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:

  1. 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.
  2. Rate-limit retry with exponential backoff (default 3 attempts, 500 ms base) — on a 429 response from Browserbase's session creation endpoint, the stage retries with exponential backoff. If the response includes a Retry-After header, its value is honored in place of the computed backoff. New error variants BrowserError::RateLimited and StageFailureKind::RateLimited distinguish 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_extract call with explicit mode and optional browserbase_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 patternDescription
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 }"
}