Skip to main content
Glama
anusoft

ultrastealth

by anusoft

Ultrastealth

Ultrastealth is a standalone Python package for maximum-stealth browser automation. It uses rebrowser-playwright (with CDP leak fixes), real Google Chrome by default, Chromium fallback, and headed Xvfb modes on Linux to avoid bot detection systems.

Prerequisites

  • Python 3.12+

  • Google Chrome installed for best brand parity, or Chromium/Playwright's bundled Chromium fallback.

Related MCP server: gotham-browser

Installation

From a repo clone, installation is one command:

./install.sh

This installs the package in editable mode, installs rebrowser-playwright's Chromium fallback build, and applies the driver fingerprint patch. To select a specific Python interpreter:

PYTHON=/path/to/python3 ./install.sh

You can also install directly from GitHub with curl and bash:

curl -fsSL https://raw.githubusercontent.com/anusoft/ultrastealth/main/install.sh | bash

This keeps the checkout at ~/.ultrastealth/src/ultrastealth by default so the editable install remains valid. Override with ULTRASTEALTH_INSTALL_DIR, ULTRASTEALTH_REF, or PYTHON when needed.

If the package is already installed, rerun the same browser install + patch step directly:

ultrastealth-install

Basic Usage

You can use the UltrastealthFetcher in your own scripts:

import asyncio
from ultrastealth import UltrastealthFetcher

async def fetch_example():
    # headless=False runs headed. On macOS this prefers native headful Google Chrome;
    # on Linux it uses Xvfb when a display is not already available.
    async with UltrastealthFetcher(headless=False) as us:
        # fetch_and_evaluate avoids issues with default Playwright page.content() on SPAs
        title = await us.fetch_and_evaluate(
            url="https://bot.sannysoft.com/",
            js_expression="() => document.title",
            wait_secs=3.0
        )
        print("Page Title:", title)

if __name__ == "__main__":
    asyncio.run(fetch_example())

Benchmark

bot_benchmark.py scores Ultrastealth against 21 bot-detection / fingerprint sites (sannysoft, rebrowser, creepjs, deviceandbrowserinfo, iphey, fingerprint-scan, cloudflare, reCAPTCHA/Turnstile demos, …). Current baseline: 96% (105/109)docs/research/bot_benchmark_ultrastealth_phase_a_baseline.json. Scoring counts pass/fail only; an untriggered probe (skip) and an unreachable site (error) are both reported separately rather than counted as a failure. Run it under a virtual display:

# one-time: start Xvfb on :99 (or use your own display)
Xvfb :99 -screen 0 1920x1080x24 &

DISPLAY=:99 python3 bot_benchmark.py                      # all sites
DISPLAY=:99 python3 bot_benchmark.py --sites sannysoft rebrowser
python3 bot_benchmark.py --methods ultrastealth patchright   # compare engines
python3 bot_benchmark.py --compare bot_benchmark_results.json   # reprint table

Each site has its own extraction JS + scorer; results are written to JSON and printed as a table. Notes: pixelscan/incolumitas only return a verdict from a residential IP; cloudflare (nowsecure.nl) needs the bundled Turnstile solver. The fingerprint is the consistent, unmodified real-Chrome one — benchmarking showed it beats a spoofed fingerprint on every fingerprint site tested, so there is no JS-spoofing layer to opt into.

Browser Runner Defaults

The default runner is chrome+default-profile: Ultrastealth launches Google Chrome with the OS default Chrome user-data directory and --profile-directory=Default. On macOS this is:

~/Library/Application Support/Google/Chrome

This gives MCP tools access to the same cookies and logged-in state as the normal Chrome default profile. Override it when needed:

ULTRASTEALTH_RUNNER=chrome+temp-profile ultrastealth-mcp --transport stdio
ULTRASTEALTH_PROFILE_DIRECTORY="Profile 1" ultrastealth-mcp --transport stdio
ULTRASTEALTH_USER_DATA_DIR=/path/to/chrome-user-data ultrastealth-mcp --transport stdio
ultrastealth-mcp --transport stdio --runner chrome+default-profile --user-data-dir "/path/to/chrome-user-data" --profile-directory "Profile 1"
ultrastealth-mcp --transport stdio --runner chromium+default-profile --user-data-dir "/path/to/chromium-user-data" --profile-directory "Profile 1"

On Linux the default profile root is:

~/.config/google-chrome

If Chrome is already running with the same user-data directory and no profile was explicitly requested, Ultrastealth retries once with a temporary profile so MCP calls can still open the requested page in one shot. If you set ULTRASTEALTH_PROFILE_DIRECTORY, ULTRASTEALTH_USER_DATA_DIR, or pass profile arguments to an MCP tool, Ultrastealth treats that as an explicit profile request and will not silently fall back to a temporary profile. Close Chrome first if you need automation control of that exact logged-in profile.

MCP calls can request a specific Chrome profile for one-shot navigation:

browser_navigate({"url": "https://mail.google.com", "profile_directory": "Profile 1"})
browser_restart({"navigate_to": "https://mail.google.com", "profile_directory": "Profile 1"})

