An MCP server that exposes invisible_playwright — a patched stealth Firefox that beats reCAPTCHA v3, FingerprintPro, CreepJS, and the rest — to any MCP client (Claude Code, Claude Desktop, Cursor, Cherry Studio, …).
Tool surface mirrors Microsoft's @playwright/mcp: same names, same (element, target) ref pattern, drop-in for any agent already using it. Adds multi-session support so one agent can drive several independent fingerprints in parallel.
- 34 MCP tools covering navigation, interaction, snapshot/screenshot, content extraction, cookies, downloads, tabs, dialogs, network/console inspection, coordinate-based mouse, and session management.
- Stealth by default. Every session gets a unique coherent fingerprint sampled from real Firefox telemetry;
.toString()is clean because the patches are in Gecko C++, not in injected JS. - Persistent login. Pass
profile_dirtobrowser_context_createto keep cookies/history/localStorage across sessions — re-opening with the same path restores everything. - Multi-session.
browser_context_createspawns isolated browsers with their own seed/proxy/locale; passcontext_idto any tool to target a specific one. - Full fingerprint pinning.
pindict lets you override any seed-derived value (gpu.renderer,screen.width,hardware.concurrency, audio/canvas/font knobs, …);extra_prefsaccepts any raw Firefox preference. - Snapshot-driven refs.
browser_snapshottags interactable elements with[ref=eN]and returns a markdown outline. Pass that ref tobrowser_click/browser_type/etc. — no XPath, no fragile selectors.
Requirements: Python 3.11+, Windows x86_64 or Linux x86_64 (macOS not supported — upstream invisible_playwright constraint).
pip install git+https://github.com/ColorSource/invisible_playwright_mcp.gitThat's it. The patched invisible_playwright package is pulled in as a dependency. The ~100 MB Firefox binary is downloaded lazily on the first browser_context_create (or first auto-created default session), SHA256-verified, and cached at %LOCALAPPDATA%\invisible-playwright\Cache\ (Windows) or ~/.cache/invisible-playwright/ (Linux). Pre-fetch it if you want to avoid the wait on first call:
python -m invisible_playwright fetchAdd to ~/.claude.json (or %USERPROFILE%\.claude.json on Windows) under mcpServers:
{
"mcpServers": {
"invisible_playwright": {
"command": "python",
"args": ["-m", "invisible_playwright_mcp"]
}
}
}Or run once:
claude mcp add invisible_playwright -- python -m invisible_playwright_mcpDrop examples/claude_desktop_config.json into your Claude Desktop config (%APPDATA%\Claude\claude_desktop_config.json on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS). Restart the app.
Same pattern — command python, args ["-m", "invisible_playwright_mcp"], transport stdio.
| Group | Tool | Purpose |
|---|---|---|
| Session | browser_context_create |
Start a new isolated stealth browser — seed, proxy_*, locale, timezone, headless, humanize, profile_dir, full pin dict, extra_prefs |
browser_context_list |
Show all live sessions | |
browser_context_close |
Close a session (preserves profile_dir on disk if used) | |
| Navigation | browser_navigate / _back / _forward / browser_reload |
URL nav + history |
browser_wait_for |
Wait for text to appear / disappear / fixed time | |
| Snapshot | browser_snapshot |
Markdown tree of interactable elements with [ref=eN] |
browser_take_screenshot |
PNG/JPEG of page or single element | |
browser_evaluate |
Run JS on page or element | |
| Content | browser_get_text |
Visible text of page or element |
browser_get_html |
Raw HTML (inner or outer) | |
| Interaction | browser_click / browser_hover / browser_type |
Standard actions, target by ref or selector |
browser_drag |
Drag-drop between two refs | |
browser_press_key |
Keyboard press | |
browser_select_option |
<select> value(s) |
|
browser_fill_form |
Multi-field form fill | |
browser_file_upload |
Upload files to <input type=file> |
|
| Cookies | browser_get_cookies / _set_cookies / _clear_cookies |
Round-trip login state between sessions |
| Downloads | browser_downloads_list |
List files auto-captured to per-session temp dir |
| Tabs/Dialogs | browser_tabs |
list/new/close/select |
browser_handle_dialog |
Arm a one-shot alert/confirm/prompt handler | |
| Observability | browser_network_requests / browser_network_request |
List + drill into HTTP traffic |
browser_console_messages |
Drain captured console logs | |
| Window/Mouse | browser_resize |
Viewport size |
browser_mouse_click_xy / _move_xy / _drag_xy |
Coordinate-level mouse |
Every interaction/capture tool accepts an optional context_id parameter (default "default"). The default context is created lazily on first use.
Note: PDF export (
page.pdf()) is Chromium-only in Playwright — Firefox does not support it, so we don't expose a PDF tool. Usebrowser_take_screenshot(full_page=true)for a visual capture.
1. browser_navigate(url="https://abrahamjuliot.github.io/creepjs/")
2. browser_wait_for(time=5) # let the page finish profiling
3. browser_snapshot() # find the "Lies" element ref
→ "- div \"0 lies\" [ref=e12]"
4. browser_take_screenshot(full_page=true) # confirm visually
browser_context_create(
context_id="github",
profile_dir="C:/profiles/github_account_a",
seed=42
)
browser_navigate(url="https://github.com/login", context_id="github")
# ... user logs in once ...
browser_context_close(context_id="github")
# Next session: same login still there
browser_context_create(context_id="github", profile_dir="C:/profiles/github_account_a", seed=42)
browser_navigate(url="https://github.com/dashboard", context_id="github") # logged in
browser_context_create(context_id="acct_a", seed=1, proxy_server="socks5://gate:1080", proxy_username="u1", proxy_password="p1")
browser_context_create(context_id="acct_b", seed=2, proxy_server="socks5://gate:1080", proxy_username="u2", proxy_password="p2")
browser_navigate(url="...", context_id="acct_a")
browser_navigate(url="...", context_id="acct_b")
The whole point is that no Gecko-level guarantee leaks when you wrap it. To sanity-check end-to-end:
browser_navigate(url="https://bot.sannysoft.com")
browser_take_screenshot(full_page=true)
# every row should be green
browser_navigate(url="https://fingerprint.com/products/bot-detection/")
browser_evaluate(function="() => document.body.innerText")
# 'bot detected: not detected'
MCP client (Claude Code, Cursor, …)
│ stdio JSON-RPC
▼
FastMCP server (server.py)
│
▼
BrowserContextManager ← context_id → SessionState
│
▼
invisible_playwright.async_api.InvisiblePlaywright
│
▼
patched Firefox 150 (C++ level fingerprint spoofing)
Ref resolution: browser_snapshot tags interactable DOM elements with data-mcp-ref="eN" via JS injection, stores page.locator('[data-mcp-ref="eN"]') keyed by ref. Interaction tools' target parameter accepts either a ref ("e3") or any Playwright selector. Refs are cleared on every snapshot and on navigation.
MIT. Bundles no Firefox binary — invisible_playwright fetch retrieves it from the upstream release.