Skip to main content

invisible-playwright-mcp

A stealth Firefox, exposed as an MCP server, so an AI agent can browse and act on real websites without being blocked by anti-bot systems.

It wraps invisible-playwright, a real Firefox patched at the C++ source, and hands your client the usual browser tools: navigate, click, type, read, screenshot. The fingerprint is set inside the engine, not bolted onto the page.

First, one prerequisite

Python 3.11 or newer, on Windows (x86_64) or Linux (x86_64, arm64) - macOS is not supported, the last engine build for it was firefox-20. Then uv, because the command below starts with uvx:

curl -LsSf https://astral.sh/uv/install.sh | sh              # Linux
irm https://astral.sh/uv/install.ps1 | iex                   # Windows

Then pick one

Do you already use an AI assistant that can run tools? If yes, it brings the model and you add this browser to it. If no, or if you would rather watch the work happen, there is an interface that brings a model too.

1. Add it to the assistant you have

Claude Code:

claude mcp add --scope user stealth -- uvx invisible-playwright-mcp

Then check it took, before trusting it:

claude mcp list

For every other client, see Adding it to your client below.

2. Run the interface instead

AIHawk. Bring an OpenRouter account and its key, get a page with the chat on the left and the live browser on the right.

uvx aihawk ui --openrouter-key sk-or-...

Open http://127.0.0.1:8765.

It is a client of this server like any other, with no private path to the page - which is the reason to believe the tools below are enough to build on.

The download nobody warns you about

The browser is about a quarter of a gigabyte and it is not fetched when the server is installed, nor when it starts. It arrives on the first tool call that needs a page, so the first thing you ask your assistant to do sits there, and on a slow connection you get a timeout that says nothing about a download.

Get it over with first, in a terminal where you can watch it:

uvx invisible-playwright fetch

Cached afterwards, and shared with anything else that uses this engine. Per-platform sizes are in the engine's README.

Adding it to your client

Four clients have a command for this. The rest take a config file, and the file is not the same everywhere: three different top-level keys, and one of them is not even JSON. Find yours below.

If your client has a command

Claude Code. --scope user because the default scope is the current project, so without it the tools do not appear anywhere else and nothing reports an error:

claude mcp add --scope user stealth -- uvx invisible-playwright-mcp

Codex:

codex mcp add stealth -- uvx invisible-playwright-mcp

Gemini CLI. No -- separator here, unlike the two above:

gemini mcp add --scope user stealth uvx invisible-playwright-mcp

VS Code (GitHub Copilot agent mode):

code --add-mcp "{\"name\":\"stealth\",\"command\":\"uvx\",\"args\":[\"invisible-playwright-mcp\"]}"

If your client takes a config file

Most use a top-level mcpServers - Claude Desktop, Cursor, Windsurf, Cline:

{
  "mcpServers": {
    "stealth": {
      "command": "uvx",
      "args": ["invisible-playwright-mcp"]
    }
  }
}
Client File
Claude Desktop (macOS) ~/Library/Application Support/Claude/claude_desktop_config.json
Claude Desktop (Windows) %APPDATA%\Claude\claude_desktop_config.json
Cursor .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project
Windsurf ~/.codeium/windsurf/mcp_config.json
Cline ~/.cline/data/settings/cline_mcp_settings.json, or the Configure MCP Servers button in its MCP panel, which opens whichever file your version uses

Zed calls the key context_servers, not mcpServers, in ~/.config/zed/settings.json (%APPDATA%\Zed\settings.json on Windows):

{
  "context_servers": {
    "stealth": {
      "command": "uvx",
      "args": ["invisible-playwright-mcp"]
    }
  }
}

VS Code calls it servers, in .vscode/mcp.json for a workspace:

{
  "servers": {
    "stealth": {
      "type": "stdio",
      "command": "uvx",
      "args": ["invisible-playwright-mcp"]
    }
  }
}

