jev-ultrafast-mcp
This server lets an agent drive a real browser through MCP tools, from simple reads and clicks to handing over a whole goal to a server-side decision model.
Open pages and read an accessible element table (
browser_open,browser_observe)Act on the page in batched round trips: click, type, select, toggle, hover, upload, keys, scroll, navigate, wait, screenshot, tabs, and eval when enabled (
browser_act)Verify outcomes deterministically with code checks like URL/text/element/value/checked/count assertions (
browser_assert)Record, replay, list, inspect, or delete macros with zero model calls and placeholder substitution (
browser_macro)Hand over a full browser task in one call — server-side decision model drives until done, with optional verification (
browser_goal)Manage tabs and sessions: list, open, switch, close, and shut down the browser (
browser_tabs,browser_sessions,browser_close)Diagnose the environment: browser binary, connection, API keys, and policy envelope (
browser_doctor)
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jev-ultrafast-mcpOpen example.com and tell me what the page says."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jev-ultrafast-mcp
English · 简体中文

Hand the browser work off — an MCP server that drives the page for your agent.
Your agent should not be opening the browser at all. A browser task goes over whole — the URL, the goal, and the check that proves it — and the loop runs on the server. One tool call instead of twenty. Three seconds instead of a minute. A cent instead of a frontier model's context. And it never invents a target: it picks from what the page actually has, and the server refuses rather than guesses.
What it cost. A cent for the whole day, and a cent is all of it:

Quick start
Three commands, then restart your client.
git clone https://github.com/jiawei686/jev-ultrafast-mcp.git
cd jev-ultrafast-mcp
python3 -m venv .venv
.venv/bin/pip install -e . # Windows: .venv\Scripts\pip install -e .
python scripts/install.py # finds your MCP clients and writes their configinstall.py looks for WorkBuddy, Claude Code, Claude Desktop, Codex CLI, Cursor, VS Code, Cline,
Windsurf and Gemini CLI, and writes the format each one expects — merging into your existing
config and saving a .bak first. Needs Python ≥ 3.10 and any Chromium-family browser.
Restart the client, and then just say what you want:
You: Set this form to 3 adults, tick Nonstop only, then submit it. Your agent:
browser_goal(goal=…, url=…, verify=[…])— one call; the page is opened and the loop runs server-side, and the result is checked by code afterwards. (what that costs)
You: Open example.com and tell me what the page says. Your agent:
browser_open→ reads the element table → answers — a look is not a task, so it does not need the model. (verbatim run)
Restarted and the tools are not there? Some clients make you approve the server once. In WorkBuddy that is Connectors → Custom connectors → Trust. The approval is remembered against the config itself, so if you later edit the config it asks once more.
Related MCP server: Ghostlight
Cheap and fast, and here is the bill
A three-step goal on a real page, driven by browser_goal. This is everything your agent sent and
everything it got back — one turn, and the page never entered its context:
browser_goal(
goal="On this flight search form: set Passengers to 3 adults, tick the 'Nonstop only' "
"checkbox, then submit the search. Do not type into any city field.",
verify=[{"type": "text_contains", "text": "3 adults · nonstop"}],
)
goal: On this flight search form: set Passengers to 3 adults, …
status: done
steps: 3
turbo: 4 decisions · 14,626 tokens · 1.8s model + 1.1s page · 3.3s wall
trace:
1. SELECT e6 Passengers → ok (759ms model / 30ms browser)
2. TOGGLE e7 Nonstop only → ok (336ms model / 692ms browser)
3. CLICK e8 Search → ok (370ms model / 410ms browser)
4. DONE (conf 0.93)
verified: PASS
ok text_contains: '3 adults · nonstop' found in page textWhat the second time costs. Nothing. The second time is a recorded macro, and a macro makes no model calls at all — it does not even need a key.
How long it took. 3.3 s wall for the whole goal: 1.8 s of model, 1.1 s of page. Every run prints that line itself, so the numbers are checkable rather than persuasive.
That run is not a mock-up. scripts/turbo_check.py reproduces it against a real Chrome and the real
model, and then checks the page with code rather than trusting the model's account of its own
work. The three actions — plus the reading and re-reading between them — all happened on the server.
Your agent spent one turn and never saw an element table.
The division of labour is the whole design decision, so it is yours to make per task:
agent drives |
| |
Tool calls for a 3-step flow | 6+ (observe, act, observe, act…) | 1 |
Who holds the page in context | your agent | the decision model, server-side |
Per-step cost | one agent turn | one typed request, no screenshot |
Who names the target | the model writes a selector | the model picks a |
If it goes wrong | a wrong click, usually silent | the server refuses, with the reason |
Knowing it worked | the model's summary | code-checked assertion, which wins the disagreement |
Second time around | run the model again | macro replay, zero model calls |
No checkout needed if you would rather install it as a package. It is on PyPI, so the name is enough:
uvx jev-ultrafast-mcp # run it straight from PyPI, nothing installed
pip install jev-ultrafast-mcp # or install it yourselfA client config wants a stable interpreter path rather than uvx's cache, so:
python3 -m venv ~/.jev-ultrafast-mcp/venv
~/.jev-ultrafast-mcp/venv/bin/pip install jev-ultrafast-mcpThat gives you a jev-ultrafast-mcp console script and a stable interpreter path to put in a
client config — verified against the latest mcp SDK on Python 3.13, every one of the ten tools
listed.
python scripts/install.py --list # what is installed, and the file each one reads
python scripts/install.py --print # show the config it would write, change nothing
python scripts/install.py -c cursor,codex # only these two
python scripts/install.py --headed # keep a visible browser window
python scripts/install.py --allow-domains example.com,*.example.org
python scripts/install.py --uninstall # take the entry back outRuntime dependencies: mcp, websockets, httpx. No Playwright, no Selenium, no
browser-harness.
What it is
Browser automation usually makes the agent do the driving: read the page, pick one element, act, read again to see whether that worked. Ten clicks is ten turns, the page passes through the agent's context every time, and a mis-click rarely announces itself.
This server can take that job instead. browser_goal is one tool call from your agent; the loop
runs here, server-side, with Jev — TypeSafe's decision model — choosing each step. The model never
writes a selector: it picks among the elements the page actually has, and the server refuses
anything that is not on the page rather than guessing. When it stops, browser_assert checks the
page it left behind in code, and a passing assertion outranks the model's own account of what it did.
Four things follow from that:
One call, not one per click. The run above took a 3-step goal on a real page through 4 decisions, 14,626 tokens, 1.8 s model + 1.1 s page, 3.3 s wall — for one turn of your agent's context.
Accurate by construction. A target is a
reffrom a numbered table of what is on the page, not a selector or a coordinate the model invented, and the action is re-checked against the page before it runs.Free after the first run. Record the path once; replay costs zero model calls, works with no key at all, and refuses to proceed when the page no longer matches.
Text, not pixels. No screenshots, no HTML dumps. It speaks CDP straight to a Chrome you already have — no Playwright, no Selenium, no screenshot pipeline.
Everything except browser_goal — browser_open, browser_observe, browser_act,
browser_assert, browser_macro — needs no key, no account, and no network beyond the page itself,
from any MCP client: WorkBuddy, Claude Code, Codex, Cursor or VS Code. If you would rather keep your
hands on the wheel, that whole surface is still here.
browser_open → element table → browser_act [refs] → browser_assertInspired by browser-use/jev-ultrafast and
TypeSafe's typed-question API. Independent project, not affiliated with either — see
docs/DESIGN.md for what is different and why.
Contents · Quick start · Cheap and fast · What it is · What a session looks like · Connecting an agent · What you can ask it to do · What the agent reads · Why another browser MCP? · Tools · Configuration · FAQ · Try it without an agent · See also
What a session actually looks like
You say:
Open example.com and tell me what the page says.
Your agent does this, and this is everything it sees:
browser_open("https://example.com")
[obs#1] https://example.com/ "Example Domain" scroll=0/216 reachable=1/1
e1 lnk More information...
browser_observe()
[delta#2] … 1 element
= no change (1 element)Then it answers. No screenshot was taken, no HTML was dumped, and the page never entered a model's context: your agent read the table and answered.
A more realistic one — searching a real site, with your agent doing the driving:
browser_open("https://duckduckgo.com")
e4 cmb* Search with DuckDuckGo ▸ ""
browser_act([{type, ref: "e4", text: "python asyncio tutorial"}, {keys, key: "Enter"}])
→ 2/2 ops ok, one round trip, page navigated
browser_observe()
[delta#3] https://duckduckgo.com/?…&q=python+asyncio+tutorial reachable=9/59
+ e5 lnk Python Asyncio Tutorial
+ e6 lnk Async IO in Python: A Complete Walkthrough
…
43 new, 0 changed, 0 gone
browser_assert([{url_contains, text: "q="}, {count_at_least, role: "link", min: 5}])
PASSThat is a verbatim run against the live web — scripts/live_check.py reproduces it end to end.
Connecting an agent
Client | Config file | After installing |
WorkBuddy |
| restart, then Connectors → Custom connectors → Trust |
Claude Code |
| or |
Claude Desktop |
| quit the app fully and reopen |
Codex CLI |
|
|
Cursor |
| reload the window |
VS Code (Copilot) |
| Agent mode only — not Ask/Edit |
Cline |
| reload the window |
Windsurf |
| reload the window |
Gemini CLI |
|
|
Every client below needs the same three facts: an absolute interpreter path, the module, and one
environment variable. Substitute your own path for /ABS/PATH.
WorkBuddy — ~/.workbuddy-ai/mcp.json
WorkBuddy reads its config directory from WORKBUDDY_CONFIG_DIR and otherwise falls back to
~/.workbuddy. A machine can carry both — an older app alongside the current one — and writing
the one the app is not reading registers nothing and logs nothing. install.py resolves this
the same way the app does and tells you when it had to choose.
Then restart the app before looking for the tools. The config file is only watched if it already existed when the app launched, so a freshly created one is invisible until the next start. After the restart the server appears as a first connection, and you approve it once.
{
"mcpServers": {
"jev-ultrafast-mcp": {
"command": "/ABS/PATH/jev-ultrafast-mcp/.venv/bin/python",
"args": ["-m", "jev_ultrafast_mcp"],
"env": { "JEVMCP_HEADLESS": "1" }
}
}
}Claude Code
claude mcp add --scope user jev-ultrafast-mcp \
--env JEVMCP_HEADLESS=1 \
-- /ABS/PATH/jev-ultrafast-mcp/.venv/bin/python -m jev_ultrafast_mcpOr write the same mcpServers object by hand: ~/.claude.json for user scope, .mcp.json in a
project for team scope (committed to git).
Codex CLI — ~/.codex/config.toml. Codex uses TOML, and the table is mcp_servers, not
mcpServers:
[mcp_servers.jev-ultrafast-mcp]
command = "/ABS/PATH/jev-ultrafast-mcp/.venv/bin/python"
args = ["-m", "jev_ultrafast_mcp"]
startup_timeout_sec = 20
[mcp_servers.jev-ultrafast-mcp.env]
JEVMCP_HEADLESS = "1"The same entry works as codex mcp add jev-ultrafast-mcp --env JEVMCP_HEADLESS=1 -- /ABS/PATH/…/python -m jev_ultrafast_mcp.
Cursor — ~/.cursor/mcp.json for every project, .cursor/mcp.json for one. Same mcpServers
object as WorkBuddy.
VS Code (Copilot) — .vscode/mcp.json, or Command Palette → MCP: Open User Configuration for
all workspaces. VS Code is the odd one out twice over: the key is servers, and every entry must
declare "type": "stdio" or it is silently skipped.
{
"servers": {
"jev-ultrafast-mcp": {
"type": "stdio",
"command": "/ABS/PATH/jev-ultrafast-mcp/.venv/bin/python",
"args": ["-m", "jev_ultrafast_mcp"],
"env": { "JEVMCP_HEADLESS": "1" }
}
}
}Claude Desktop — claude_desktop_config.json (%APPDATA%\Claude\ on Windows), same
mcpServers object. Restart the app from the tray, not just the window.
The browser does not start until the first browser_open, and the tab it drives is a background
tab it owns — focus emulation keeps animations and menus running without stealing your window.
Making sure your agent actually hands it over
Pointing the client at the server is half of it. The other half is that the agent has to know to hand over — and that part is not automatic everywhere.
A server sends a short instructions block when a client connects, and this one's first rule is
that a browser task goes to browser_goal, in one call, with the URL. Clients that read it behave.
Not all of them do: WorkBuddy delivers a server's tools to the model and drops its
instructions — measured, not assumed: the tool schemas are in the recorded request payload, the
instructions text is not. A host in that position does the obvious thing and drives the page itself,
one call per click, which is exactly the work this server exists to take away.
Two ways to close that gap — either is enough:
Install the skill.
skills/jev-ultrafast-mcp/SKILL.mdcarries the same rule in the form a client reads as a skill, plus the traps that waste a run. Copy it into your client's skills folder (~/.workbuddy/skills/for WorkBuddy):mkdir -p ~/.workbuddy/skills/jev-ultrafast-mcp cp /path/to/jev-ultrafast-mcp/skills/jev-ultrafast-mcp/SKILL.md ~/.workbuddy/skills/jev-ultrafast-mcp/Or say it once. "Browser tasks go to
browser_goal" is enough for most sessions — an agent told that keeps doing it.
How to tell it took. Ask for something that needs a click. If browser_goal comes back with a
url inside the call, the handoff is live. If the agent opens the page and starts walking the
element table for you instead, the rule did not arrive — install the skill, or say it once.
What you can ask it to do
Say this | What happens |
"Fill in this form and submit it" | handed over whole — one |
"Walk this flow in staging and tell me if it worked" | the same one call — |
"Do this same thing again tomorrow" | record a macro; replay costs zero model calls and needs no key at all |
"Open this page and tell me what it says" | a look, not a task: reads the visible text and the controls — no model, no key |
"Did the deploy actually ship?" |
|
"Log in and download last month's invoice" | you log in by hand once; the profile persists |
"Click through checkout in staging" | payment-like buttons come back as |
What it is not
Being clear about this saves everyone time:
It never looks at pixels. A captcha, a chart, a canvas-only app — anything that needs real visual judgement — is out of scope. Use a screenshot-and-vision agent for those, or use the
screenshotop here to capture evidence for a human.It is not a scraper framework. One browser, one session at a time. No proxy rotation, no concurrency, no crawling at scale.
It is not a recorder for humans. There is no click-to-record UI; macros are recorded by the agent driving the task normally.
What the agent actually reads
Not a DOM dump, not a screenshot — a table of the controls it can act on. Each row is a ref
(element number), a role code, flags, and the accessible name; editable things carry their current
value, and selectable things carry their options:
[obs#1] http://127.0.0.1:54409/fixture.html "Ultrafast Fixture" scroll=0/860 reachable=16/16
e1 lnk Home
e2 lnk About
e3 lnk Open popup
e4 inp* Where from? ▸ ""
e5 cmb* Where to? ▸ ""
e6 cmb Passengers ▸ 1 adult opts{1 adult=1 | 2 adults=2 | 3 adults=3 | 4 adults=4}
e7 chk· Nonstop only
e8 btn Search
e10 inp* Password ▸ ""
e11 file CV accept=.pdf,.txt
e12 btn Delete accountFlags: * editable · » off-screen (the server scrolls it into view) · ⊘ covered by something
else · ▾ expanded · ✓/· checked state. reachable=16/19 means three controls exist but are
covered or off-screen right now.
After an action it reports only what changed — that is the single biggest saving in a long loop:
[delta#2] http://127.0.0.1:54409/fixture.html "Ultrafast Fixture" reachable=16/16
~ e4 inp* Where from? ▸ "Zurich" (was "")
~ e7 chk✓ Nonstop only
2 changed, 0 new, 0 goneAn action that accomplished nothing is the most expensive thing in an agent loop, because the model retries it. So it is spelled out in one line:
[delta#3] … 16 elements
= no change (16 elements)New rows appear with + and disappear with -. When two controls share a name, the row carries the
context that tells them apart:
+ e17 btn Select @Zurich → Anywhere Option 1 · 1 adult · nonstop Select
+ e18 btn Select @Zurich → Anywhere Option 2 · 1 adult · nonstop SelectAnd a ref that no longer points at anything is refused, with a reason instead of a wrong click:
[{"op": "click", "ok": false, "ref": "e999", "error": "detached"}]See that table for your own page
The extension in
chrome-extension/
is a window on it. Load it unpacked, click it on any page, and you get the same rows the model gets —
drawn by the same observer and a port of the same renderer, so a ref in the popup means what it
means in a session. The second read of a page renders as a delta, which is how you watch a page
change.
It also runs a macro with no model in the loop — the resolver, the dispatcher and the report writer are all ports of the server's own code, so a replay there is the replay here.
Both halves are why it asks for four permissions and no host access at all: activeTab (the tab you
clicked it on, and nothing else), scripting, storage, and debugger. The last one is the cost of
the replay, and it is not a shortcut: element.click() produces isTrusted: false events a site is
entitled to ignore, so a replayed click that is to be believed has to come through the DevTools
protocol. The extension's own
README says
what that buys, what it costs, and which four ops it declines to do rather than reach outside the tab
you pointed it at.
Why another browser MCP?
Two differences, and the first one is the reason this exists.
The driving is not the agent's job. A browser flow is a loop, and in most servers that loop
lives in the calling agent: read the page, name one element, wait, read again. Fine for two steps,
absurd for twenty — twenty turns of an expensive context to do what a smaller model could have done
in one call. Here the loop lives on the server: one browser_goal call carries the URL, the goal
and the check, and the agent never opens the browser itself. The manual tools stay for the two cases
that need them — reading a page, which is not a task and should not cost a model call, and the
fallback when no model key is configured.
The model never invents a target. Most browser MCP servers hand over CDP primitives —
click_at_xy, a CSS selector, evaluate. Maximum flexibility, minimum safety: a wrong selector
fails silently or, worse, succeeds on the wrong element. Here a target is a ref from a numbered
table of what is on the page, turning that ref into a real click is the server's problem, and the
server refuses rather than guesses. That is also what makes the handoff safe: whatever is driving
is choosing among options the page actually has, so accuracy does not rest on it being careful.
primitives-based browser MCP | jev-ultrafast-mcp | |
Who runs the loop | the calling agent, every step | the server — one |
How a target is named | a selector / coordinate / JS the model writes | a |
Extra model calls | none | one small decision model per step, and only inside |
API keys required | none | none for the browser tools; a decision-model key only for |
Ref lifetime | n/a (agent re-invents each step) | stable across observations |
Re-reading the page | full dump every time | delta — |
Round trips | one per action | batched — many ops per call |
Ambiguous target | agent guesses | server refuses with a reason |
Shadow DOM / iframes | usually unsupported | traversed, with frame-offset-aware scrolling |
Pages that render late | depends on the agent sleeping | waits for elements to appear, bounded |
Repeating a flow | re-runs the model | macro replay at zero model cost |
Knowing it worked | the model eyeballs the page | deterministic |
Destructive clicks | whatever the model decides |
|
Batching and deltas are not cosmetic. In the bundled end-to-end run, 29 ops and their follow-up observations cost 15.4 KB of context, of which 13.6 KB was deltas and 1.8 KB full tables — the model re-reads only the part of the page that moved.
Tools
Ten tools. Most sessions need four of them.
browser_open(url, session="default", hint="")
Opens a URL in its own tab and returns the full element table. hint restates your goal in one
line and is echoed back.
browser_observe(session="default", mode="auto", include_text=True, include_json=False)
Re-reads the page. auto emits a delta; full forces the whole table, delta forces a diff.
= no change means the last action did nothing — change strategy, do not retry.
browser_act(ops, session="default", dry_run=False, stop_on_error=True, observe_after=True)
Executes ops in order in one round trip, then returns a delta.
op | fields |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{"ops": [
{"op": "type", "ref": "e4", "text": "Zurich"},
{"op": "select", "ref": "e6", "value": "3 adults"},
{"op": "toggle", "ref": "e7"},
{"op": "click", "ref": "e8"}
]}A failing op reports why: occluded, detached, target_changed, page_changed,
needs_confirmation, blocked_by_policy. Reach for browser_observe, not a retry.
For tabs, prefer target_id over index. Indexes are positional and get renumbered whenever the
tab list changes, so an index read one call ago can address a different tab.
browser_assert(checks, session="default")
Deterministic checks — no model judgement about whether it worked.
{"checks": [
{"type": "url_matches", "pattern": "*/checkout*"},
{"type": "text_contains", "text": "Order confirmed"},
{"type": "element_exists", "role": "button", "name": "Continue"},
{"type": "value_equals", "ref": "e4", "value": "Zurich"},
{"type": "count_at_least", "role": "link", "min": 3}
]}browser_macro(action, session="default", name="", params={}, ...)
record_start → drive the task → record_stop → run. Replay costs no model calls: it
navigates back to where the task began and re-resolves every step by role + accessible name,
refusing weak or ambiguous matches rather than clicking the wrong thing. params fills
{{placeholders}} in typed text and URLs.
browser_goal(goal, url="", session="default", max_steps=20, verify=[...])
Hands the whole task over. Pass url and the goal and the page is opened and driven to the end
server-side using TypeSafe speculative fan-out (one request per step) — one call, one turn. Leave
url out to carry on from the page the session is already showing. Needs TYPESAFE_API_KEY, or
OPENROUTER_API_KEY with TYPESAFE_BASE_URL pointed at OpenRouter's decisions route. Returns
verified: PASS/FAIL when verify checks are supplied.
Reading a page is not a task: browser_open, browser_observe and browser_assert are direct,
free and keyless, so a look stays cheap. The handoff is for work that changes the page.
Every run also reports its own bill — turbo: 4 decisions · 14,626 tokens · 1.8s model + 1.1s page · 3.3s wall — so what the handoff cost is visible in the answer, alongside how much of the wall time
was the model and how much was the page.
Every way the decision model can fail — no key, no credits, unreachable, a malformed answer, a body
that is not JSON — comes back as turbo_unavailable: with nothing executed. The trace of the steps
already taken is kept, so a run that dies on step five still reports what steps one to four did.
status is the model's own summary, and verify is checked by code, so when the two disagree the
assertion decides: if the page passes your checks the run reports status: done whatever the model
said, and the trace records that it overruled. This is the ordinary shape of a goal whose last
action removes what it acted on — click a check-in button and the button is gone, so the model,
finding nothing left to do, reports BLOCKED on a goal that in fact succeeded.
browser_tabs · browser_sessions · browser_close · browser_doctor
Tab management (list / new / switch / close), session listing, teardown, and a self-check that reports which browser was found and whether it is reachable.
Configuration
All optional; the defaults are the point.
Variable | Default | Meaning |
| auto-detected | Chrome/Chromium/Edge/Brave executable |
|
|
|
| — |
|
| (browser defaults) | data directory of the browser being attached to, if |
|
|
|
|
|
|
|
|
|
|
| browser window size |
|
| persistent profile — log in once, stay logged in |
| (all) | comma-separated; navigation elsewhere is refused |
| (none) | comma-separated blocklist |
| pay / delete / unsubscribe … | clicks matching these need |
|
| enables |
|
| gates the |
|
| element-table cap, applied by usefulness |
|
| visible-text cap per observation |
|
| how long to wait for a late-rendering page to show controls |
|
| how often to re-read while waiting |
|
| profile, macros and screenshots |
| — | optional; enables |
|
| where the decision model lives; point it at |
| — | used as the decision-model key when |
|
| decision-model slug |
| — | optional; only for the small text helper |
|
| endpoint for that helper |
|
| model for that helper |
JEVMCP_MODE=attach is the "use the browser I already have open" route — the one to take when the
login you need already lives in your own profile. Chrome 144+ exposes that through
chrome://inspect/#remote-debugging, and its server answers 404 to /json/version by design; jev
falls back to DevToolsActivePort rather than treating that as "nothing is listening". Attach mode
only ever touches the tab it opens: browser_close detaches rather than quitting, and the same holds
when the server exits. Your other windows, and the session in them, are never closed.
Everything above the last seven rows is local: it configures a browser on your machine. Only the
decision-model group talks to the network, and only when browser_goal actually runs. Pointing
TYPESAFE_BASE_URL at OpenRouter means one OPENROUTER_API_KEY covers both the decision model and
the text helper, and needs no TypeSafe account.
Two of these are worth setting before you point an agent at your own accounts:
JEVMCP_ALLOW_DOMAINS pins the browser to a set of hosts and refuses everything else, and a
persistent JEVMCP_PROFILE_DIR means you log in once by hand instead of teaching the model your
password.
FAQ
So this is just another model doing the work? Who is in charge?
You are, and you choose per task. browser_goal puts Jev — a small decision model — in charge of
one goal: which of the page's elements to touch, one step at a time. It is not a general agent, it
has no memory between goals, and it never writes code or selectors, only picks from options the
server hands it. Your agent still decides what to ask for, and verify decides whether it
actually happened. If you would rather be in the loop for every step, do not call that one tool —
nothing else sends anything anywhere.
Do I need an API key or an account?
Not for the browser tools. browser_open, browser_observe, browser_act, browser_assert,
browser_macro and the tab/session tools never call out — no telemetry, no phone-home, nothing
leaves your machine. browser_goal is the exception, and it is opt-in: it sends your goal and the
current element table to a decision model, which is why it needs a key. Leave that one tool unused
and nothing about the page goes anywhere.
Will a browser window pop up and take over my screen?
No. It runs headless by default and drives a background tab it owns — animations and menus still
work, but nothing steals focus. --headed (or JEVMCP_HEADLESS=0) shows the window if you want to
watch it work.
How do I use it on a site I am logged into?
Set JEVMCP_PROFILE_DIR to a persistent directory, open the browser once by hand, log in, and the
session is remembered. That is far better than teaching an agent your password — and password
fields are redacted in observations when you do type them.
Can it just use the browser I already have open, with my logins in it?
Yes. JEVMCP_MODE=attach plus JEVMCP_CDP_URL=http://127.0.0.1:9222 drives your own Chrome. In
Chrome 144+ you switch debugging on from chrome://inspect/#remote-debugging — no restart, so your
tabs and logins survive — and Chrome asks you to approve the client. The first connection waits on
that click, so give it a moment before deciding it failed.
Do I have to approve that click for every action?
No. The approval is per browser session, not per connection and not per action. Once you have
approved it, every later action rides the same open WebSocket and never prompts again — not even
from a fresh process (measured: three new processes connecting twenty minutes after the click, all
accepted with no prompt). So the cost is one click per browser session, not one per operation. To
drop even that, use the default JEVMCP_MODE=launch: it starts its own browser on a real
debugging port with no approval dialog at all, at the price of logging in once in that profile.
curl http://127.0.0.1:9222/json/version returns 404. Is debugging even on?
Probably yes. The server behind chrome://inspect/#remote-debugging is WebSocket-only and
deliberately serves no HTTP discovery endpoints, so a 404 there is the documented behaviour rather
than a broken setup (and it is not the same thing as --remote-debugging-port=9222, even though both
print port 9222). jev does not rely on it: when /json/version does not answer, it reads the port and
the browser WebSocket path out of Chrome's DevToolsActivePort file. If your browser keeps its data
directory somewhere unusual, point JEVMCP_ATTACH_PROFILE_DIR at it.
Nothing is happening and the page looks empty.
A page that renders from JavaScript can briefly look empty. The server waits for controls to appear
(up to JEVMCP_SETTLE_TIMEOUT), but if a site is stuck behind a cookie wall or a consent dialog, the
element table will show it — look for the overlay warning in the observation header.
There is a captcha. Can it solve it? No, and that is deliberate — it never looks at pixels. Use a screenshot-and-vision agent for that.
Is it on PyPI? Is it in the MCP registry?
On PyPI, yes — pip install jev-ultrafast-mcp, or uvx jev-ultrafast-mcp to run it without
installing anything. In the registry, not yet, but nothing is left to do by hand:
server.json validates
against the registry's schema and the release workflow submits it on every tag over OIDC, so it
lands with the next release. See
Publishing for
what that involves.
How is this different from the Playwright MCP? Playwright's server exposes page primitives; the agent writes selectors and coordinates. This one exposes a numbered table of controls and refuses ambiguous targets. If you need pixel-level control or a mature recorded-testing ecosystem, use Playwright. If you want an agent that cannot silently click the wrong button, use this.
Is this an alternative to browser-use?
They solve the same problem from opposite ends. browser-use
is a library that runs the agent loop in-process; this is an MCP server that gives your existing
agent the same kind of hands. The optional turbo path here is a port of
browser-use/jev-ultrafast, which asks a decision
model one typed question per step instead of free-form text.
Is it safe to let it loose on my accounts?
It is built assuming it should not be trusted. Destructive-sounding clicks come back as
needs_confirmation instead of executing, JEVMCP_ALLOW_DOMAINS refuses navigation outside a
domain you list, sensitive fields are redacted, and eval is off unless you turn it on. Start with
a domain allowlist and an account you do not mind breaking.
Try it without an agent
.venv/bin/python scripts/smoke.py # headless, 58 checks
.venv/bin/python scripts/smoke.py --headed # watch it driveThis launches Chrome, serves tests/fixture.html, and drives the real code paths: batch execution,
autocomplete, a covering modal, shadow DOM, a same-origin iframe, a file upload, a password field, a
destructive-click guard, stale refs, macro record/replay, tab handoff, screenshots, and a page that
renders after readyState already says "complete".
1. Observation — one atomic read, indexed refs
[ok ] element table is not empty — 16 elements
[ok ] shadow DOM element indexed — Shadow action -> e14
...
5. Occlusion — precomputed, not discovered by a failed click
[ok ] covered control flagged before any click — e8 occluded=True
[ok ] click on a covered control is refused with a reason — occluded
...
58/58 checks passedTo test against the real web rather than a fixture:
.venv/bin/python scripts/live_check.py # Bing + DuckDuckGo + tabs + screenshots
.venv/bin/python scripts/live_check.py --headed # watch it happenThat one needs the network and third-party sites, so it is deliberately not part of CI. Unreachable sites are reported as skipped, and the summary says so plainly, so a fully-skipped run cannot be mistaken for a passing one.
And to prove turbo mode itself — the one path that spends money, and therefore the one nothing else exercises end to end:
.venv/bin/python scripts/turbo_check.py # the model drives: dropdown, checkbox, submit
.venv/bin/python scripts/turbo_check.py --headed # watch it decideIt serves the same fixture, points a real Chrome at it, and lets Jev drive the goal, then verifies
the page the model left behind with code rather than trusting its claim of success. Without a key it
prints skipped and exits 0, so the exit code and the word agree.
A check-in that stops paying for itself
examples/checkin.html is a stand-in for the thing people actually automate: a daily button.
scripts/checkin.py drives it in three stages, cheapest first — and the point is that only the
first run ever costs anything.
.venv/bin/python scripts/checkin.py --port 8901 # learn once, then never again
.venv/bin/python scripts/checkin.py --port 8901 --record # re-learn, ignoring the saved macro1. Already done. Read the page. If today's check-in is already on it, stop — no click, no model call, nothing to undo.
2. Replay. Run the macro the first run recorded: zero model calls, a few hundred milliseconds, and it refuses rather than guessing when the page has moved on. This is the stage that runs on every ordinary day, and it needs no key at all.
3. Explore. Only when there is no macro, or the saved one no longer matches. The decision model works the page out, and what it did is recorded as a macro so stage 2 takes over tomorrow. Its path is only saved when the page proves it worked.
Pinning --port matters for the demo: a page's origin includes its port, so a second run on a
different port is a different site to the browser, with an empty localStorage and no memory of
having checked in.
For a real site:
.venv/bin/python scripts/checkin.py --url https://example.com/rewards \
--goal "Click the daily check-in button" --expect "已签到"
.venv/bin/python scripts/checkin.py --url https://example.com/rewards --replay-onlyThe goal and the proof are separate arguments on purpose. --goal is what the model is asked to do;
--expect is the text the page must show afterwards, checked by code — so a run is judged by the
page, never by the model's summary of its own work. Sign in once with --headed --wait 120; the
browser profile persists, so later runs reuse the session. --replay-only never calls the model,
which is the flag you want in a cron job.
Layout
jev_ultrafast_mcp/
js/observer.js in-page observer: stable refs, shadow/frame traversal, verify/resolve
cdp.py synchronous CDP client + Chrome launch (no wrapper library)
browser.py sessions, guarded execution, op dispatch, macro recording
observe.py element model, compact renderer, delta computation
macros.py semantic descriptors, scored re-resolution, storage
assertions.py deterministic checks
policy.py optional TypeSafe turbo policy (speculative fan-out)
safety.py domain envelope, redaction, confirmation rules
config.py environment-driven configuration
server.py the MCP surface
scripts/
install.py writes the right config for each MCP client on this machine
smoke.py end-to-end proof against a real browser
mcp_check.py drives the server over real stdio MCP
live_check.py the same, against real websites (needs the network)
turbo_check.py lets the decision model drive a real browser (needs a key)
extension_check.py the extension in a real Chrome: its table, and a macro it replayed
checkin.py a real check-in: learn once with the model, then replay for free
chrome-extension/
lib/observer.js a byte-identical copy of jev_ultrafast_mcp/js/observer.js
lib/render.js a port of observe.py, held to the real renderer by generated fixtures
lib/macro.js a port of macros.py, held to the real resolver by generated fixtures
lib/session.js a port of browser.py's op dispatcher, held to the real one by generated fixtures
lib/report.js a port of server.py's report writers, so a report reads the same anywhere
lib/store.js macros in chrome.storage.local, under the server's own {{placeholder}} rules
background.js the service worker, and the only file that calls the debugger API
examples/
checkin.html the daily-button page checkin.py drives
assets/
social-preview.png the card GitHub shows when this repository is shared
make_social_preview.py renders it, so the words on it are placed rather than generated
llms.txt what this server is, for agents that read before recommending it
server.json the MCP registry entry, submitted by the release workflowDevelopment
.venv/bin/pip install -e ".[dev]"
.venv/bin/ruff check .
.venv/bin/python -m pytest -q
.venv/bin/python scripts/smoke.py # 58 checks, real browser
.venv/bin/python scripts/mcp_check.py # 17 checks, real stdio MCPAll of it runs in CI on Python 3.10, 3.12 and 3.13 against headless Chrome. Read
CONTRIBUTING.md before changing how targets are resolved — that logic is the
whole point of the project.
See also
browser-use/jev-ultrafast— the project the optional turbo path is a port of: one typed question per step, answered by a decision model.TypeSafe — the typed-question decision API behind
browser_goal.Model Context Protocol — the protocol this server speaks.
The official MCP registry — where clients go looking for servers like this one.
Chrome DevTools Protocol — what it drives the browser with, through no wrapper library.
License
MIT — see LICENSE.
Available Tools
10 toolsbrowser_actA
Execute one or more ops in order, then return a delta observation.
Batch ops into a single call — each call is a round trip.
op fields click ref (ref may be "e12", or "e12" of a combobox to open it) type ref, text, [clear=true], [submit=false] select ref, value (option value or label) toggle ref, [state] (checkbox/radio/switch; no state = flip) hover ref upload ref, path | paths[] keys key ("Enter", "Meta+A", "ArrowDown") | keys[] scroll [dir=down|up|left|right], [amount=600], [ref] nav url back | forward | reload wait [ms=500] wait_for_ref ref, [timeout_ms=8000] wait_for_text text, [timeout_ms=8000] wait_for_load [timeout_ms=20000] screenshot [path], [full=false], [format=jpeg] tab action=list|new|switch|close, [index], [url] eval js (only when JEVMCP_ALLOW_JS=1)
Actions matching the confirmation rules (pay, delete account, …) return needs_confirmation; re-send that op with "confirm": true to proceed.
| Name | Required | Description | Default |
|---|---|---|---|
| ops | Yes | ||
| dry_run | No | ||
| session | No | default | |
| observe_after | No | ||
| stop_on_error | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the sparse annotations, the description discloses that calls return a delta observation, that batching saves round trips, that confirmation-required actions surface needs_confirmation and require re-sending with confirm: true, and that eval is gated by JEVMCP_ALLOW_JS=1. This enriches readOnlyHint=false and openWorldHint=true without contradicting either.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The main behavior, round-trip concern, and op reference are packed into a compact front-loaded table with no filler. Each row and caveat earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a high-complexity multi-op tool, this is a near-complete manual: all ops, timing defaults, confirmation flow, and the eval gate are covered, and the output schema covers return value details. Minor missing context is the semantics of the four top-level flags, though their names/defaults make them partially self-explanatory.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description compensates with a detailed op/fields table covering refs, optional flags, defaults, and value formats. However, the top-level parameters beyond ops (dry_run, session, observe_after, stop_on_error) are not explained beyond their schema names and defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first line states a specific verb and object: execute browser operations in order and return a delta observation. The op table makes it clearly an action tool, distinguishable from siblings like browser_observe, browser_tabs, and browser_close.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit batching advice: 'Batch ops into a single call — each call is a round trip,' so an agent knows to combine operations. It does not explicitly name sibling alternatives or when-not-to-use conditions, though the op table largely implies the tool's scope.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_assertARead-onlyIdempotent
Verify the current page against deterministic checks. Returns pass/fail.
checks {"type": "url_matches", "pattern": "/checkout"} {"type": "url_contains", "text": "/orders/"} {"type": "title_matches", "pattern": "Order"} {"type": "text_contains", "text": "Thanks", "regex": false} {"type": "text_absent", "text": "Error"} {"type": "element_exists", "role": "button", "name": "Continue"} {"type": "element_gone", "ref": "e12"} {"type": "value_equals", "ref": "e7", "value": "Zurich"} {"type": "checked", "ref": "e9", "state": true} {"type": "count_at_least", "role": "link", "min": 3} {"type": "js", "expr": "document.title.length > 3"}
| Name | Required | Description | Default |
|---|---|---|---|
| checks | Yes | ||
| session | No | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the description correctly adds value by emphasizing 'deterministic' checks and 'Returns pass/fail'. The enumerated check types materially expand behavioral transparency because the schema otherwise reveals only an opaque array of additionalProperties-objects. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The two-sentence intro is front-loaded with purpose and outcome, and the example block is compact and information-dense. Each line illustrates a distinct check type with no filler or redundant words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete enough for a tool with a polymorphic checks parameter and an output schema. It covers all major assertion categories and return behavior. Minor gaps remain around exact pattern-matching semantics and the precise pass/fail output structure, but those are not critical for selecting and invoking the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the checks items are 'additionalProperties: true', giving no semantics. The description fully compensates by presenting 11 concrete check object shapes with field names like pattern, text, ref, role, name, min, and expr. The session parameter is adequately covered by its schema default.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('verify') and names the resource ('current page'), and explicitly states the return contract ('Returns pass/fail'). The extensive list of concrete check types makes it unmistakable what the tool does and differentiates it from siblings like browser_observe or browser_act.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly implies this tool is for deterministic assertions on the current page, and the check examples show common scenarios. However, it never explicitly states when to prefer this over siblings such as browser_observe, browser_act, or browser_goal, nor does it mention cases where it should not be used.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_closeA
Close a session's tab. Set shutdown_browser=True to stop the browser too.
Only a browser this server launched is stopped. In attach mode the browser is yours: shutdown detaches and leaves it, and every other window, running.
| Name | Required | Description | Default |
|---|---|---|---|
| session | No | default | |
| shutdown_browser | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=false), the description reveals a non-obvious behavioral trait: only a server-launched browser is actually stopped, while attach-mode browsers are left running. This is valuable context; it could go further by stating what happens to the session after the tab closes, but the key caveat is present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tight and front-loaded: one clear action, one conditional flag, and one caveat. Every sentence earns its place, with no redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given its moderate complexity, an output schema, and annotations, the description covers the main behaviors an agent needs: what is closed, when the browser stops, and how attach mode differs. The main gap is the undocumented session parameter, which keeps this from a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description must carry parameter meaning. It clearly explains shutdown_browser=True and its attach-mode limits, but it leaves the session parameter undefined beyond the schema title 'Session', so an agent must infer what a session is and what values are valid.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a specific verb and resource: it closes a session's tab, with an optional flag to stop the browser. This is clear and not a restatement of the tool name, but it does not explicitly differentiate the tool from siblings such as browser_tabs or browser_act.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives conditional guidance for shutdown_browser and explains attach-mode behavior, but it never states when to prefer browser_close over browser_tabs, browser_act, or other siblings. The intended use is implied rather than explicitly contrasted.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_doctorARead-onlyIdempotent
Report environment: browser binary, connection, keys, and policy envelope.
Call this when anything behaves unexpectedly — it separates "no browser" from "blocked by policy" from "no key".
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the description doesn't need to restate safety. It adds context by revealing that the tool's behavior is to report environment details and triage failure causes, which is beyond what annotations convey. No contradictions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with zero fluff. The core purpose is front-loaded in the first sentence, and the usage guidance is in the second. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a diagnostic tool with no parameters, an existing output schema, and annotations covering safety, the description fully equips an agent to decide when to call it and what it can expect. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the description doesn't need to explain any. The baseline for 0-param tools is 4, and there is no missing parameter information to compensate for.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Report') and a specific resource ('environment') plus the scope of that report (browser binary, connection, keys, policy envelope). It clearly distinguishes this diagnostic tool from action-oriented siblings like browser_open, browser_act, and browser_close.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to call the tool: 'when anything behaves unexpectedly.' It also clarifies the diagnostic value by separating failure modes. It does not explicitly name alternatives or when not to use it, but the context is clear enough for an agent to route correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_goalA
Hand a whole browser task over. Needs a decision-model key.
This is the entry point for browser work, not an optimisation on top of the
manual loop. Pass url and the goal and the page is opened and driven to
the end here: one call, one host turn, instead of a turn per click.
Leave url out when the task continues from a page an earlier step left
behind; the goal then runs against whatever the session is already showing.
Each step costs one request (operation + every target head in a single
speculative fan-out). verify runs browser_assert-style checks on the final
page, so the result is a fact rather than a model's claim of success.
The key is TYPESAFE_API_KEY, or OPENROUTER_API_KEY when TYPESAFE_BASE_URL points at https://openrouter.ai/api/alpha/decisions -- same model, same contract, no TypeSafe account needed. Reading a page needs no key at all, so when no key is set the handoff is unavailable while browser_open, browser_observe and browser_act keep working.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | ||
| goal | Yes | ||
| verify | No | ||
| session | No | default | |
| verbose | No | ||
| max_steps | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses key behavioral traits: cost per step (one request per operation and fan-out), verification behavior (runs browser_assert-style checks for factual results), and API key requirements (TYPESAFE_API_KEY or OPENROUTER_API_KEY) as well as the no-key case. It adds context beyond the annotations (which only say non-read-only and open world), such as the implication of non-read-only via key requirements, though not explicitly stating mutability or potential side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured, starting with a clear one-line purpose, then explaining the entry-point nature, URL omission, cost model, and API key details. Each paragraph adds distinct value. It's somewhat long but every sentence carries important information for an agent deciding and calling the tool. No fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity (6 parameters, no schema description, output schema exists), the description covers most critical aspects: when to use, key requirements, cost implications, and verification. It doesn't detail all parameters (e.g., `session`, `max_steps`) but those are lower-stakes, and the output schema likely describes return values. It's comprehensive enough for an agent to invoke correctly without major gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 0% coverage on parameters, so the description must compensate. The description explains `url` (optional, for continuation) and `verify` (runs checks on final page), but does not explain `goal`, `session`, `verbose`, or `max_steps`. However, it covers the two most semantically significant parameters, and with 6 parameters, the coverage rate is high enough, though not complete, to warrant a 4 rather than a 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: to hand over a whole browser task, opening a URL and driving the page to completion in one call. It distinguishes itself from the manual loop (browser_act per click) and explicitly names the entry-point nature. The phrase 'Hand a whole browser task over' is a strong verb+resource statement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit guidance on when to use: as the entry point for browser work, not an optimization on the manual loop. It also tells the agent when to omit `url` (when continuing from an existing session) and contrasts with siblings by saying reading a page needs no key, while handoff is unavailable without a key. This effectively routes to alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_macroA
Record, replay, list, or delete a macro — a discovered path with no model calls.
action="record_start" begin capturing ops (needs the session to be driving the task)
action="record_stop" finish and save under name
action="run" replay name; params fills {{placeholders}} in text/url
action="list" | "inspect" | "delete"
Replay re-resolves each step by role + name against a fresh observation and refuses to act when the best match is weak or ambiguous.
| Name | Required | Description | Default |
|---|---|---|---|
| goal | No | ||
| name | No | ||
| action | Yes | ||
| params | No | ||
| session | No | default | |
| start_url | No | ||
| threshold | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description adds meaningful behavioral details: replay re-resolves each step by role and name, refuses to act on weak/ambiguous matches, and macros contain no model calls. There is no contradiction with the annotations' readOnlyHint=false and destructiveHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with a one-line summary followed by an action-oriented reference list. Every line earns its place, and there is no redundant prose or restating of schema fields.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The output schema exists, so return values need not be described, but the tool is polymorphic with five actions and seven parameters. The description does not specify parameter requirements per action, what delete or inspect do in detail, or how threshold and start_url affect replay, leaving gaps for an agent invoking it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the burden of explaining parameters. It does clarify action, name, params, and session indirectly, but threshold, start_url, and goal are left unexplained, and it is unclear which parameters apply to which actions beyond run/record_stop.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-and-resource statement: 'Record, replay, list, or delete a macro — a discovered path with no model calls.' This clearly distinguishes browser_macro from the other browser_* siblings, which focus on opening, observing, acting, or asserting, not on managing reusable discovered paths.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives action-level guidance, including a prerequisite for record_start ('needs the session to be driving the task') and explains when to use run. However, it does not explicitly say when to choose browser_macro over related tools like browser_act or browser_observe, leaving some usage inference to the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_observeARead-onlyIdempotent
Re-read the page: new element table, or a delta if little changed.
mode: "auto" (delta when possible), "full" (whole table, e.g. after a big
change), "delta" (force). include_json=True appends a machine-readable
copy of the element table when you want to plan over it programmatically.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | auto | |
| session | No | default | |
| include_json | No | ||
| include_text | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as read-only and idempotent, and the description adds meaningful behavioral details: the page is re-read, the result may be a full element table or a delta depending on changes, and include_json appends a machine-readable copy. This gives the agent an accurate picture of what to expect without contradicting the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: the core behavior appears in the first line, followed by concise parameter guidance. Every sentence adds useful information, and there is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description, annotations, and output schema together cover the important aspects: read-only behavior, mode selection, and return-style options. The main gaps are the undocumented session and include_text parameters and the lack of an explicit pointer to sibling tools, but these are lower-risk because the core usage is clear.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the burden. It explains mode and include_json well, but it does not cover session or include_text, leaving those two parameters undocumented in both schema and description. It partially compensates for the schema gap but not fully.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Re-read the page' and names the concrete output ('new element table, or a delta if little changed'), making the operation and resource clear. This clearly differentiates it from siblings like browser_act or browser_assert, which are action/assertion tools rather than observation tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly explains when to use each mode: 'auto' for delta when possible, 'full' after a big change, and 'delta' to force. It also tells the agent when to set include_json=True, namely when planning programmatically over the element table.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_openA
Open a URL in a new owned tab and return the element table.
Use hint to restate the goal in one line; it is echoed back so the next
step has the goal in context without re-reading this call.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| hint | No | ||
| session | No | default |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already signal this is not read-only and may have external effects. The description adds useful context about creating an 'owned tab' and echoing back the hint, but it does not disclose details like behavior when the URL fails, whether the current tab changes, or session implications. Acceptable but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tightly written sentences. The first front-loads the core behavior, and the second adds targeted guidance on `hint` without wasting words. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple open action with an output schema and annotations, the core behavior is covered. However, the description omits the `session` parameter semantics and does not clarify how this relates to the sibling tools, so an agent may still have unanswered questions about multi-tab or multi-session workflows.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explains `hint` well but says nothing about `url` or `session`. The `url` is required and only typed as string; the description does not add format, protocol, or usage guidance. Only one of three parameters gains real semantic value from the text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Open a URL in a new owned tab and return the element table.' This clearly distinguishes it from sibling tools like browser_tabs or browser_observe by emphasizing the new owned tab and the returned element table.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description makes clear this is the entry action for opening a URL and gives specific guidance for using the `hint` parameter. It does not explicitly name alternatives or exclusions, but the open-and-return-table purpose is unambiguous enough for an agent to select it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_sessionsARead-onlyIdempotent
List open sessions (independent owned tabs).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, so the safety profile is covered. The description adds a bit of conceptual context by explaining that sessions are 'independent owned tabs,' but it does not disclose return format, pagination, or any other behavioral nuance.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short sentence with no filler or redundant wording. The key action and resource definition are front-loaded, and every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only listing tool with an output schema present, the description is nearly complete. It would be stronger if it explicitly mentioned the relationship to browser_tabs, but nothing needed to make the call itself is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has no parameters, so the description does not need to explain parameter semantics. Baseline 4 applies for a zero-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List'), a resource ('open sessions'), and defines that resource as 'independent owned tabs,' which distinguishes it from the sibling browser_tabs. An agent can tell what this tool does and roughly what it returns.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is given about when to use this tool versus alternatives such as browser_tabs. The parenthetical helps define the concept but does not explicitly state when this tool should be selected or when another tool is preferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_tabsA
List, open, switch to, or close tabs.
Tabs opened by the page show up in observations on their own. To act on one,
prefer target_id (the #handle printed by action="list"): indexes are
positional and get renumbered whenever the tab list changes, so an index
read a call ago can address a different tab. index is a convenience when
listing and acting in the same breath; omit both to mean "the current tab".
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | about:blank | |
| index | No | ||
| action | No | list | |
| session | No | default | |
| target_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish the tool is not read-only and not destructive/open-world; the description adds critical runtime behavior: page-opened tabs appear in observations, target_id is stable via '#handle', and index is unstable across list changes. This explains why an index read earlier may point at a different tab, which annotations alone cannot convey. It does not disclose session persistence or close side effects, but the core risk is covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact: a one-line action summary followed by a focused paragraph on handle/index pitfalls. Every sentence contributes either scope or usage nuance, with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a multi-action 5-parameter tool, the description covers the most important usage pitfalls and the current-tab default, and the output schema exists to document return shape. The main gap is the absence of explicit parameter-to-action mapping for url and session, though defaults and the opening action list make these mostly inferable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema coverage, the description must compensate, and it does for the two hardest parameters: target_id and index, including their semantics and defaults. It also grounds action='list' by referencing the '#handle' it prints. However, url, session, and the full action value set are not explicitly defined, leaving some inference required.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening line 'List, open, switch to, or close tabs' gives a specific verb set and resource, making the tool's scope immediately clear. It does not explicitly contrast with sibling tools like browser_open or browser_close, but 'tabs' as the resource and the multi-action framing differentiate it well enough.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The second paragraph gives explicit how-to guidance: prefer target_id over index because indexes are positional and renumber, use index only when listing and acting in the same breath, and omit both to target the current tab. This is clear context for when each addressing mode should be used, though it does not name alternatives such as browser_observe or browser_open.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.5- Changed
browser_goal1 field changed- added
Input schema / properties / urlAdded value: +{ + "default": "", + "title": "Url", + "type": "string" +}
10 tool updates
v0.1.0- First observed
browser_act - First observed
browser_assert - First observed
browser_close - First observed
browser_doctor - First observed
browser_goal - First observed
browser_macro - First observed
browser_observe - First observed
browser_open - First observed
browser_sessions - First observed
browser_tabs
TDQS
Scored across 10 tools
Most tools have clearly distinct responsibilities, but browser_sessions and browser_tabs overlap in listing managed tabs, and browser_tabs also handles opening/closing which overlaps with browser_open and browser_close. The descriptions help disambiguate, so this is only a minor concern.
All tools share the browser_ prefix and snake_case style, but the second word mixes verbs (open, observe, act, assert, close) with nouns (sessions, macro, goal, tabs, doctor). The naming is readable and predictable enough, though not a consistent verb_noun pattern.
Ten tools is a reasonable, well-scoped count for a browser automation server. There is slight redundancy between session/tab management tools, but the overall count is neither bloated nor too thin.
The tool set covers the full browser automation lifecycle: opening pages, observing, acting, asserting, tab management, macros, autonomous goal completion, cleanup, and diagnostics. No obvious dead ends or missing core operations are apparent.
Maintenance
Related MCP Connectors
Real Chrome for agents: start a browser, read pages as numbered markdown, click, type, hand off.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Hyperbrowser MCP — wraps the Hyperbrowser AI-agent browsing API
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to control Chrome browser actions like navigation, clicking, form filling, screenshots, and console/network logging via an MCP server and Chrome extension.532 npmMIT
- AlicenseAqualityAmaintenanceMCP server that lets AI agents drive your real Chromium browser with your existing signed-in sessions, providing visible, local, and inspectable automation for tasks like navigation, clicking, typing, and form filling.251Apache 2.0
- FlicenseNot gradedqualityBmaintenanceMCP server that enables AI agents to control Chrome via DevTools CDP and accessibility trees, providing 21 tools for browser automation including tab management, navigation, interactions, and page capture.-
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to operate an isolated local Chromium browser through MCP, with semantic snapshots, ref-based actions, search, research, crawling, and CDP access.Apache 2.0