Stealth Browser MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Stealth Browser MCP Serverbrowse https://example.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Stealth Browser MCP Server
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.
Features
Dual Engine Architecture — Patchright (Chromium) as primary engine, Camoufox (Firefox) as fallback with stronger anti-fingerprinting
Auto Bot-Block Detection — Detects Cloudflare, CAPTCHAs, and other bot protection; automatically retries with Firefox when
engine: autoHeaded Mode via Xvfb — Runs real browser windows (not headless) to beat fingerprint detection
18 MCP Tools — Browse, interact, extract, scrape, crawl, structured data extraction, session management, persistent profile state save/load/list/delete, X/Twitter search extraction helpers, heuristic topic research summaries, thread readers, deep topic research, and saved report bundles
3-Tier Content Extraction — trafilatura → readability → innertext fallback chain
SSRF-Hardened — DNS resolution validation blocks localhost, private IPs, cloud metadata,
file://Session Pooling — Up to 5 isolated BrowserContext sessions per engine, with 10-minute idle eviction
Smart Truncation — Large pages truncated at 50K chars on paragraph boundaries
CAPTCHA Detection — Detects Cloudflare Turnstile, reCAPTCHA, hCaptcha; reports structured
captcha_detectedflagAuto-Cleanup — Idle sessions evicted after 10 minutes, crashed browser auto-restarts
Related MCP server: ScrapeLab MCP
Tools
browse
Navigate to a URL and return page content as clean markdown.
Parameter | Type | Required | Description |
| string | yes | URL to navigate to (http/https only) |
| string | no | Reuse an existing session. If omitted, creates a new one |
| string | no | CSS selector to wait for before extracting |
| string | no |
|
Returns: url, title, content, session_id, truncated, captcha_detected, extraction_method, timing_ms, status_code, engine
interact
Interact with the current page in a session.
Parameter | Type | Required | Description |
| string | yes | Session from a previous |
| string | yes | One of: |
| string | yes | CSS selector for the target element |
| string | no | Required for |
Returns: success, session_id, action_performed, page_url, timing_ms
extract
Re-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 |
| string | yes | Session to extract from |
| string | no |
|
Returns: content, session_id, url, extraction_method, truncated
close_session
Close a browser session and free its resources.
Parameter | Type | Required | Description |
| string | yes | Session to close |
Returns: status, session_id
save_session_state
Persist an active session's cookies and local storage to a named profile.
Parameter | Type | Required | Description |
| string | yes | Session to persist |
| string | yes | Safe profile name to save under |
Returns: status, session_id, profile_name, storage_state_path, meta
load_session_state
Create a new session from a previously saved profile.
Parameter | Type | Required | Description |
| string | yes | Saved profile name |
| string | no | Optional custom session ID |
| string | no |
|
Returns: status, session_id, profile_name, engine, meta
list_saved_profiles
List saved persistent profiles on disk.
Returns: profiles, count
delete_saved_profile
Delete a saved profile from disk.
Parameter | Type | Required | Description |
| string | yes | Saved profile name |
Returns: status, profile_name
search_x
Open an X search results page for a query and return structured tweet cards.
Parameter | Type | Required | Description |
| string | yes | Search query |
| string | no |
|
| int | no | Max tweets to extract (1-50, default 20) |
| int | no | Additional scroll/collect rounds (0-10, default 0) |
| string | no | Reuse an existing session |
| string | no | Load a persisted login profile into a fresh session |
| string | no |
|
Returns: query, mode, search_url, session_id, tweets, extracted_count, scroll_rounds_completed, captcha_detected, engine
extract_x_search_results
Extract structured tweet cards from the current page of an existing X search session.
Parameter | Type | Required | Description |
| string | yes | Active session already on an X search page |
| int | no | Max tweets to extract (1-50, default 20) |
Returns: session_id, tweets, extracted_count, page_url, page_title
research_x_topic
Run X search and produce a lightweight heuristic topic summary from the extracted tweets.
Parameter | Type | Required | Description |
| string | yes | Search query |
| string | no |
|
| int | no | Max tweets to extract (1-50, default 20) |
| int | no | Additional scroll/collect rounds (0-10, default 0) |
| string | no | Reuse an existing session |
| string | no | Load a persisted login profile into a fresh session |
| string | no |
|
Returns: everything from search_x plus research, normalized, and report_markdown
read_x_thread
Open a tweet/thread URL and extract the visible main tweet plus replies from the detail page.
Parameter | Type | Required | Description |
| string | yes | X tweet/thread URL |
| int | no | Max visible tweets to extract (1-50, default 20) |
| string | no | Reuse an existing session |
| string | no | Load a persisted login profile into a fresh session |
| string | no |
|
Returns: main_tweet, replies, reply_count_extracted, and page metadata
research_x_topic_deep
Run X search, pick a few high-signal tweets, load their thread pages, and produce a richer deep-research summary.
Parameter | Type | Required | Description |
| string | yes | Search query |
| string | no |
|
| int | no | Max search tweets to collect |
| int | no | Additional search scroll rounds |
| int | no | Number of thread URLs to inspect (default 3) |
| int | no | Max tweets to extract per thread |
| string | no | Reuse an existing session |
| string | no | Load a persisted login profile into a fresh session |
| string | no |
|
Returns: deep_dive_candidates, threads, deep_research, normalized, and report_markdown in addition to the base search output
save_x_research_report
Run topic research (normal or deep) and save JSON + markdown report bundle to disk.
Parameter | Type | Required | Description |
| string | yes | Search query |
| bool | no | If true, use deep research workflow |
| string | no |
|
| int | no | Max tweets to collect |
| int | no | Additional search scroll rounds |
| int | no | Thread deep-dive count |
| int | no | Max tweets per thread |
| string | no | Reuse an existing session |
| string | no | Load a persisted login profile into a fresh session |
| string | no |
|
| string | no | Optional custom output name |
Returns: research output plus saved_report paths
list_saved_x_reports
List saved research report bundles from disk.
Returns: reports, count
scrape_webpage
Navigate to a URL, extract content in the requested format, and auto-close the session.
Parameter | Type | Required | Description |
| string | yes | URL to scrape (http/https only) |
| string | no |
|
| string | no | Reuse session. If omitted, creates ephemeral session that auto-closes |
| string | no | CSS selector to wait for before extracting |
| string | no |
|
Returns: url, title, content, session_id, status_code, timing_ms, extraction_method, engine
extract_structured_data
Extract structured DOM data (metadata, links, tables, JSON-LD, etc.) from a webpage.
Parameter | Type | Required | Description |
| string | yes | URL to extract from (http/https only) |
| string | no | Reuse session. If omitted, creates ephemeral session |
| list | no | Sections to include. Default: all. Options: |
| string | no | CSS selector to wait for before extracting |
| string | no |
|
Returns: url, title, session_id, timing_ms, engine, + requested data sections
crawl_pages
Crawl multiple pages via BFS starting from a URL.
Parameter | Type | Required | Description |
| string | yes | Starting URL (http/https only) |
| int | no | Maximum pages to crawl (1-20, default 5) |
| string | no | Regex to filter link hrefs |
| string | no |
|
| bool | no | Only follow same-domain links (default: true) |
| string | no |
|
Returns: pages (list of {url, title, content, status_code}), total_pages, total_timing_ms, engine
Installation
Prerequisites
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.
Setup
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 chromiumVerify
# Run tests
.venv/bin/python -m pytest tests/ -v
# Start server (will wait for MCP stdio input)
.venv/bin/python -m stealth_browserRegister with Claude Code
Add 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.
Architecture
┌─────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────┘Dual Engine & Auto-Fallback
With engine: auto (the default), every request:
Tries Patchright (Chromium) first — fast, low overhead
Checks for bot-block signals: HTTP 403, title keywords ("Just a moment", "Attention Required"), empty content
If blocked, automatically retries with Camoufox (Firefox) which has stronger anti-fingerprinting
For crawl_pages, the engine switch happens on the first page and sticks for the rest of the crawl.
Content Extraction Pipeline
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)Session Management
Two persistent browsers launched at MCP server start (Chromium + Firefox)
Each
browse()call with nosession_idcreates a newBrowserContext(~100ms)Sessions are isolated (separate cookies, storage, state)
Max 5 concurrent sessions, oldest evicted if at capacity
Idle sessions evicted after 10 minutes
All operations per session are serialized via
asyncio.LockEach session tracks its engine type (
chromiumorfirefox)
Security (SSRF Protection)
Every URL is validated before navigation:
Scheme check — only
httpandhttpsallowedDNS resolution — hostname resolved to actual IPs
IP validation — all resolved IPs checked against private/reserved ranges
Redirect validation — redirects re-validated at each hop
Blocked:
localhost,127.0.0.1,::1Private ranges (
10.x,172.16.x,192.168.x)Cloud metadata (
169.254.169.254)Link-local, multicast, reserved IPs
file://,data://,javascript://,ftp://
Usage Tips for AI Agents
Use
extractto re-read the same page — don't callbrowseagainUse
browseonly for actual navigation (new URL or page change)Reuse
session_idacross related operationsAlways call
close_sessionwhen done to free resourcesUse
scrape_webpagefor one-shot scraping (auto-closes session)Use
crawl_pagesto spider multiple pages from a starting URLDefault navigation uses
domcontentloaded(fast, reliable) — usewait_forif you need a specific element
Project Structure
stealth-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 testsConfiguration
Defaults in config.py — no config file needed:
Setting | Default | Description |
|
| Headed mode (Xvfb) for better stealth |
|
| Auto-start Xvfb for headed mode |
|
| Max concurrent browser sessions |
|
| Idle session eviction timeout |
|
| Page load timeout |
|
| Navigation wait strategy |
|
| Content truncation limit (chars) |
|
| Block images/fonts/media for speed |
|
| Enable Firefox fallback engine |
|
| Hard cap for crawl_pages |
|
| Content limit per crawled page |
Dependencies
Package | Purpose |
MCP server framework (Anthropic) | |
Stealth Playwright fork (Chromium) | |
Anti-fingerprint Firefox (fallback engine) | |
Article/content extraction | |
Fallback HTML extraction | |
HTML to markdown conversion |
Troubleshooting
Browser fails to launch: error while loading shared libraries
Chromium 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 won't start
Camoufox requires xvfb for headed mode:
sudo apt-get install -y xvfbIf Camoufox still fails, it falls back gracefully — Chromium-only mode still works.
MCP server not showing in Claude Code
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.
Tools show "Permission denied"
Add all 7 tools to ~/.claude/settings.json permissions (see Register section above).
Page content is empty or too short
Try
extractwithmode="text"for SPAs/JS-heavy pagesAdd
wait_forparameter with a CSS selector to wait for dynamic contentTry
engine: firefox— some sites respond better to CamoufoxThe default
domcontentloadeddoesn't wait for lazy-loaded content — pass a selector that appears after the page fully renders
Bot-blocked on both engines
If engine: auto falls back to Firefox and still gets blocked, the site may require:
A different IP/proxy (Phase 2)
Manual CAPTCHA solving
Specific cookies/authentication
Session not found
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.
Phase 2 (Planned)
screenshottool — for CAPTCHA/consent debuggingevaluate_jstool — targeted DOM queriessession_infotool — list active sessions and statePer-toolcall hard timeout guard
Proxy/Tor opt-in support
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/Axe240-commits/stealth-browser-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server