A side-by-side look at the Invisible Playwright MCP and MCP Accessibility Scanner MCP servers: transport, auth, tools, quality signals, and the config to install either one in Claude, Cursor, Windsurf or VS Code.
Verdict: choose Invisible Playwright MCP for browser automation work as a local process; choose MCP Accessibility Scanner for browser automation as a local process. You can also run both side by side in one mcpServers block.
browser_open, browser_close, browser_list.scan_page, audit_site, scan_page_matrix.Rows where the two servers differ are marked.
| Spec | ||
|---|---|---|
| Category | Browser Automation | Browser Automation |
| Quality signal | 67/100 · Great | 61/100 · Good |
| Install command | Confirmed | Confirmed |
| Transport | Local subprocess (stdio) | Local subprocess (stdio) |
| Auth | Not declared | Not declared |
| Pricing | Not declared | Not declared |
| Required env vars | None listed | None listed |
| Verified owner | Community listing | Community listing |
| GitHub stars | 2,760 | 57 |
| Directory activity | 1 views 0 installs 0 upvotes | 0 views 0 installs 0 upvotes |
| Tools | 17 | 42 |
| Full listing | View Invisible Playwright MCP | View MCP Accessibility Scanner |
browser_openOpen `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_closeClose 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_listWhich 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_statusWho 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_navigateGo 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_textThe 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_snapshotTitle, 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_htmlThe 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_screenshotOne screenshot of this browser's page, on demand.browser_watchThe 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_clickClick 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_atClick (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.scan_pagePerforms a comprehensive accessibility scan on the current page using Axe-core.audit_siteCrawls and scans multiple internal pages, then aggregates violations across the site.scan_page_matrixRuns Axe scans on the same page across viewport/media/zoom variants and compares deltas against baseline.audit_keyboardAudits real keyboard focus behavior by pressing Tab (and optional Shift+Tab) with practical heuristics.audit_screen_readerAudits what a screen reader actually announces, using the browser's own accessibility tree (`page.ariaSnapshot`) plus element geometry. No screen reader is installed or driven; this is a static reading of the exposed tree.browser_navigateNavigate to a URL.browser_navigate_backGo back to the previous page.browser_navigation_timeoutSet default navigation timeout for existing tabs.browser_default_timeoutSet default operation timeout for existing tabs.browser_snapshotCapture accessibility snapshot of the current page (better than screenshot for analysis).browser_findSearch the current page accessibility snapshot without returning the full snapshot.browser_clickPerform click on a web page element.Paste either block (or both) into your client config, e.g. claude_desktop_config.json or ~/.cursor/mcp.json. We only show a config when the install command is confirmed.
{
"mcpServers": {
"invisible-playwright-mcp": {
"command": "uvx",
"args": [
"invisible-playwright-mcp"
]
}
}
}
{
"mcpServers": {
"mcp-accessibility-scanner": {
"command": "npx",
"args": [
"-y",
"--allow-scripts=re2",
"mcp-accessibility-scanner"
]
}
}
}