jev-browser-mcp
by bothuany
README.md
<div align="center">
# jev-browser-mcp
**A browser for your agent that keeps the page out of its context.**
Your model says what it wants. A tiny typed-decision model clicks, a cheap model reads,<br>
and only the answer comes back — never the DOM.
[](LICENSE)



**94× less context than Playwright MCP when reading** · **$0 replays** · **self-healing recipes** · **exports plain Playwright tests**
</div>
---
```bash
claude mcp add --scope user jev-browser -- npx -y jev-browser-mcp
```
Then ask Claude something like *"on the Ada Lovelace Wikipedia page, when and where was she born?"*.
Claude gets back **266 tokens**. The same question through Playwright MCP costs **108,726**.
## Why
Browser MCP servers hand the page to your model. A real page is 10k–100k tokens, and whatever
lands in the conversation is paid for again on **every later message**. Twenty steps on a real site
and the context is mostly DOM.
This server splits the work between three models, and the expensive one does the least:
```mermaid
flowchart LR
A["🧠 Your model<br/>(Claude, GPT…)"] -- "intent:<br/>'find the price'" --> S["jev-browser-mcp"]
S -- "answer · 300 tokens" --> A
S -- "compressed state" --> J["⚡ Jev<br/>which element? which op?<br/>~$0.04 / 1M tokens"]
S -- "page text" --> F["📖 Gemini Flash<br/>reads the page,<br/>writes field values"]
S <--> P["🌐 Chrome<br/>(Playwright)"]
```
| who | job | sees the page? |
|---|---|---|
| **your model** | states the intent, reads the result | **no** |
| **[Jev](https://typesafe.ai)** | decides each step: which element, which operation, is it done | a compressed state |
| **Gemini Flash** | answers questions about the page, writes values into fields | yes, on the server |
## What it's good at
### 📖 Knowing something without reading the page
`browse_ask` reads the page on the server and returns the answer, optionally as JSON. Measured on
four live pages, all answers correct:
| page | Playwright MCP | jev-browser | |
|---|---:|---:|---:|
| Wikipedia · Ada Lovelace | 108,726 tokens | **266** | **409×** |
| GitHub · microsoft/playwright | 14,531 | **399** | 36× |
| MDN · `Array.prototype.at` | 13,918 | **519** | 27× |
| Hacker News · top 3 as JSON | 13,478 | **418** | 32× |
| **total** | **150,653** | **1,603** | **94×** |
Each read costs $0.0004–$0.003 in Flash tokens.
### 🔁 Record once, replay for free, heal when the site changes
```text
browse_goal saveAs=checkout.json → Jev drives it once ~$0.0005
browse_replay checkout.json → no model at all $0 · ~2 s
… the site is redesigned: ids renamed, labels changed …
browse_replay checkout.json heal=true → only the broken step ~$0.0001
browse_replay checkout.json → free again $0
browse_replay export=playwright → a plain @playwright/test spec
```
Each step is recorded with several locators, strongest first: role and name, label, field name, id,
and the stable tail of a framework-generated id. When every locator misses, **only that step** goes
to Jev, and the repaired step is written back into the recipe.
Measured on six hard cases (calendar, autocomplete, dependent dropdown, ordered form, delayed
dialog, form inside an iframe): each page was redesigned so every recorded locator missed. The old
recipe broke, **healed 6/6**, and the exported specs pass on plain Playwright with this server not
running.
### 🏢 Enterprise screens that break other tools
It was hardened against a live JSF/PrimeFaces admin console, not only demo pages:
- generated `j_idt…` ids
- `ViewState` fields that must never be touched
- buttons drawn as `<span>`s
- disabled state expressed only as a CSS class
- iframes
- modals that appear seconds late
A three-step search recorded there replays in **4.4–4.9 s at $0**. The replay passed 3/3 even when
the session started with a different search already in the box.
### 🪶 Cheap actions too
| task | Playwright MCP, used well | `browse_act` | `browse_goal` |
|---|---:|---:|---:|
| 4-field form + submit | 360 tokens · 4 calls | 252 · 3 | 527 · **1** |
| dependent dropdown | 321 · 4 | 208 · 2 | 340 · **1** |
| **Wikipedia search, live** | **18,875** · 3 | **405** · 3 | **456** · **1** |
On a toy page there is nothing to save. The saving grows with the size of the page.
## What it is *not*
> [!IMPORTANT]
> **This is not an autonomous open-web agent.** Left alone on 30 live
> [WebVoyager](https://github.com/MinorJerry/WebVoyager) tasks, it completes **30%**. Agents where a
> large model reasons about every step report far more. Jev picks the right control on the screen;
> it does not reason across ten criteria on a page. That reasoning is your model's job.
It is built to be the **hands under a strong model**, and that is how it is measured.
## How it compares
All results below were measured live and are reproducible. Methodology, raw results and grading
reasons are in [docs/BENCHMARKS.md](docs/BENCHMARKS.md).
**vs [jev-ultrafast](https://github.com/browser-use/jev-ultrafast)**: both run autonomously, 30
WebVoyager tasks, same text model.
| | passed | median time | cost / task |
|---|---:|---:|---:|
| **jev-browser** | **9 / 30** | 18 s | **$0.0009** |
| jev-ultrafast | 6 / 30 | 16 s | $0.0046 |
About **5× cheaper** on every task. The success difference is **not significant** (McNemar p = 0.51).
ultrafast passes Google Flights; this server does not.
**vs Playwright MCP**, with the same GPT-5 driver on both. Four tasks, so this is an observation,
not a statistic:
| | passed | driver tokens | list price |
|---|---|---:|---:|
| Playwright MCP 0.0.82 | 1 / 4 | 1.15 M | $1.54 |
| jev-browser | 1 / 4 verified + 2 unverified¹ | **0.76 M** | **$1.15** |
On the Apple task the driver made 1 `browse_goal` call and 1 `browse_ask` call: **3 steps and 11k
tokens**, against 16 steps and 88k through Playwright MCP.
¹ A harness bug left the final page unread. Those runs were graded on the driver's answer alone, so
they are not counted as passes.
> [!NOTE]
> **The first version lost this comparison.** The driver ignored `browse_goal` and `browse_ask`,
> went step by step, and spent more tokens than Playwright MCP (486k vs 340k). The advantage only
> exists when the model uses the tools that keep the page out of its context. Two changes fixed it:
> action results got smaller, and the server now sends MCP `instructions` that tell the model when
> to use those tools.
## When to use it
| use it for | use something else for |
|---|---|
| ✅ self-healing regression tests: record, replay free, export to Playwright | ❌ "go do anything on the internet" with no model on top |
| ✅ repetitive work in internal tools and admin panels | ❌ single clicks on small pages: no saving there |
| ✅ reading big pages without blowing up your context | ❌ bot-protected consumer sites like Google Flights |
| ✅ legacy JSF / PrimeFaces / iframe-heavy apps | |
| ✅ capturing a legacy screen's behaviour and checking the rewrite against it | |
## Tools
Thirteen tools cover Playwright MCP's capabilities (20/20 probes, checked on the page:
`node bench/coverage.mjs`). Their definitions take about 3.8k tokens; Playwright MCP's defaults take
about 5.6k.
| tool | what it does |
|---|---|
| `browse_goal` | drive toward a plain-language goal; optionally record it (`saveAs`) |
| `browse_ask` | a question about the page, answered on the server, optionally as JSON |
| `browse_replay` | replay a recipe with no model; `heal` broken steps; `export=playwright` |
| `browse_act` | click · type · fill (a whole form by label) · select · hover · drag · upload · key · scroll · wait |
| `browse_verify` | exact checks in code (free), or a plain-language claim judged by Jev |
| `browse_navigate` · `browse_observe` | open a URL · the compressed element list, with `find` |
| `browse_tabs` · `browse_dialog_policy` | tabs · decide `alert`/`confirm` before they block |
| `browse_eval` · `browse_console` · `browse_screenshot` | look under the hood |
| `browse_page` | pdf · resize · media · network requests · tracing · save session · close |
A run ends as `done`, `blocked`, `escalate` or `max_steps`. **`escalate` is a normal outcome**: Jev
would not commit, so the final page comes back and your model takes the next step itself.
## Setup
**Requirements:**
- Node 20+
- Chromium: `npx playwright install chromium`, or attach to your own Chrome
- one [Vercel AI Gateway](https://vercel.com/ai-gateway) key, which reaches both Jev and Gemini
The server reads the key from `AI_GATEWAY_API_KEY`, `JEV_KEY_FILE`, or `~/.config/jev/key`. Keep it
out of the MCP registration so it never lands in shell history. On the free tier, Flash allows 5
requests a minute; the server retries through that limit instead of failing.
<details>
<summary><b>Pages behind a login</b></summary>
A profile directory alone is not enough. Server-rendered logins issue session cookies with no
`Expires`, and Chrome drops those when it closes. Sign in by hand once and capture the live
session:
```bash
npx jev-browser-login https://example.com/login # sign in, then press ENTER
export JEV_BROWSER_STORAGE=~/.config/jev/session.json
```
For a session that never expires, run `./chrome-cdp.sh <url>` from a checkout and set
`JEV_BROWSER_CDP=http://localhost:9222`. You can watch every step and take the window back at any
time.
You type your own password into your own window. The server never reads, stores or forwards it,
and it refuses to type into password fields unless `JEV_ALLOW_SECURE_FIELDS=1`. A web page cannot
set that variable.
</details>
<details>
<summary><b>Environment variables</b></summary>
| variable | effect |
|---|---|
| `JEV_BROWSER_STORAGE` | session captured from a manual login |
| `JEV_BROWSER_PROFILE` | persistent profile directory |
| `JEV_BROWSER_CDP` | attach to your own Chrome, e.g. `http://localhost:9222` |
| `JEV_BROWSER_HEADED` | show the window |
| `JEV_BROWSER_IGNORE_HTTPS_ERRORS` | accept a corporate TLS interception certificate |
| `JEV_ALLOW_SECURE_FIELDS` | allow typing into password fields (off by default) |
| `JEV_TEXT_MODEL` | text model (default `google/gemini-2.5-flash`) |
| `JEV_TEXT_THINKING` | its thinking budget (default 0; thinking was 90% of the cost) |
| `JEV_OBSERVE_LIMIT` | elements per observation (default 120) |
| `JEV_DONE_BAR` | how sure the completion check must be (default 0.7) |
| `JEV_IFRAMES=0` | do not walk embedded documents |
| `JEV_OUTPUT_DIR` | downloads, PDFs, traces, large response bodies |
| `JEV_VIEWPORT_WIDTH` / `_HEIGHT` | default 1600×900 (admin consoles hide controls at 1280) |
| `JEV_REPLAY_WAIT_MS` | wait before a replay step is healed (default 8000) |
| `JEV_SETTLE_MS` / `JEV_SETTLE_QUIET_MS` | settle budgets for parsing (15 s) and restless (4 s) pages |
| `JEV_DEBUG=1` | print every offered operation, page class and field value |
</details>
## Known limits
- **Google Flights** fails, and fails differently each time.
- **30% autonomous success** on the open web. See [What it is not](#what-it-is-not).
- **Borderline completion:** one SPA case lands `done` or `escalate` depending on the run.
- **Tested on macOS only.** No CI; the benchmarks cost real money, so they are run by hand.
## Under the hood
[docs/ENGINEERING.md](docs/ENGINEERING.md) explains why the loop owns progress, waiting, click
correction and the completion check, and how a decision went from 7,226 tokens to ~1,500. It also
lists the four fixes a real JSF console forced. Almost every rule in it exists because the simpler
version failed on a real page first.
```bash
npm test # 14 capability probes
node bench/hard/run.mjs # 6 hard cases
node bench/heal.mjs # record → replay → redesign → heal → exported specs
node bench/context.mjs # reading cost vs Playwright MCP, live pages
node bench/competitors/webvoyager.mjs A # vs jev-ultrafast on WebVoyager
```
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues