# invisible_playwright_mcp [Health: Active]

**Category:** 📂 Browser Automation  
**Repository:** https://github.com/feder-cr/invisible_playwright_mcp  
**GitHub Stars:** 878  
**Views:** 5  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/invisible-playwright-mcp

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

## Tools
Capabilities this server exposes over MCP:

- **browser_open** — 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.
    
- **browser_close** — 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.
    
- **browser_list** — 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.
    
- **browser_status** — 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.
    
- **browser_navigate** — 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.
- **browser_read_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.
- **browser_snapshot** — 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.
    
- **browser_read_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.
    
- **browser_take_screenshot** — One screenshot of this browser's page, on demand.
- **browser_watch** — 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.
- **browser_click** — 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.
- **browser_click_at** — 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.
- **browser_type** — 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.
- **browser_select_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.
- **browser_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`.
- **browser_press_key** — Press a key on whatever has focus: "Enter", "Tab", "Escape",
    "ArrowDown", "Control+a", or a single character.
- **browser_evaluate** — 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.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "invisible-playwright-mcp": {
    "command": "uvx",
    "args": ["invisible-playwright-mcp"]
  }
}
```

## Documentation

## What invisible_playwright_mcp does

invisible_playwright_mcp exposes a browser agent through the Model Context Protocol. It opens real web pages, follows redirects, inspects visible controls, enters text, selects options, uploads files, presses keys, and reads rendered content. The browser operates through pointer and keyboard interactions rather than directly changing page state, which suits workflows where a page must be used as a person would use it.

Typical tasks include checking information across several dates, completing web forms, navigating sites whose content is loaded dynamically, and reviewing the result of an interaction. The package also offers a standalone local web interface for chatting with the agent and viewing the live browser.

## How it works

The server maintains up to two browser identities: `main` and `support`. The support browser is useful when a task needs a second identity and can be closed when it is no longer needed. Each browser can use a seed, profile directory, and proxy. A seed provides a repeatable identity, while a profile preserves cookies, logins, and the seed between runs. Proxy settings affect the browser's network exit and related locale and timezone behavior.

Use `browser_snapshot` to obtain the visible interactive elements and their exact selectors. Use `browser_click_at` only when a coordinate is needed, since viewport coordinates can become stale after scrolling or other page movement. `browser_read_text` is suited to visible content, while `browser_read_html` preserves useful structure such as forms and tables. Navigation reports the resulting HTTP status and final URL, so redirects, login walls, errors, and regional domains can be checked before reading the page.

`browser_evaluate` is restricted to read-only JavaScript inspection. Page changes should use the interaction tools so the site receives actual keyboard or pointer input.

## Setup and configuration

Install Astral `uv` first. The package runs as an MCP server without a subcommand, while `invisible-playwright-mcp ui` starts the standalone interface. The documented client installation paths include Claude Code, Codex, and Gemini CLI. The web UI is available at `http://127.0.0.1:8765` by default and requires an OpenRouter API key.

Supported options include:

- `--openrouter-key` or `OPENROUTER_API_KEY` for the web UI
- `--model` or `INVISIBLE_MCP_MODEL` for the OpenRouter model
- `--proxy`, `--seed`, and `--profile-dir` for browser identity and routing
- `--binary` for an existing compatible browser engine
- `--headed` to show the browser window
- `--host` and `--port` to change the local interface address

A `.env` file in the working directory can supply configuration. Command-line flags take precedence over environment variables, which take precedence over `.env` values. Uploads require absolute file paths within directories listed by `INVISIBLE_MCP_UPLOAD_DIRS`.

## Tools and capabilities

- Open, close, list, and inspect browser identities
- Navigate to URLs and choose page-load wait conditions
- Read visible text or cleaned HTML
- Inspect visible interactive elements and exact selectors
- Click selectors or viewport coordinates
- Type text, press keys, and select dropdown options
- Upload local files through file inputs
- Take screenshots or watch the live browser window
- Read page state through restricted JavaScript evaluation

## Limitations and notes

The browser runs locally, and sessions, profiles, and screenshots remain on the machine under `INVISIBLE_MCP_HOME` or the system application-data directory. Visited sites receive normal browser traffic. The web UI sends the conversation and page content read by the agent to OpenRouter; MCP usage sends it through the model provider selected by the connected client. The first startup downloads the engine from a GitHub release, and proxy use can also trigger a GeoIP database download.

Changing the web UI host exposes an interface without authentication. Coordinate clicks should be preceded by a fresh snapshot after page movement. Large HTML responses are not truncated, so reading an entire complex page can produce a long result. Users should follow site terms and rate limits and avoid submitting content that has not been reviewed by a person.

The current license is MIT. Material distributed before 2 September 2026 remains under AGPL-3.0.

_Full upstream README: https://allmcps.com/mcp/invisible-playwright-mcp/readme_

