An MCP server that exposes
invisible_playwright — an
anti-detect Firefox with patched fingerprint surfaces — to LLMs.
Your assistant can launch a fingerprint-randomized browser, navigate, click, type, read the DOM, and take screenshots through it, using the standard Playwright surface, but with a patched Firefox engine under the hood.
The patched Firefox binary is a one-time ~100 MB download (
python -m invisible_playwright fetch, or thefetch_binarytool).
invisible_playwrightis a drop-in Playwright replacement that patches Firefox at the C++ level (navigator, GPU/WebGL, canvas, fonts, audio, WebRTC, timezone, network) and humanizes mouse motion in the driver.- An MCP server is a resident process, which is exactly what a stateful
browser needs. This server keeps long-lived sessions in-process; each
session owns an
InvisiblePlaywrightinstance plus one or more pages. Tools operate on the session's active page unless told otherwise.
LLM ──MCP/stdio──► server ──► InvisiblePlaywright ──► patched Firefox
▲ (async API)
sessions held in-process
pip install -e . # or: pip install spectra-mcp
python -m invisible_playwright fetch # one-time ~100 MB, SHA256-verifiedRequires Python 3.11+. Supported platforms: Windows x86_64, Linux x86_64/arm64, macOS arm64/x86_64.
On macOS the app is ad-hoc signed (not notarized): if Gatekeeper complains, clear the quarantine flag once with
xattr -dr com.apple.quarantineon the cachedFirefox.app.
{
"mcpServers": {
"spectra-mcp": {
"command": "spectra-mcp"
}
}
}{
"mcpServers": {
"spectra-mcp": {
"command": "spectra-mcp"
}
}
}If the console script isn't on PATH, use the module form instead:
{ "command": "python", "args": ["-m", "spectra_mcp"] }fetch_binary(once) — download the patched Firefox ifbinary_statussaysready: false.start_session→ returnssession_id,seed, and the initialpage_id. Pass aproxy_serverfor residential/socks proxies.gotoa URL, thenquery_elements/get_text/screenshotto understand the page.click/fill/press_keyto act. Mouse motion is humanized.close_sessionwhen done (or just let the server shut down — the lifespan cleans up all Firefox processes).
Log the seed to replay an identical fingerprint later (start_session with
the same seed).
Tool exposure is selected at server startup with SPECTRA_MCP_TOOL_PROFILE:
| Profile | Tools | Approx. tool tokens | Intended use |
|---|---|---|---|
setup |
2 | ~230 | Binary status/download only. |
core |
28 | ~4,165 | Default. Setup plus normal browsing, reading, forms, tabs, screenshots and evaluate. |
full |
55 | ~7,942 | Every advanced mouse, keyboard, dialog, cookie/storage and frame tool. |
Token estimates use the o200k_base tokenizer and an OpenAI-style function-tool
payload. Exact numbers vary by MCP client and whether output schemas are included.
core includes session lifecycle, page switching/popups, goto/reload,
click/fill/type_text/press_key, scrolling/hover/selects, text/HTML/
attribute/element reads, visibility, screenshots, waits and evaluate.
SPECTRA_MCP_TOOL_PROFILE=core spectra-mcp
SPECTRA_MCP_TOOL_PROFILE=full spectra-mcpPowerShell equivalent:
$env:SPECTRA_MCP_TOOL_PROFILE = "full"
spectra-mcpThe server must be restarted after changing profiles. The reference below lists
the complete full profile; disabled functions remain available internally for
tests but are not advertised to the model.
| Tool | Description |
|---|---|
binary_status |
Is the patched Firefox cached? Does not download. |
fetch_binary |
Download + SHA256-verify the binary (~100 MB, one-time). force=True to re-download. |
| Tool | Description |
|---|---|
start_session |
Launch a browser with a fresh/seeded fingerprint. Returns session_id, seed, page_id. |
close_session |
Close a session and free its Firefox process. |
list_sessions |
Active session ids. |
session_info |
Session metadata, pages, active page id, URL and title. |
| Tool | Description |
|---|---|
new_page |
Open a new tab; becomes the active page. |
close_page |
Close a page (active by default). |
switch_page |
Change the active page. |
goto, go_forward, reload
click, fill, type_text, press_key, keyboard_press, select_option,
hover, focus, check, uncheck, scroll
get_text, get_html, get_attribute, query_elements, is_visible
query_elements returns a structured snapshot (tag, id, name, type, role, href,
text, value, placeholder, visible, bounding rect) so the LLM can decide what to
click without scraping raw HTML.
screenshot → returns image content directly to the LLM. Defaults to JPEG
(quality=85) to keep payloads small; image_format="png" for lossless.
full_page=True captures the whole scrollable page.
wait_for_selector, wait_for_timeout
wait_for_selector returns reached=true when the requested state is reached;
element_present reports whether an element handle still exists (it is normally
false after successful hidden / detached waits).
evaluate — run arbitrary JavaScript in the page and return its value. Powerful;
use sparingly.
mouse_drag, mouse_move, mouse_click
mouse_drag is intended for sliders, canvas handles and similar coordinate-based
controls. With the current firefox-15 patched binary, hold-drag requires a
session started with humanize=False.
keyboard_down, keyboard_up, keyboard_type
Use keyboard_down / keyboard_up for modifier-key combinations. Always release
keys that were explicitly held.
set_dialog_handler, get_dialogs, wait_for_page
Dialogs are handled automatically according to the session policy so they cannot freeze an in-flight action. Popups are adopted into the session without changing the active page.
get_cookies, add_cookies, clear_cookies, save_storage_state
Storage state contains cookies and localStorage. Treat saved files as secrets and
resume them with start_session(storage_state_path=...). Files are written via a
private temporary file and atomically replaced; on Unix the final mode is 0600.
list_frames, frame_click, frame_fill, frame_type, frame_get_text,
frame_wait_for_selector, frame_query_elements, frame_evaluate,
frame_get_html, frame_get_attribute
list_frames returns a unique selector for each top-level iframe. Chain selectors
with >>> to address nested iframes.
| Param | Default | Notes |
|---|---|---|
seed |
random | Reproducible fingerprint when set. |
headless |
true |
Windows/macOS self-cloak the window. |
proxy_server |
"" |
socks5://host:1080, http://..., etc. DNS routes via proxy. |
proxy_username / proxy_password |
"" |
Proxy auth. |
timezone |
"" |
IANA zone; empty = auto from egress IP. |
locale |
"auto" |
auto = from egress country, or e.g. en-US. |
humanize |
true |
Bezier mouse paths + human timing. |
profile_dir |
"" |
Persistent profile path (enables persistent context). |
prep_recaptcha |
false |
Pre-seed reCAPTCHA cookies (non-persistent only). |
storage_state_path |
"" |
JSON file created by save_storage_state; cannot be combined with profile_dir. |
The resident server applies hard limits so a single tool call cannot exhaust the browser host or the stdio/MCP channel. Defaults can be changed with environment variables:
SPECTRA_MCP_MAX_SESSIONS=8SPECTRA_MCP_MAX_PAGES_PER_SESSION=32SPECTRA_MCP_MAX_TEXT_CHARS=200000SPECTRA_MCP_MAX_ELEMENT_RESULTS=500SPECTRA_MCP_MAX_TIMEOUT_MS=300000SPECTRA_MCP_MAX_DELAY_MS=10000SPECTRA_MCP_MAX_CLICK_COUNT=10SPECTRA_MCP_MAX_MOUSE_STEPS=1000SPECTRA_MCP_MAX_SCREENSHOT_BYTES=10485760SPECTRA_MCP_MAX_SCREENSHOT_PIXELS=50000000SPECTRA_MCP_MAX_EVALUATE_RESULT_BYTES=1048576
Set SPECTRA_MCP_DATA_ROOT to restrict profile_dir, storage-state input, and
storage-state output to one filesystem tree. When it is unset, arbitrary local
paths remain available for trusted local MCP use.
timeout_ms=0 keeps Playwright's conventional “no timeout” meaning. Negative
timeouts and positive values above the configured maximum are rejected.
fetch_binary(force=true) is rejected while sessions are active or cleaning up;
binary launch/download/cache mutation is serialized with a cross-process lock.
- IP matters. The browser fingerprint is handled; ~90% of captchas that remain are from known/blocked proxy IPs. Use clean residential IPs.
- First
start_sessionneeds network to resolve geo/timezone (and to download the binary if you skippedfetch_binary). - Humanized clicks are slower than vanilla Playwright — raise
timeout_msif needed. - Coordinate hold-drag requires
humanize=Falseon the current patched Firefox build. - File chooser, browser download events, selector drag-and-drop and back
navigation are intentionally not exposed because
firefox-15does not support them reliably. Useevaluate-based workflows when appropriate. evaluateruns arbitrary JS — only enable for trusted use.- All logging goes to stderr so it never corrupts the stdio JSON-RPC stream.
- Calls against a single session must be sequential (the natural LLM pattern: the model waits for each tool result before sending the next). Concurrent calls to one session are not supported.
The regression suite uses only Python's standard library:
python -m unittest discover -s tests -v
python -m compileall -q src testsReal-browser integration checks are opt-in because they launch the patched Firefox and may require network access for session geo initialization:
SPECTRA_MCP_RUN_INTEGRATION=1 python -m unittest tests.test_integration -vMIT. The patched Firefox binary is MPL-2.0 (Firefox upstream). This wrapper does not redistribute the binary; it downloads it on demand from the upstream releases.
{ "mcp": { "spectra-mcp": { "type": "local", "command": ["spectra-mcp"], "enabled": true } } }