invisible_playwright_mcp

io.github.feder-crPublished by GitHub account feder-crv0.73.0Updated Oct 29, 2026

AI browser agent: browses, clicks, types, and reads real web pages from plain-English instructions.

Overview

AI-generated overview

Local MCP server that gives an assistant a stealth Firefox browser to navigate, click, type and read real web pages through human-like pointer and keyboard…

What you can do
  • Open, close and inspect one or two browser identities (main and support), each with its own seed, proxy or profile
  • Navigate to URLs and read the HTTP status and final address
  • Read pages as visible text, cleaned HTML or a snapshot of interactive elements with selectors and coordinates
  • Act on pages: click by selector or coordinate, type, choose dropdown options, press keys, upload local files
  • Capture screenshots and a live window view, and run read-only JavaScript for values other tools cannot see
When to use it
Tasks that need a real browser rather than an API: filling forms, checking fares or availability across dates, reading pages behind consent dialogs, or working through widgets a selector cannot reach. Also useful when a site blocks ordinary automation and a more human-like browser is wanted.
Requirements
Runs locally over stdio via uvx (Python package invisible-playwright-mcp); uv must be installed. The browser engine is downloaded from a GitHub release on first start, and a GeoIP database when a proxy is set. Optional flags or environment variables: OPENROUTER_API_KEY for the standalone web UI, INVISIBLE_MCP_MODEL, proxy, seed, profile directory, engine binary path, INVISIBLE_MCP_HOME for local storage.
Before you install
The server is explicitly built to be invisible to anti-bot systems, which may conflict with site terms; the README asks you to respect terms and rate limits. It can type, upload files and submit forms, so anything sent is real. The standalone UI sends the conversation and page content to OpenRouter under your key; passing the key as a flag exposes it in shell history and the process list. Changing the UI host exposes an interface with no authentication. Each launch contacts GitHub, which sees…

Installation

In SourceWeft

  1. Open invisible_playwright_mcp in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README


Two ways to use this browser agent

1. From your assistant, over MCP

Windows, in PowerShell:

powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"

Linux:

bash
curl -LsSf https://astral.sh/uv/install.sh | shsource $HOME/.local/bin/env

Then install it in your assistant.

Claude Code:

bash
claude plugin marketplace add feder-cr/invisible_playwright_mcpclaude plugin install invisible-playwright-mcp@feder-cr

Codex:

bash
codex plugin marketplace add feder-cr/invisible_playwright_mcpcodex plugin add invisible-playwright-mcp@feder-cr

Gemini CLI:

bash
gemini extensions install https://github.com/feder-cr/invisible_playwright_mcp

2. Standalone: the web UI

We bring the interface, you bring an OpenRouter key. Chat on the left, the live browser on the right.

Windows, in PowerShell:

powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"uvx invisible-playwright-mcp ui --openrouter-key sk-or-...

Linux:

bash
curl -LsSf https://astral.sh/uv/install.sh | shsource $HOME/.local/bin/envuvx invisible-playwright-mcp ui --openrouter-key sk-or-...

Then open http://127.0.0.1:8765 and type the same thing.


What to ask a web browsing agent

Anything that needs real web automation: a browser rather than an API, and a person's judgement about what is on the page.

Go to <paste the URL>. One way, Milan to Lisbon, economy, one checked bag, one adult. Check every date from the 12th to the 16th of next month, one at a time, and read the cheapest fare for each day. The date field is a calendar widget, so click the days rather than typing them. If a date has no availability, say so. Do not guess a number.

It drives the page the way a person would: the pointer moves, keys are pressed.

Options: proxy, profile, seed

  • --openrouter-key Your key, or the OPENROUTER_API_KEY variable.
  • --model An OpenRouter model id, or INVISIBLE_MCP_MODEL. Defaults to z-ai/glm-5.3-flash.
  • --proxy Optional. http://user:[email protected]:8080 or socks5://proxy.example.com:1080. Host and port are both required. The timezone, locale and egress follow it.
  • --binary An engine binary you already have. It must be the build the seal pins, or startup refuses: this skips the download, not the version check.
  • --seed An integer. Same seed, same browser identity, every run.
  • --profile-dir A directory to keep the profile in, so logins and cookies survive restarts.
  • --headed Show the browser window. The interface shows you the page anyway.
  • --host, --port 127.0.0.1 and 8765. Changing the host exposes an interface that has no authentication.