Codex uses TOML, in ~/.codex/config.toml:

[mcp_servers.stealth]
command = "uvx"
args = ["invisible-playwright-mcp"]

Continue uses YAML with its own block format, which changed recently enough that we would rather point you at their documentation than print a block here that may already be stale.

Where a proxy and the other settings go

Everything in Settings below goes under env on the server entry, in whatever shape your client uses:

{
  "mcpServers": {
    "stealth": {
      "command": "uvx",
      "args": ["invisible-playwright-mcp"],
      "env": {
        "STEALTHFOX_PROXY": "http://user:pass@proxy.example.com:8080",
        "STEALTHFOX_SEED": "4242"
      }
    }
  }
}

In Codex's TOML that is a [mcp_servers.stealth.env] table; on the command line, Claude Code and Codex take -e KEY=value and --env KEY=value.

"Added" is not "connected". Every one of these writes a config entry without running anything, so a typo, a missing uv, or the first-run browser download all surface later as a server that will not start. Check before you trust it: claude mcp list, codex mcp list, or your client's MCP panel.

Settings

Environment variables, all optional. A proxy is the one worth adding: without it the exit IP, timezone and locale are your own machine's, which is a real gap between what the browser says it is and where it appears to be.

Variable Meaning
STEALTHFOX_PROXY Proxy URL, e.g. http://user:pass@proxy.example.com:8080 or socks5://proxy.example.com:1080. Host and port are both required. Bring your own. With it set, the session's timezone, locale and egress are derived from the proxy.
STEALTHFOX_NO_PROXY 1 to go out from this machine's own address even when STEALTHFOX_PROXY is set.
STEALTHFOX_SEED Integer seed for a deterministic fingerprint (same seed, same identity). A profile's own seed wins over this one.
STEALTHFOX_PROFILE_DIR A directory for a persistent profile, so logins survive across runs.
STEALTHFOX_BINARY Path to an engine binary you already have. It must be the build the packaged seal pins, or startup refuses.
STEALTHFOX_HEADLESS 0 to run headed; headless by default.
STEALTHFOX_MCP_TRANSPORT http to serve over streamable HTTP instead of stdio. Default is stdio, which is what MCP clients expect.
STEALTHFOX_MCP_HOST Bind address for the HTTP transport. Default 127.0.0.1.
STEALTHFOX_MCP_PORT Port for the HTTP transport. Default 8765, which is also the AIHawk interface's default: change one of the two if you run both.

Tools

session_start, session_status, session_new_page, session_list_pages, session_select_page, session_close_page, browser_navigate, browser_read_text, browser_snapshot, browser_read_html, browser_click, browser_click_at, browser_type, browser_press_key, browser_evaluate, browser_take_screenshot, browser_select_option.

Who is browsing

You can ignore this entirely. Say nothing and every session is a different stranger, which is the right default.

When you do care, session_start chooses the person and session_status tells you who you currently are. There is one browser, so two identities are visited in turn rather than at once.

  • seed is the identity. The same seed is the same fingerprint every time. Leave it out and one is drawn for you, and the answer says which, so a session worth repeating can be repeated.
  • profile is a directory holding cookies and logins. A profile also owns its seed: the first session on a new one stores the identity inside it and every session after reuses it, so a login never comes back wearing different hardware. Ask for a seed that contradicts the one a profile carries and you get a refusal naming both numbers, never a silent choice between them.
  • proxy is where the traffic leaves. Pass "" to either of the last two to insist on none, which is how you get sessions a site cannot link together even when the environment sets a default.

A profile does not own its exit the way it owns its seed. Timezone, locale and geography come from the exit, so the same login arriving from another country is as visible as one arriving on different hardware. You are warned when a profile's exit changes - but only when you change it. A provider rotating its own addresses behind one host and port is indistinguishable from here, and no warning should be read as saying otherwise.

The order to try them in