You can also pass user_data_dir and runner on those MCP calls. Explicit profile requests do not silently fall back to a temporary profile; if Chrome has locked that user-data directory, close Chrome or choose a separate user_data_dir.

Warm Daemon + Fast CLI

For fast, deterministic driving — from the shell, from scripts, or from an agent — run a warm-browser daemon and talk to it with the ultrastealth CLI (alias us). The daemon owns one persistent-profile Chrome and keeps it warm, so every command attaches in milliseconds instead of cold-booting Chrome (seconds) each time. Exactly one process holds the CDP connection; the CLI, the MCP server, and connect() scripts all attach to it — so they share the same session.

ultrastealth daemon start                                   # warm Chrome, once
ultrastealth browser navigate https://example.com
ultrastealth browser snapshot --interactive --compact      # → [e0] <button> "Login" …
ultrastealth browser click e0 --snapshot-after             # act by ref or CSS selector
ultrastealth browser type e3 --text "user@example.com"
ultrastealth daemon status        # running / socket / pid
ultrastealth daemon stop

Snapshot refs, not screenshots. snapshot returns the accessibility tree with stable eN refs; actions take a ref (e2) or a CSS selector. --snapshot-after returns the fresh snapshot in the same response. This is the token/latency win (the Playwright-MCP model), on real bot-detection-passing Chrome.

Batch multi-step flows into one call (also the browser_batch MCP tool):

ultrastealth browser batch - <<'JSON'
[{"op":"navigate","url":"https://example.com/login"},
 {"op":"wait","selector":"#email"},
 {"op":"fill","target":"#email","text":"user@example.com"},
 {"op":"click","target":"e7"},
 {"op":"wait","text":"Welcome"},
 {"op":"snapshot"}]
JSON

From Python (instant restart, persistent cf_clearance):

from ultrastealth import connect
us = connect()                                   # starts the daemon once, then reuses it
await us.call("navigate", url="https://example.com", wait_secs=2.0)
title = (await us.call("get", kind="title"))["title"]