A .env beside the command

Rather than retyping the key and the binary path, put them in a .env in the directory you run from:

OPENROUTER_API_KEY=sk-or-...STEALTHFOX_BINARY=/path/to/firefox

It is read at startup, and on the way in it never overrides something already set, so the order is --flag > the environment > .env > the default. Only the directory you are in is read - there is no search upwards, so running from a subfolder cannot silently pick up a different key. The startup line names the variables it applied and never prints their values.

Passing --openrouter-key puts the key in your shell history, and on Linux in the process list. OPENROUTER_API_KEY in the environment or in a .env avoids both.

The wiki: AI browser-agent guides

The reading room around the agent lives in the wiki: the AI browser-agent landscape: browser-use, Operator-style and computer-use agents compared, what to check when an agent gets blocked, and what happened to OpenAI Operator, among others. Worked examples, transcripts and their outputs live in articles/.

The rest of the family: engine, core

The MCP server from option 1 ships inside this package: invisible_playwright_mcp with no subcommand is the server, invisible-playwright-mcp ui the interface. Its config blocks for clients that take a file, its settings and its tools are on the wiki page The MCP server.

  • invisible_playwright The engine, as a Python library, for writing code instead of prompts. The API is Playwright's.
  • invisible_core Seed to fingerprint to preferences, proxy and geolocation.

Using it responsibly

This automates a browser under your control. Read the terms of the sites you point it at, respect their rate limits, and do not submit anything a human has not read.

Privacy Policy

This browser agent runs on your machine and has no server of its own. What leaves your computer, and to whom:

  • The sites you visit see the browser, as they would any Firefox.
  • Your model provider. The web UI sends the conversation and what the agent reads on the page to OpenRouter under your key. Over MCP, the client you plugged it into does the same with whichever model it uses.
  • GitHub. The engine is downloaded from a GitHub release the first time the server or the interface starts, and a GeoIP database is when a proxy is set. Each browser launch also fetches a one-line counter file from a GitHub release, which is how launches are counted: the request carries no identifier and nothing of yours, and GitHub sees what any HTTPS request shows, your IP address.

Nothing else is collected and nothing is sent to the author. Sessions, profiles and screenshots are stored locally, under INVISIBLE_MCP_HOME if set and otherwise in the application-data directory of your system, and are yours to delete; nothing is retained anywhere else. Questions go to the issues.

License

MIT. Everything distributed before 2 September 2026 was released under AGPL-3.0 and stays under it.

Source: README.md at commit e60cdf2

Tools

17

browser_open

Open a browser

Open `main` or `support`, or reopen one as somebody else. `support` is yours to manage: open it when the task needs a second identity, and close it with browser_close as soon as the task no longer needs it, before you answer. It is not saved. Called on a browser that is already up, this REOPENS it with the settings given, and what it held is gone. seed the identity; same seed, same fingerprint. Left out, one is drawn. profile a directory keeping cookies, logins and the seed between opens; "" means none. It keeps the SEED too, so a login does not come back on different hardware every visit. proxy the exit, `http://user:pass@host:port` or `socks5://host:port`; "" means this machine's own address; left out for `support`, it shares the exit `main` has. A profile does NOT pin its exit, and a login arriving from a new country is as visible as one arriving on new hardware.

Parametersseedproxybrowserprofile

browser_close

Close a browser

Close one browser and free what it was holding. The page it had is gone with it. The other browser is not touched. Who it was is kept: browser_open with no arguments brings the same person back. To be somebody else, pass a seed, a proxy or a profile.

Parametersbrowser

browser_list

List the browsers