The server hands every client this ladder, because a model that cannot find a way down it invents one:

  1. A named tool with a selector - browser_click, browser_type, browser_select_option, browser_press_key. browser_snapshot supplies the selector.
  2. Coordinates - the snapshot reports at: [x, y] for every element, and browser_click_at moves the pointer there. For a canvas, a slider, a map, a widget built out of divs.
  3. A screenshot - browser_take_screenshot, then browser_click_at on what you can see. For what the snapshot does not list at all.
  4. browser_evaluate, to READ what none of the above can see.

browser_evaluate reads; it will not act. Assigning to value, checked or selected, or calling click(), dispatchEvent() or submit(), is refused, and the refusal names the tool to use instead. Script reaches the page with no keystroke and no pointer, so the event carries isTrusted false, which is the clearest signal a page can collect that nobody is really there. Reading those properties is fine.

browser_click_at takes viewport coordinates instead of a selector, moves the pointer there rather than teleporting, and optionally holds before releasing - for a slider, a canvas-drawn challenge, or a press-and-hold.

browser_snapshot returns the title, the url and the visible interactive elements rather than the accessibility tree. Every element carries at: [x, y], its centre in the viewport, and a selector as well when one can reach it - pass that straight to browser_click or browser_type. browser_read_html returns reduced markup instead, for when the structure is what matters.

Tool names mirror the Microsoft Playwright MCP, so prompts written for it work here too.

Why each of those returns what it does, with the measurements behind it: docs/tool-design.md.

More than one client on the same browser

Over stdio the browser belongs to the client that opened it. Set STEALTHFOX_MCP_TRANSPORT=http and it does not: the session is owned by the server, so a second client can attach to the browser the first one left open, and closing a client no longer kills the browser.

To SEE the browser rather than share it, use AIHawk from column 2, which shows the live page beside the conversation.

STEALTHFOX_MCP_TRANSPORT=http uvx invisible-playwright-mcp        # Linux
$env:STEALTHFOX_MCP_TRANSPORT = "http"; uvx invisible-playwright-mcp   # Windows

Notes

  • This is a browser, not a captcha solver. It does not solve or bypass challenges for you; it makes an ordinary Firefox session look like a real one.

License

MIT, the same as the engine it wraps.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

invisible_playwright_mcp-0.14.0.tar.gz (112.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

invisible_playwright_mcp-0.14.0-py3-none-any.whl (54.5 kB view details)

Uploaded Python 3

File details

Details for the file invisible_playwright_mcp-0.14.0.tar.gz.

File metadata

File hashes

Hashes for invisible_playwright_mcp-0.14.0.tar.gz
Algorithm Hash digest
SHA256 3da3ce8f84999c3e24c3e05f2fb9bdb5177bb4b12e7f305d61cb3b72b2f3850f
MD5 fb0d94134e315ce96f27e79c848f2cb9
BLAKE2b-256 6ba25b223882912be78aef92fea5f174c4ae415952eb2d6ec6a52407319ba735

See more details on using hashes here.

Provenance

The following attestation bundles were made for invisible_playwright_mcp-0.14.0.tar.gz:

Publisher: publish.yml on feder-cr/invisible-playwright-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file invisible_playwright_mcp-0.14.0-py3-none-any.whl.

File metadata

File hashes

Hashes for invisible_playwright_mcp-0.14.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b94df26603c4061c3fdedf1b310c92576649cb821a774342cef438ba18436bbb
MD5 e42e20b657382e5bf1e8b7d3bf8c9a05
BLAKE2b-256 932ef88c7f366b5918860f363f8a790ddfaea39198cade1d823c6777758e0de9

See more details on using hashes here.

Provenance

The following attestation bundles were made for invisible_playwright_mcp-0.14.0-py3-none-any.whl:

Publisher: publish.yml on feder-cr/invisible-playwright-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.15.0

2 files

This release

0.14.0 This release

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page