Environment: ULTRASTEALTH_IDLE_TIMEOUT (seconds the browser stays warm after the last command; 0 = never close, default 1800), ULTRASTEALTH_DAEMON_DIR (where the socket/pid/log live; the socket auto-relocates to a short temp path if this dir would exceed the OS's Unix-socket length limit). Profile selection uses the same ULTRASTEALTH_RUNNER / _USER_DATA_DIR / _PROFILE_DIRECTORY as the rest of the stack. The stealth launch path is unchanged — the daemon-driven browser passes the same bot checks as UltrastealthFetcher.

See the bundled fast-browser skill (skills/fast-browser/) for the agent playbook and full command reference.

MCP Server for Claude Code

The ultrastealth MCP server exposes the stealth browser as tools for Claude Code, giving it the ability to navigate, click, type, screenshot, and monitor network traffic — all with maximum anti-detection.

The server runs as an HTTP service (streamable-http transport) on port 8090 by default, managed by PM2.

Available Tools

Browser automation: browser_navigate, browser_snapshot (stable eN refs), browser_batch (many steps, one call), browser_click, browser_type, browser_get_state, browser_screenshot, browser_scroll, browser_go_back, browser_evaluate, browser_press_key, browser_get_html, browser_get, browser_is, browser_wait, browser_hover, browser_focus, browser_scroll_into_view, browser_select_option, browser_add_init_script, browser_add_script, browser_add_style, browser_close

When the warm daemon is running, these MCP tools drive the same browser as the ultrastealth CLI. Set ULTRASTEALTH_MCP_NO_DAEMON=1 to make the MCP server own a private browser instead.

Tab management: browser_new_tab, browser_list_tabs, browser_switch_tab, browser_close_tab

Network monitoring (DevTools-style): browser_network_enable, browser_network_disable, browser_network_log, browser_network_detail, browser_network_response_body, browser_network_clear, browser_network_summary

Session, storage, and diagnostics: browser_cookies, browser_storage, browser_state_save, browser_state_load, browser_console_list, browser_console_clear, browser_errors_list, browser_errors_clear

Resource management: browser_status, browser_cleanup, browser_restart

Running the Server

# Start with PM2 (recommended)
pm2 delete ultrastealth-mcp 2>/dev/null
pm2 start /usr/bin/python3 --name "ultrastealth-mcp" \
  --cwd /path/to/your/project -- -m ultrastealth.mcp_server --port 8090

# Or run directly
python3 -m ultrastealth.mcp_server                          # HTTP on 0.0.0.0:8090
python3 -m ultrastealth.mcp_server --port 9000              # HTTP on custom port
python3 -m ultrastealth.mcp_server --transport stdio         # stdio mode (legacy)

Connecting Claude Code

The MCP endpoint is http://localhost:8090/mcp (streamable-http).

Add to .claude/settings.json in your project root:

{
  "mcpServers": {
    "ultrastealth": {
      "type": "url",
      "url": "http://localhost:8090/mcp"
    }
  }
}

User-Level (all projects)

Add to ~/.claude/settings.json:

{
  "mcpServers": {
    "ultrastealth": {
      "type": "url",
      "url": "http://localhost:8090/mcp"
    }
  }
}

Via CLI

claude mcp add --transport http ultrastealth http://localhost:8090/mcp

Verify

After restarting Claude Code, run /mcp to confirm the ultrastealth server appears and its tools are listed.

Example Workflow

> use ultrastealth to check bot.sannysoft.com

Claude calls: browser_navigate("https://bot.sannysoft.com")
Claude calls: browser_screenshot()
Claude calls: browser_get_html("table")
→ Returns all test results from the page

Network Monitoring Example

> enable network capture and navigate to example.com, then show me all API calls

Claude calls: browser_network_enable()
Claude calls: browser_navigate("https://example.com")
Claude calls: browser_network_log(filter_type="xhr")
→ Returns table of all XHR/fetch requests with status, timing, size
Claude calls: browser_network_detail(request_id=3)
→ Returns full headers and body for a specific request

Driver Fingerprint Patch (patch_rebrowser.py)

rebrowser-playwright's bundled Node driver leaks four identifiers that detectors (e.g. bot-detector.rebrowser.net) probe for in the page context:

  • globalThis.__pwInitScripts — the init-script dedup map, created by the driver before any bypass runs (so a JS bypass can't reliably hide it).

  • UtilityScript — the class wrapping every page.evaluate; its name leaks into Error().stack captured by page JS.

  • globalThis.__playwright_builtins__ — a cache of native setTimeout/Date/Map/etc. that every injected script recreates before user scripts run, so it can't be hidden by a JS bypass either.

  • globalThis.__playwright__binding__ — the CDP Runtime.addBinding channel name the driver exposes on every page regardless of whether the caller uses exposeBinding; a JS bypass races the CDP call and can only win after the property already existed.

patch_rebrowser.py renames all four at the driver source (__pwInitScripts → __execGuards, UtilityScript → ExecutionProxy, __playwright_builtins__ → __nativeRefs, __playwright__binding__ → __execChannel), consistently so functionality is preserved. With all four renames applied, bot-detector.rebrowser.net scores 6/6 pass, 0 fail (4 of its 10 probes need main-world access that alwaysIsolated mode denies by design — see below — and are correctly reported as skip, not a failure). Full 21-site benchmark: 96% (105/109)docs/research/bot_benchmark_ultrastealth_phase_a_baseline.json.

ultrastealth-install --skip-browser-install        # apply after dependency upgrades
ultrastealth-patch --check                         # report status
ultrastealth-patch --revert                        # undo

The patch edits the installed pip package, so a pip install -U rebrowser-playwright reverts it — re-run ultrastealth-install --skip-browser-install afterward. It is idempotent, revertible, and upstream-safe: each edit anchors on the original token and warns + skips (never corrupts) if upstream changed it. UltrastealthFetcher.start() also attempts to apply the patch before launching Chrome/Chromium and logs a warning if it cannot.

Maximum stealth: isolated evaluate (opt-in tradeoff)

The one remaining rebrowser detection — mainWorldExecution — is by design: rebrowser's default addBinding mode runs page.evaluate in the main world (detectable). Setting:

export REBROWSER_PATCHES_RUNTIME_FIX_MODE=alwaysIsolated

runs evaluate in an isolated world0 positive detections on rebrowser. Trade-off: isolated evaluate can read the shared DOM (querySelector, outerHTML, embedded JSON like __NEXT_DATA__ via textContent) but not main-world JS globals (window.someAppState). Ultrastealth sets this mode by default before launch; set REBROWSER_PATCHES_RUNTIME_FIX_MODE=addBinding if you need main-world JS access.

Alternative Engine: patchright (opt-in)

UltrastealthFetcher(engine="patchright") / ULTRASTEALTH_ENGINE=patchright switches the automation driver from rebrowser-playwright to patchright, an independently maintained undetected-Playwright fork. Head-to-head across all 21 benchmark sites it scores identically to the default engine (96%, same failures) but runs ~22% faster overall — most of that on slow challenge-solve sites (cloudflare, egp_announcements).

It is not a drop-in replacement for the default. Patchright's Page has no accessibility attribute — upstream Playwright removed it by the version patchright tracks — so it cannot back the browser_snapshot/browser_batch eN ref system this project's MCP server and CLI depend on. Use it only through UltrastealthFetcher.fetch() / fetch_and_evaluate(), or bot_benchmark.py --methods patchright. Full verified findings: CLAUDE.md.

Browser Ops

Beyond the CLI/MCP tools listed above, browser_core.py's op registry (reachable via ultrastealth browser <op>, the Python client.connect() API, and browser_batch steps) also includes:

  • cookies — the current browser context's cookies.

  • find — best-matching interactive element eN ref for a natural-language-ish query, scored against the accessibility snapshot's roles/names (no LLM call).

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.

  • A paid remote MCP for AI agent browser DevTools MCP, built to return verdicts, receipts, usage logs,

  • AI-powered browser automation — navigate, click, fill forms, and extract data from any website.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/anusoft/ultrastealth'

If you have feedback or need assistance with the MCP directory API, please join our Discord server