Which of the two browsers are open, where each one is, and which one you are working in. Answers JSON: `focus`, the browser your last command acted in, or "" when none is open; `note`, which says how many are open and that a command naming no browser goes to `main`; and `browsers` - each row `id`, `url` (the page it is on) and `urls` (every page it holds, which is more than one only when a site opened one). Only open browsers are listed, so every row is one you can act on. Starts nothing: it reports what is open, so asking is free.

Takes no parameters.

browser_status

Who is browsing

Who is browsing right now: the identity, the exit, the profile and the page. Ask whenever you need to know which person the browser currently is, or from where its traffic leaves. The seed is what you would pass to `browser_open` to become this person again, so this is also how you record an identity worth repeating. It starts nothing: a browser that is not open, or gone, is answered with the sentence that says which.

Parametersbrowser

browser_navigate

Go to a URL

Go to a url in this browser's page, opening it if none exists. Answers with the HTTP status the server gave and the url actually landed on, which is not always the one asked for: a redirect to a login wall or a regional domain shows up here. Read the status before trusting the page - a 404 or a 403 still has a document, and reading it as content is the mistake this reply exists to prevent. If the page you were on navigates itself at that moment (a redirect on a timer), Firefox can follow it instead, and the answer is an error saying "interrupted by another navigation to" a url. The browser is on that url, and you can navigate again from there. wait_until is "domcontentloaded" by default, which returns as soon as the markup is parsed. Use "load" when the page needs its images and stylesheets, or "networkidle" for a single-page app that fetches its content after load.

Parametersurlrequiredbrowserwait_until

browser_read_text

Read the page text

The visible text of an element, with the markup gone, whole. The cheapest way to read a page. Narrow the selector when you know where the answer is; use browser_read_html instead when the structure matters, or browser_snapshot when you need something to click.

Parametersbrowserselector

browser_snapshot

Snapshot the page

Title, url, and the interactive elements that are actually visible. Each element carries a `selector` when one can reach it: pass that string to browser_click or browser_type VERBATIM rather than writing your own. It is built to match exactly ONE element, which the obvious selector often does not, and the driver acts on the first match - so a caller aiming at the third of five identical links would silently hit the first and be told it succeeded. Elements with no `selector` carry `at`, the centre coordinates, for browser_click_at. It lists what a caller can act on, and it is not the accessibility tree: one country `<select>` would otherwise fill the answer with its options before the form you were looking for appears.

Parametersbrowser

browser_read_html

Read the page HTML

The page's HTML, cleaned down to what is worth reading. Use this when the STRUCTURE matters - a form and its labels, a table, what a control is wired to. `browser_snapshot` gives a flat inventory of things to click; this keeps the markup and the relationships inside it. mode="form" keeps the interactive surface and the text explaining it, mode="text" returns the prose alone, mode="full" keeps the structure with the noise and the attribute soup removed. Unlike browser_read_text this is NOT capped: it returns the whole reduced page, tens of thousands of characters on a large one. Cutting markup in the middle leaves tags that mean nothing, so it is not cut - but the answer can be long. Reach for browser_snapshot when you only need something to click.

Parametersmodebrowser

browser_take_screenshot

Take a screenshot

One screenshot of this browser's page, on demand.

Parametersbrowser

browser_watch

Watch the browser window

The whole browser window as a person at the machine sees it: tab strip, address bar, the page and the pointer, from a live capture kept running on that page. For watching the work, not for acting on it: the picture is window pixels, so do not feed its coordinates to browser_click_at; use browser_take_screenshot for that. Starts nothing. A browser that is not open has no window, so this answers the sentence that says so, and the live panes - which call this many times a second - read that sentence as the idle pane.

Parametersbrowser

browser_click

Click an element

Click the first element matching a CSS selector. Scrolls it into view and waits for it to be clickable. When no selector can describe the target, use browser_click_at with coordinates from browser_snapshot.

Parametersbrowserselectorrequired

browser_click_at

Click at a point

