Stealth Browser Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Stealth Browser Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A Model Context Protocol (MCP) server that provides stealth web browsing capabilities using dual browser engines — Patchright (Chromium) and Camoufox (Firefox) — with automatic bot-detection bypass.
Built for use with Claude Code and other MCP-compatible AI agents.
engine: autofile://captcha_detected flagbrowseNavigate to a URL and return page content as clean markdown.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | yes | URL to navigate to (http/https only) |
session_id | string | no | Reuse an existing session. If omitted, creates a new one |
wait_for | string | no | CSS selector to wait for before extracting |
engine | string | no | auto (default), chromium, or firefox |
Returns: url, title, content, session_id, truncated, captcha_detected, extraction_method, timing_ms, status_code, engine
interactInteract with the current page in a session.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session from a previous browse call |
action | string | yes | One of: click, type, select, hover, scroll |
selector | string | yes | CSS selector for the target element |
value | string | no | Required for type and select. For scroll, pixel amount |
Returns: success, session_id, action_performed, page_url, timing_ms
extractRe-extract content from the current page without re-navigating. Use this instead of browse when you're already on the page.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session to extract from |
mode | string | no | auto (default), article, or text (raw innertext) |
Returns: content, session_id, url, extraction_method, truncated
close_sessionClose a browser session and free its resources.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session to close |
Returns: status, session_id
save_session_statePersist an active session's cookies and local storage to a named profile.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Session to persist |
profile_name | string | yes | Safe profile name to save under |
Returns: status, session_id, profile_name, storage_state_path, meta
load_session_stateCreate a new session from a previously saved profile.
| Parameter | Type | Required | Description |
|---|---|---|---|
profile_name | string | yes | Saved profile name |
session_id | string | no | Optional custom session ID |
engine | string | no | chromium (default) or firefox |
Returns: status, session_id, profile_name, engine, meta
list_saved_profilesList saved persistent profiles on disk.
Returns: profiles, count
delete_saved_profileDelete a saved profile from disk.
| Parameter | Type | Required | Description |
|---|---|---|---|
profile_name | string | yes | Saved profile name |
Returns: status, profile_name
search_xOpen an X search results page for a query and return structured tweet cards.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search query |
mode | string | no | latest (default) or top |
max_items | int | no | Max tweets to extract (1-50, default 20) |
scroll_rounds | int | no | Additional scroll/collect rounds (0-10, default 0) |
session_id | string | no | Reuse an existing session |
profile_name | string | no | Load a persisted login profile into a fresh session |
engine | string | no | auto (default), chromium, or firefox |
Returns: query, mode, search_url, session_id, tweets, extracted_count, scroll_rounds_completed, captcha_detected, engine
extract_x_search_resultsExtract structured tweet cards from the current page of an existing X search session.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | yes | Active session already on an X search page |
max_items | int | no | Max tweets to extract (1-50, default 20) |
Returns: session_id, tweets, extracted_count, page_url, page_title
research_x_topicRun X search and produce a lightweight heuristic topic summary from the extracted tweets.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search query |
mode | string | no | latest (default) or top |
max_items | int | no | Max tweets to extract (1-50, default 20) |
scroll_rounds | int | no | Additional scroll/collect rounds (0-10, default 0) |
session_id | string | no | Reuse an existing session |
profile_name | string | no | Load a persisted login profile into a fresh session |
engine | string | no | auto (default), chromium, or firefox |
Returns: everything from search_x plus research, normalized, and report_markdown
read_x_threadOpen a tweet/thread URL and extract the visible main tweet plus replies from the detail page.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | yes | X tweet/thread URL |
max_items | int | no | Max visible tweets to extract (1-50, default 20) |
session_id | string | no | Reuse an existing session |
profile_name | string | no | Load a persisted login profile into a fresh session |
engine | string | no | auto (default), chromium, or firefox |
Returns: main_tweet, replies, reply_count_extracted, and page metadata
research_x_topic_deepRun X search, pick a few high-signal tweets, load their thread pages, and produce a richer deep-research summary.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search query |
mode | string | no | latest (default) or top |
max_items | int | no | Max search tweets to collect |
scroll_rounds | int | no | Additional search scroll rounds |
deep_dive_count | int | no | Number of thread URLs to inspect (default 3) |
thread_items | int | no | Max tweets to extract per thread |
session_id | string | no | Reuse an existing session |
profile_name | string | no | Load a persisted login profile into a fresh session |
engine | string | no | auto (default), chromium, or firefox |
Returns: deep_dive_candidates, threads, deep_research, normalized, and report_markdown in addition to the base search output
save_x_research_reportRun topic research (normal or deep) and save JSON + markdown report bundle to disk.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search query |
deep | bool | no | If true, use deep research workflow |
mode | string | no | latest (default) or top |
max_items | int | no | Max tweets to collect |
scroll_rounds | int | no | Additional search scroll rounds |
deep_dive_count | int | no | Thread deep-dive count |
thread_items | int | no | Max tweets per thread |
session_id | string | no | Reuse an existing session |
profile_name | string | no | Load a persisted login profile into a fresh session |
engine | string | no | auto (default), chromium, or firefox |
report_name | string | no | Optional custom output name |
Returns: research output plus saved_report paths
list_saved_x_reportsList saved research report bundles from disk.
Returns: reports, count
scrape_webpageNavigate to a URL, extract content in the requested format, and auto-close the session.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | yes | URL to scrape (http/https only) |
output_format | string | no | markdown (default), text, html, or links |
session_id | string | no | Reuse session. If omitted, creates ephemeral session that auto-closes |
wait_for | string | no | CSS selector to wait for before extracting |
engine | string | no | auto (default), chromium, or firefox |
Returns: url, title, content, session_id, status_code, timing_ms, extraction_method, engine
extract_structured_dataExtract structured DOM data (metadata, links, tables, JSON-LD, etc.) from a webpage.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | yes | URL to extract from (http/https only) |
session_id | string | no | Reuse session. If omitted, creates ephemeral session |
include | list | no | Sections to include. Default: all. Options: metadata, og_tags, json_ld, headings, links, tables, forms |
wait_for | string | no | CSS selector to wait for before extracting |
engine | string | no | auto (default), chromium, or firefox |
Returns: url, title, session_id, timing_ms, engine, + requested data sections
crawl_pagesCrawl multiple pages via BFS starting from a URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | yes | Starting URL (http/https only) |
max_pages | int | no | Maximum pages to crawl (1-20, default 5) |
link_pattern | string | no | Regex to filter link hrefs |
output_format | string | no | markdown (default), text, html, or links |
same_domain | bool | no | Only follow same-domain links (default: true) |
engine | string | no | auto (default), chromium, or firefox |
Returns: pages (list of {url, title, content, status_code}), total_pages, total_timing_ms, engine
System libraries (Ubuntu/Debian/WSL2):
sudo apt-get install -y libnspr4 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 \
libpango-1.0-0 libcairo2 libasound2t64 xvfbPython 3.12+ and uv (recommended) or pip.
git clone https://github.com/Axe240-commits/stealth-browser-mcp.git
cd stealth-browser-mcp
chmod +x setup.sh
./setup.shOr manually:
uv venv
uv pip install -e ".[dev]"
.venv/bin/python -m patchright install chromium# Run tests
.venv/bin/python -m pytest tests/ -v
# Start server (will wait for MCP stdio input)
.venv/bin/python -m stealth_browserAdd to ~/.claude/mcp_servers.json:
{
"stealth-browser": {
"type": "stdio",
"command": "/path/to/stealth-browser-mcp/.venv/bin/python",
"args": ["-m", "stealth_browser"]
}
}Then add permissions in ~/.claude/settings.json:
{
"permissions": {
"allow": [
"mcp__stealth-browser__browse",
"mcp__stealth-browser__interact",
"mcp__stealth-browser__extract",
"mcp__stealth-browser__close_session",
"mcp__stealth-browser__save_session_state",
"mcp__stealth-browser__load_session_state",
"mcp__stealth-browser__list_saved_profiles",
"mcp__stealth-browser__delete_saved_profile",
"mcp__stealth-browser__search_x",
"mcp__stealth-browser__extract_x_search_results",
"mcp__stealth-browser__research_x_topic",
"mcp__stealth-browser__read_x_thread",
"mcp__stealth-browser__research_x_topic_deep",
"mcp__stealth-browser__save_x_research_report",
"mcp__stealth-browser__list_saved_x_reports",
"mcp__stealth-browser__scrape_webpage",
"mcp__stealth-browser__extract_structured_data",
"mcp__stealth-browser__crawl_pages"
]
}
}Restart Claude Code. The tools will be available immediately.
┌─────────────────────────────────────────────────┐
│ Claude Code / MCP Client │
│ │
│ browse ─ interact ─ extract ─ close_session │
│ scrape_webpage ─ extract_structured_data │
│ crawl_pages │
└────────────────┬────────────────────────────────┘
│ stdio (JSON-RPC)
┌────────────────▼────────────────────────────────┐
│ server.py — FastMCP Server (7 tools) │
│ ├── security.py — SSRF validation (every URL) │
│ ├── session.py — per-session lock + state │
│ ├── browser_manager.py — dual engine pool │
│ ├── extractor.py — 3-tier content extraction │
│ ├── dom_extractor.py — structured DOM data │
│ └── config.py — configuration │
└───────┬─────────────────┬───────────────────────┘
│ │
┌───────▼──────┐ ┌───────▼──────┐
│ Patchright │ │ Camoufox │
│ (Chromium) │ │ (Firefox) │
│ Primary │ │ Fallback │
└───────┬──────┘ └───────┬──────┘
│ │
┌───────▼─────────────────▼───────────────────────┐
│ Xvfb :99 — 1920x1080 (headed mode) │
└─────────────────────────────────────────────────┘With engine: auto (the default), every request:
For crawl_pages, the engine switch happens on the first page and sticks for the rest of the crawl.
trafilatura (best for articles, tables, links)
↓ fallback if < 200 chars
readability-lxml + html2text (complex HTML)
↓ fallback if < 200 chars
page.inner_text('body') (SPAs, JS-rendered content)browse() call with no session_id creates a new BrowserContext (~100ms)asyncio.Lockchromium or firefox)Every URL is validated before navigation:
http and https allowedBlocked:
localhost, 127.0.0.1, ::110.x, 172.16.x, 192.168.x)169.254.169.254)file://, data://, javascript://, ftp://extract to re-read the same page — don't call browse againbrowse only for actual navigation (new URL or page change)session_id across related operationsclose_session when done to free resourcesscrape_webpage for one-shot scraping (auto-closes session)crawl_pages to spider multiple pages from a starting URLdomcontentloaded (fast, reliable) — use wait_for if you need a specific elementstealth-browser-mcp/
├── pyproject.toml # Dependencies, build config
├── setup.sh # One-command setup
├── src/stealth_browser/
│ ├── __init__.py
│ ├── __main__.py # Entry: python -m stealth_browser
│ ├── server.py # MCP server, 7 tools, lifespan
│ ├── browser_manager.py # Dual engine lifecycle, context pool
│ ├── session.py # Session state, locking, actions
│ ├── extractor.py # 3-tier content extraction
│ ├── dom_extractor.py # Structured DOM data extraction
│ ├── security.py # SSRF-hardened URL validation
│ ├── config.py # Configuration dataclass
│ └── proxy.py # Stub (Phase 2: Tor)
└── tests/
├── test_security.py # URL/IP validation tests
├── test_extractor.py # Extraction mode/fallback tests
├── test_dom_extractor.py # DOM structured data tests
└── test_server_helpers.py # Server helper function testsDefaults in config.py — no config file needed:
| Setting | Default | Description |
|---|---|---|
headless | False | Headed mode (Xvfb) for better stealth |
use_xvfb | True | Auto-start Xvfb for headed mode |
max_sessions | 5 | Max concurrent browser sessions |
session_timeout_minutes | 10 | Idle session eviction timeout |
navigation_timeout_ms | 30000 | Page load timeout |
wait_until | domcontentloaded | Navigation wait strategy |
max_content_length | 50000 | Content truncation limit (chars) |
block_media | True | Block images/fonts/media for speed |
camoufox_enabled | True | Enable Firefox fallback engine |
crawl_max_pages_limit | 20 | Hard cap for crawl_pages |
crawl_per_page_max | 10000 | Content limit per crawled page |
| Package | Purpose |
|---|---|
| mcp | MCP server framework (Anthropic) |
| patchright | Stealth Playwright fork (Chromium) |
| camoufox | Anti-fingerprint Firefox (fallback engine) |
| trafilatura | Article/content extraction |
| readability-lxml | Fallback HTML extraction |
| html2text | HTML to markdown conversion |
error while loading shared librariesChromium needs system libraries that aren't installed by default on minimal Linux/WSL2:
error while loading shared libraries: libnspr4.so: cannot open shared object fileSolution:
sudo apt-get install -y libnspr4 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 \
libpango-1.0-0 libcairo2 libasound2t64 xvfbCamoufox requires xvfb for headed mode:
sudo apt-get install -y xvfbIf Camoufox still fails, it falls back gracefully — Chromium-only mode still works.
The server must be registered in ~/.claude/mcp_servers.json:
{
"stealth-browser": {
"type": "stdio",
"command": "/absolute/path/to/.venv/bin/python",
"args": ["-m", "stealth_browser"]
}
}After adding, restart Claude Code — MCP servers are loaded at startup only.
Add all 7 tools to ~/.claude/settings.json permissions (see Register section above).
extract with mode="text" for SPAs/JS-heavy pageswait_for parameter with a CSS selector to wait for dynamic contentengine: firefox — some sites respond better to Camoufoxdomcontentloaded doesn't wait for lazy-loaded content — pass a selector that appears after the page fully rendersIf engine: auto falls back to Firefox and still gets blocked, the site may require:
Sessions are evicted after 10 minutes of inactivity or when the 5-session limit is reached. If you get "Session 'xyz' not found", create a new one with browse.
screenshot tool — for CAPTCHA/consent debuggingevaluate_js tool — targeted DOM queriessession_info tool — list active sessions and stateMIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.