MCP server · works with any client

The same page, forty-four times smaller.

Playwright was built to test pages you wrote, where you already know the selectors. An agent is working pages it has never seen — and the snapshot Playwright's MCP server hands it to decide what to click is 241,988 characters for one Wikipedia article. That is so large the server writes it to a file rather than returning it. Here is that page both ways, at the same moment.

Playwright MCP snapshot 241,988 chars
- generic [active] [ref=e1]: - link "Jump to content" [ref=e2] [cursor=pointer]: - /url: "#bodyContent" - banner [ref=e4]: - generic [ref=e5]: - navigation "Site" [ref=e6]: - generic "Main menu" [ref=e7]: - button "Main menu" [ref=e8] [cursor=pointer] - generic [ref=e11]: Main menu - link [ref=e12] [cursor=pointer]: - /url: /wiki/Main_Page - generic [ref=e13]: - img "Wikipedia" [ref=e14]
~61,600 tokens. Still going, thousands of lines later.
agent-browser 3,877 chars
Accessibility - Wikipedia | https://en.wikipedia.org/wiki/Accessibility [e1] button "Main menu" [e2] link "" form "searchform": [e3] search "Search Wikipedia" [e4] button "Search" [e5] link "Donate" [e6] link "Create account" [e7] link "Log in" ## Contents [e8] button "Hide Contents" [e9] link "(Top)" [e10] link "Legislation"
~1,100 tokens. That is the whole answer.
$ claude mcp add agent-browser -- npx -y @rebelstudios/agent-browser
MIT · Node 20+ · any MCP client · source on GitHub · npm

Measured, not asserted

Five live pages, both routes, same run

The left column is page._snapshotForAI() — the exact string Playwright's MCP server sends a model, checked against the live server. Each route gets its own browser and navigation, so neither warms a cache for the other. npm run bench reproduces this; run it before believing it.

PagePlaywright MCPagent-browserSmaller
en.wikipedia.org/wiki/Accessibility241,9883,87762.4×
developer.mozilla.org · fetch167,5992,66762.8×
news.ycombinator.com57,4471,74033.0×
playwright.dev/docs/intro37,2962,90512.8×
example.com3152081.5×
Total · 5 pages504,64511,39744.3×

example.com stays in the set at 1.5×. A page with nothing on it is the floor, and a benchmark that drops its worst case is advertising. Time was 5.8s against 3.5s across the five — 1.7×, because every page here reaches network idle quickly; the large time wins are on pages that never go idle at all, and this set doesn't contain one yet. An earlier run of this benchmark measured ariaSnapshot() instead and reported 19.9×; that omits the [ref] annotations MCP adds, and understated the gap by nearly half. Measured 25 Sep 2026.

Why it is smaller, and what else came out of it

Every row here cost someone an afternoon first

The page description floods the context
A compact outline instead: headings, forms and dialogs as groups, each control as [e12] button "Next" (disabled), navigation and footers collapsed to six links and a count.
Element refs go stale after a re-render
Refs live on the element, so a button keeps e12 across snapshots for as long as it exists.
A cookie bar eats the click
click checks what is actually on top of the target, presses that layer's Accept or Close, and says which one it moved.
Two things on the page share a name
It acts on the first and says it had a choice. Silence there is how a click meant for a wizard's submit button reopens a sidebar instead.
"Not found" and "not loaded yet" look identical
wait answers one of three things: there; still loading; or absent on a page that has gone quiet.
Dropdowns stopped being <select> years ago
Drives an ARIA listbox or combobox too, and confirms from aria-selected rather than from the click having landed.
Secrets pass through the transcript
fill_secret takes a key name from your credentials file. The value reaches the page and not the conversation.
You cannot tell what the page really did
console gives its errors and uncaught exceptions; network gives its requests, filtered by failed, third-party or URL. A tracker injected after hydration leaves nothing in the served HTML — this is how you catch it.
A screenshot you cannot see
The image comes back inline, so looking at a page is one call instead of save-then-read.
A bot wall snapshots like an empty site
A challenge or block page is named — blocked: this is an interstitial bot check (Cloudflare) — instead of arriving as a page with nothing on it.

What it will not do

It does not try to defeat bot protection. A Cloudflare interstitial, a block page or a rate-limit notice is reported, so you know what you are looking at — and the way through is to be a browser you are genuinely signed in to, or to use the site's API.

Dressing up as something else is a race that gets lost on the next update, and it breaks the terms of most sites worth visiting.

The comparison a reader who knows the field will want

Where it wins by less, and where it loses

44× is large because Playwright's format is verbose, not because this is magic. browser-use already builds a compact representation and is far more widely adopted: measured the same day, its own output on these five pages totals 38,101 characters against 11,397 — about 3.3×. On example.com it beats this tool, 138 to 208, because a page with almost nothing on it still costs us a header and a URL. bench/browser-use.py reproduces it. Stagehand is left unmeasured rather than estimated: v4 expects a paid Browserbase account.

Limits, before they surprise you

What it is not

WebKit is untested
Chromium and Firefox both are — AB_BROWSER=firefox switches engine and the whole suite passes on each.
One page at a time
A link that opens a tab is followed; there is no tab switcher.
Not a reader
The outline describes controls and headings. For prose-heavy pages, use the js escape hatch.
The network log starts when you do
It holds nothing from before the server began driving.