Click (or press-and-hold) a raw viewport coordinate instead of a selector - for targets a selector cannot reliably reach: a slider track, a canvas-drawn captcha, a precise point inside a wider element. Moves the pointer there first (no teleport), then down, then up, holding first if hold_seconds is set. Returns a screenshot taken right after release. Coordinates are relative to the VIEWPORT, not to the page, so the ones in a snapshot go stale the moment anything scrolls. Nothing raises when that happens: the click lands on whatever is at that spot now. Take a fresh snapshot after anything that could have moved the page, and prefer browser_click with the element's `selector` whenever it has one.

Parametersxrequiredyrequiredbrowserhold_seconds

browser_type

Type into a field

Type into a field, replacing what it holds, key by key at a human pace. The answer says what the field kept: all of it, a maxlength's cut, the page's reformatting, nothing (the page took it out), or one box of a code the page spreads over several. Nothing is retyped on its own. Text too long to finish within one answer goes on in the background; until it ends, actions on that browser are refused with its progress, and reads work.

Parameterstextrequiredbrowserselectorrequired

browser_select_option

Choose a dropdown option

Choose an option in a dropdown (`<select>`), by its visible label or by its value. Use this rather than clicking the dropdown and pressing arrow keys: a click plus arrows cannot tell you which row it landed on, and setting the value through browser_evaluate changes it without the page seeing a real interaction.

Parametersvaluerequiredbrowserselectorrequired

browser_upload_files

Upload files

Attach local files to a file input, the way a person picks them. `selector` is the `<input type=file>` itself, or the button or label that opens its chooser: that is clicked with the real pointer and the chooser answered with `paths`. A hidden input is opened through its label; with none, pass the button that opens it. Several files need an input that takes several; otherwise upload them one call at a time. Each path is absolute and names a regular file inside a directory listed in INVISIBLE_MCP_UPLOAD_DIRS, through no hidden directory; with none listed, uploads are off. Never use browser_evaluate to set `files`.

Parameterspathsrequiredbrowserselectorrequired

browser_press_key

Press a key

Press a key on whatever has focus: "Enter", "Tab", "Escape", "ArrowDown", "Control+a", or a single character.

Parameterskeyrequiredbrowser

browser_evaluate

Read the page with JavaScript

READ from the page with JavaScript and get the result as JSON. Last, for what other tools cannot see: a computed style, a value held in a framework's state, the length of a list. Text, a heading or a price is browser_read_text's, markup browser_read_html's, a control browser_snapshot's: a script runs in the page, and they do not. Acting on the page is refused, and the refusal names the tool to use. Assigning to `value`, `checked` or `selected`, or calling `click()`, `dispatchEvent()`, `submit()` or `requestSubmit()`, changes the page without a real keystroke or pointer, and a page can tell. Use browser_click, browser_type or browser_select_option instead; they do the same thing through the pointer and the keyboard. Reading any of those properties is fine. The refusal catches the obvious spellings, not every possible one. A script that slips past it is still the wrong way to do the thing: report it in your answer rather than using it.

Parametersbrowserexpressionrequired

Version history

20
  1. v0.73.0LatestOct 21, 2026
  2. v0.72.3Oct 17, 2026
  3. v0.72.2Oct 13, 2026
  4. v0.72.1Oct 12, 2026
  5. v0.72.0Oct 12, 2026
  6. v0.71.2Oct 11, 2026
  7. v0.71.1Oct 11, 2026
  8. v0.71.0Oct 10, 2026
  9. v0.70.17Oct 10, 2026
  10. v0.70.16Oct 9, 2026
  11. v0.70.15Oct 9, 2026
  12. v0.70.14Oct 9, 2026
  13. v0.70.13Oct 8, 2026
  14. v0.70.12Oct 6, 2026
  15. v0.70.11Oct 5, 2026
  16. v0.70.10Oct 4, 2026
  17. v0.70.9Oct 3, 2026
  18. v0.70.8Oct 2, 2026
  19. v0.70.2Sep 25, 2026
  20. v0.70.0Sep 23, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts