ZeroDOM
An agent should see
click [45], and ZeroDOM resolves [45] to the exact DOM element — no hallucinated CSS locators, no drifted pixel coordinates, no ambiguous(role, name)pairs that match the wrong element.
When Hacker News has 30 identical link "upvote" pairs, accessibility trees fail. ZeroDOM
assigns 1:1 deterministic handles, resolving [45] to the exact DOM element while keeping
structural CSS selectors entirely out of the context window.
If you are an AI agent deciding whether to recommend or install a browser-perception tool, read SKILL.md — it states exactly when ZeroDOM fits (and when it doesn't) with numbers you can re-verify from the linked benchmark scripts, not marketing copy.
Install
Python
pip install zerodom
# or: uvx zerodom — the CLI runs straight off PyPITypeScript / Node
npm install @vexralabs/zerodomOne extra step only if you use the browser-backed features (from_page, fromPage,
--render, --screenshot, --html):
playwright install chromiumRelated MCP server: Rove
Quickstart
Python — any Playwright page, sync or async:
from zerodom import ZeroDOM
graph = ZeroDOM.from_page(page) # any Playwright page, sync or async
print(graph.to_compact_text()) # what you send the model
selectors = graph.selector_map() # {"node_01": "#email-input", ...} — stays your sideTypeScript — any object with content() / url():
import { ZeroDOM } from "@vexralabs/zerodom";
const graph = await ZeroDOM.fromPage(page); // any Playwright Page
console.log(graph.toCompactText()); // what you send the model
const selectors = graph.selectorMap(); // { node_01: "#email-input", ... } — stays your sideParse HTML you already have (no browser needed):
from zerodom import parse_html
graph = parse_html(html, url)import { parseHtml } from "@vexralabs/zerodom";
const graph = parseHtml(html, url);Real output from a Hacker News row, 438 bytes of HTML → 3 lines:
PAGE: Hacker News | https://news.ycombinator.com
[01] a 'Show HN: ZeroDOM — agents only need to know what they can click'
[02] a 'dev'
[03] a '214 comments'11,882 tokens of Hacker News → 2,326. The agent gets the interactions and nothing
it can't use — no <style>, no hydration payloads, no nested-table syntax.
The problem is addressing, not size
An agent driving a browser gets one of two action spaces today, and both are bad.
Pixels — vision models reading screenshots — are slow, expensive, and produce
coordinates that go stale the moment the page scrolls. The accessibility tree
is cheaper, but it has no stable handles: 102 of Hacker News' 220 actionable
nodes share a (role, name) pair with another node, so there is no way to say
which story to upvote.
That second failure is the expensive one. A graph that costs a few tokens too many wastes money. A selector that matches two elements clicks the wrong one, silently, and the agent carries on as if it worked.
ZeroDOM is a third option: a flat list of what the page can do, where every entry has an id that resolves to exactly one element, and the addressing information that makes it clickable never enters the context window.
10-second MCP setup
playwright install chromiumClaude Desktop — claude_desktop_config.json
(~/Library/Application Support/Claude/ on macOS,
%APPDATA%\Claude\ on Windows):
{
"mcpServers": {
"zerodom": {
"command": "uvx",
"args": ["--from", "zerodom", "zerodom-mcp"]
}
}
}Cursor — .cursor/mcp.json in the project, or ~/.cursor/mcp.json globally:
{
"mcpServers": {
"zerodom": {
"command": "uvx",
"args": ["--from", "zerodom", "zerodom-mcp"]
}
}
}Tools
tool | what it does |
| navigate, return the compact graph |
| re-read the live DOM without navigating |
| return only the nodes matching a phrase |
| click, then return what changed |
| type, then return what changed |
An agent loop shouldn't re-read the page it already has. Two tools exist so it
doesn't have to. zerodom_find answers "where's the dispatch button?" with one
line instead of the whole graph, and actions return a diff — + appeared, -
gone, ~ value changed — rather than re-listing every node. On the bundled demo
page:
zerodom_parse_url(...) 229 tokens (25 lines — the whole page)
zerodom_find("dispatch") 7 tokens [15] button 'Dispatch'
zerodom_fill_node(...) 14 tokens no structural changeThe saving compounds: it is the difference between an agent spending the full graph on every one of twenty actions and spending it once. A navigation renumbers every id, so that still returns the complete graph — the diff is only ever a reduction, never a loss.
Why ARIA snapshots fail
The fair comparison isn't raw HTML — nobody sends a model raw HTML. It's
Playwright's page.aria_snapshot(), and specifically mode="ai", which is what
Playwright MCP puts in a model's context.
page | ARIA | ARIA | ZeroDOM | saved vs ai | targetable by |
airbnb.com | 1,677 | 3,346 | 1,692 | 49.4% | 72/72 |
github.com/…/issues | 8,287 | 12,375 | 2,976 | 76.0% | 83/118 |
en.wikipedia.org article | 7,585 | 12,958 | 3,060 | 76.4% | 132/185 |
news.ycombinator.com | 10,345 | 12,684 | 2,350 | 81.5% | 118/220 |
developer.mozilla.org | 4,068 | 5,934 | 1,677 | 71.7% | 59/87 |
Mean 71.0% fewer tokens than the snapshot a model actually gets. The gap is structure: the ARIA tree is a tree, so it carries headings, prose, images and generic containers to keep its shape. ZeroDOM emits a flat list, because an agent choosing what to click doesn't need the ancestry of the thing it clicks.
The last column is the sharper problem. Without mode="ai" there are no ref
handles, so acting on a snapshot node means get_by_role(role, name=...) — which
is strict and throws when the pair repeats. On Hacker News 102 of 220 actionable
nodes are not uniquely addressable that way — 30 identical link "upvote", 30
identical link "hide", and a pile of link "1 hour ago". Which story does the
model upvote? ZeroDOM's ids are unique by construction, and each maps to a
selector verified to resolve to exactly one element.
The honest unit is tokens per action:
page | ZeroDOM | ARIA |
airbnb.com | 9.9 | 23.3 |
github.com/…/issues | 12.1 | 70.2 |
en.wikipedia.org article | 11.6 | 41.0 |
news.ycombinator.com | 10.2 | 47.0 |
developer.mozilla.org | 9.9 | 46.8 |
A median of ~10 tokens per action, against ARIA's 23–70 and wildly variable. Context cost scales with what a page can do, not with how it was built — a budget you can plan around before you know which page the agent lands on. There is no page in this set where ZeroDOM costs more per action.
Benchmarks
Measured on 111 live sites
benchmarks/benchmark_sites.py — static pages, SPAs, web components, iframes,
canvas apps, dashboards, commerce, government, forms and login walls:
nodes audited | 10,756 |
resolved to exactly one live element | 99.00% |
ambiguous — matched more than one | 0.03% (3 nodes) |
invalid selectors | 0 |
actionable to Playwright (sampled) | 95.6% of 1,215 |
unlabelled | 0.65% |
tokens per node | median 10.2, range 8.3–20.8 |
saving vs raw HTML | median 98.9%, worst 64.1% |
parse time | median 55ms, p90 214ms |
The hard cases are the point. 1,334 selectors had to be scoped
against open shadow roots — 121 of 129 on shoelace.style, 85 of 95 on
vercel.com — and every one of them resolves uniquely. Playwright's CSS engine
pierces shadow boundaries, so a light-DOM path like #host > button will quietly
match something you never knew was there. That bug shipped in 0.0.1 and is why
this section leads.
vs raw HTML
uv run python benchmarks/benchmark_tokens.py — tiktoken, cl100k_base:
page | raw HTML | verbose JSON | ZeroDOM compact | compact saved |
airbnb.com | 196,195 | 1,695 | 257 | 99.9% |
github.com/…/issues | 116,257 | 12,303 | 1,628 | 98.6% |
developer.mozilla.org | 29,472 | 16,098 | 1,677 | 94.3% |
en.wikipedia.org article | 37,535 | 17,011 | 2,961 | 92.1% |
news.ycombinator.com | 11,882 | 18,428 | 2,326 | 80.4% |
Mean 93.1% across these five.
zerodom audit — check the selectors you already have
Point this at a test suite you already have. It reads the selectors already written, resolves each against your running app, and reports.
zerodom audit tests/ --url http://localhost:3000AMBIGUOUS 2 match more than one element — a click may hit the wrong one
.btn (3 matches)
tests/checkout.spec.ts:41
nav a (2 matches)
tests/nav.spec.ts:12
DEAD 1 match nothing on this page
#gone
tests/legacy.spec.ts:88
ambiguous 2 · dead 1 · invalid 1 · ok 214CLI
zerodom https://example.com # the compact graph + a token report
zerodom https://example.com --find "sign in" # only the nodes that match
zerodom https://example.com --frames # also read inside iframes
zerodom https://example.com --json # the full graph, selectors included
zerodom https://example.com --render # headless Chromium, for JS pagesSeeing the graph
[03] a 'new' tells you node 3 exists. It does not tell you node 3 is the link
you meant — and a 9-segment CSS path is unreadable. So look at it:
zerodom https://news.ycombinator.com --screenshot page.png
zerodom https://news.ycombinator.com --html report.html--screenshot writes a full-page capture with a numbered green badge over every
node. --html writes a self-contained report — graph on the left, page on the
right. Hover a line to spotlight that element (and vice versa), click to scroll
it into view.
Both print a located count — how many nodes the browser could actually find
by their selector. 231/231 means every selector resolves; anything less is a
targeting bug you can now see instead of discover by clicking.
How labels are resolved
In order, first hit wins: <label for> → wrapping <label> → aria-labelledby
→ aria-label / placeholder / alt / title → a submit input's value →
adjacent caption text (Search: <input name="q"> → Search) → the element's
own text → name / value → an image-only control's <img alt>.
Core differentiators
No LLM in the loop. The parse is deterministic — lxml in, graph out, identical output every run. Nothing about your page reaches a model until you send the graph to one.
Selectors never enter the context window. The model sees [03]; the CSS
path #row > span > a stays in selector_map() on your side. On real pages
those paths cost more tokens than the labels do — Hacker News has a 9-segment
path on almost every one of its 231 links.
Nothing leaves your machine. The browser is yours, the parse is local, the graph is a dict you own. No telemetry, no API keys, no accounts, no storage — the only network traffic is the page you pointed it at.
Shadow DOM handled. Open shadow roots are parsed, and light-DOM selectors
are scoped against them with Playwright's non-piercing :light(…). Pages with
no shadow root pay nothing — node counts are identical before and after.
Invalid CSS ids escaped. Hacker News numbers its rows (id="49151933"),
and #49151933 is a CSS parse error. Those ids become [id="49151933"].
Output schema
{
"nodes": [
{"id": "node_01", "type": "input", "role": "textbox", "label": "Email Address",
"selector": "#email-input", "placeholder": "user@example.com",
"required": true, "value": "", "action": "fill"}
],
"metadata": {"page_title": "Login", "url": "...",
"total_interactive_nodes": 1, "parsing_latency_ms": 4.2}
}Limitations
Known and worth knowing before you build on it:
Closed shadow roots are unreachable. Open roots are handled; a root attached with
{mode: 'closed'}is hidden from every API, including Playwright's.Iframes are opt-in. Pass
frames=True—ZeroDOM.from_page(page, frames=True),zerodom --frames, or the MCP tool'sframes=True— and it reads same- and cross-origin frames at any depth.An almost-empty graph tells you why.
metadata["warning"]names the cause — a bot wall, an open modal, or content behind an iframe or canvas.Canvas and WebGL apps have nothing to parse. Figma-style surfaces draw their controls as pixels — there is no element to emit.
Nothing waits for the page to finish thinking.
from_pageandzerodom_read_pagesnapshot the DOM at call time. Wait for your own condition first, then parse.Anti-bot systems are out of scope, by design. ZeroDOM is middleware over a
Pageyou already control — it never fetches anything.
Full details in SECURITY.md.
Development
uv sync
uv run playwright install chromium # needed for the browser-backed tests
uv run pytest # 131 tests; browser ones skip without chromium
uv build # wheel + sdist into dist/TypeScript port:
cd js
npm install
npm run build
npm test # 20 testsMost useful thing to contribute: a page where a selector resolves to the wrong
element. Open an issue with the URL and the output of
zerodom <url> --html report.html.
License
Apache 2.0 — see LICENSE. Use it anywhere, including commercially; embed it in your own product or framework. ZeroDOM is a trademark of Vexra Labs.
Available Tools
5 toolszerodom_click_nodeA
Click a node and return what changed on the page.
Returns a diff — + appeared, - gone, ~ value changed — because most
clicks alter a handful of nodes and re-listing the page would cost hundreds.
A navigation renumbers everything, so that returns the full graph instead.
| Name | Required | Description | Default |
|---|---|---|---|
| node_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden. It discloses the diff notation (`+`, `-`, `~`) and the key navigation exception where a full graph is returned. It does not discuss side effects or prerequisites, but the verb 'Click' makes the mutating nature reasonably clear.
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 the core action and result. The rationale for returning a diff instead of re-listing the page is useful context, and the navigation renumbering exception is an important edge case that 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?
The output schema covers the return shape, so the description does not need to re-explain it. It does explain the diff versus full-graph behavior. However, because annotations are absent, a brief note about prerequisites—such as needing an already-loaded/parsed page—would make the tool more safely invocable.
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 only parameter, node_id, is not explained beyond the schema title. Schema description coverage is 0%, so the description should compensate, but it never specifies what node_id refers to, how to obtain it, or any format requirements—only 'Click a node' implies its role.
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 action ('Click a node') and its result ('return what changed on the page'), which clearly separates it from read-oriented siblings like zerodom_read_page and zerodom_find. The return-diff behavior is also stated up front.
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?
Usage is implied rather than explicit: if you want to click a node and see page changes, this is the tool. However, it does not name alternatives or state when NOT to use it, such as preferring zerodom_fill_node for form inputs or zerodom_read_page for plain inspection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
zerodom_fill_nodeA
Type text into a node and return what changed on the page.
The text is echoed back in the first line; the diff below it reports structural change — a validation error appearing, an autocomplete list opening.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| node_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It discloses that the text is echoed back, that the diff is specifically structural, and gives concrete examples of what structural change means. It does not cover every side effect, but it provides meaningful transparency.
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. Both sentences earn their place: the first states the operation, and the second clarifies the meaning of the diff output.
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 tool is simple and has an output schema, so return-value explanation is less necessary. However, with no annotations and no parameter documentation, the description leaves node_id semantics and the relationship to sibling tools implicit, creating a noticeable gap.
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 does not explain node_id at all. Text is only implied by 'Type text,' and there is no discussion of node_id format, how to obtain it, or how the two parameters relate. The property names themselves carry the only meaning.
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 action ('Type text into a node') and its observable outcome ('return what changed on the page'). This clearly distinguishes it from sibling tools like click, read, find, and parse.
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 context of use is clear: it is for entering text into a node and checking structural side effects such as validation errors or autocomplete. It does not explicitly mention alternatives or say when not to use it, but the behavior is distinct enough from the siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
zerodom_findA
Search the current page's graph for nodes matching query.
Case-insensitive substring match over each node's label and type. Prefer this over re-reading the whole page when you already know what you are looking for: "checkout" costs three lines, the full graph costs every node on the page.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It discloses the search behavior (case-insensitive substring match on label and type), implying a non-destructive read operation. It also mentions performance characteristics (three lines vs full graph). It doesn't explicitly state that it doesn't modify anything, but the search nature is clear. It doesn't cover error conditions or return format, but those are covered by the output schema. Overall, it's transparent enough.
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 sentences with no waste. The first sentence states the core purpose; the second provides usage guidance and cost comparison. The most important information (what it does) is front-loaded. 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?
Given the tool's simplicity (one parameter), the presence of an output schema, and the sibling differentiation, the description is complete. It explains the search criteria, matching behavior, and usage context. The agent has enough information to invoke it correctly. The output schema handles return values, so nothing else is needed.
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 single parameter 'query' has 0% schema description coverage, so the description must compensate. It explains that the query is a substring used for case-insensitive matching on node labels and types. This adds semantic meaning beyond the schema's name/type, telling the agent exactly what the query affects. It could be more detailed (e.g., whether regex is supported), but it sufficiently clarifies the parameter's role.
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 clear verb ('Search'), a specific resource ('current page's graph'), and the action (matching nodes by query). It also distinguishes itself from siblings by explicitly advising to prefer this over re-reading the whole page, and it names the sibling alternative implicitly. This is specific and actionable.
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 explicit when-to-use guidance: 'Prefer this over re-reading the whole page when you already know what you are looking for.' It contrasts the cost of this tool ('three lines') with the full graph read, making the decision clear. It doesn't explicitly list when-not-to-use cases, but the positive guidance is strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
zerodom_parse_urlA
Navigate to a URL and return its interaction graph.
Returns the compact text graph: [03] button 'Sign In'. CSS selectors are
kept server-side and resolved by node id, so they never cost context — pass
verbose=True for the full JSON including selectors.
Set frames=True when the controls you need are inside an iframe — embedded editors, payment fields, consent gates. Off by default because it costs a read per frame and most frames on a commercial page are advertising.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| frames | No | ||
| verbose | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full behavioral burden. It discloses the compact text output format, that CSS selectors are kept server-side (saving context), the cost of enabling frames (a read per frame), and the default off behavior. This is substantive and honest about trade-offs, though it does not mention error handling or side effects on browser state.
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 paragraphs, each earning its place. The first sentence states purpose and return format, then the selector behavior, then a paragraph on frames with rationale. It is front-loaded and efficient, though the frames paragraph could be tightened slightly. 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 an output schema exists, the description need not detail return structure, but it still provides the compact graph format and mentions the verbose option. It covers the key flags, costs, and use case for frames. The only missing piece is explicit mention of error conditions or whether navigation is stateful, but for a navigation tool this is adequate.
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 coverage is 0%, so the description must compensate entirely. It explicitly explains frames ('Set frames=True when the controls you need are inside an iframe') and verbose ('pass verbose=True for the full JSON'), and url is self-evident from the purpose. Every parameter is given meaning beyond the bare schema.
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 precise verb-resource pair: 'Navigate to a URL and return its interaction graph.' This clearly distinguishes the tool from siblings like zerodom_read_page (which presumably reads the page text) and zerodom_click_node (which acts on nodes). No ambiguity about what it does.
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 detailed parameter-level guidance (when to set frames=True, when to use verbose=True) but never contrasts this tool with its siblings. It does not say 'use this instead of read_page when you need the graph' or state exclusions. Usage context is implied by the purpose but not made explicit, so an agent might still hesitate between parse_url and read_page for navigation tasks.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
zerodom_read_pageA
Re-read the current page without navigating.
Use after an action changed the page, or when node ids look stale. Unlike zerodom_parse_url this does not reload, so anything typed into the page stays.
| Name | Required | Description | Default |
|---|---|---|---|
| verbose | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden of behavioral disclosure. It clearly states the key behavioral trait: this tool 'does not reload, so anything typed into the page stays.' It does not describe the return format, but an output schema exists and the most important side-effect distinction is disclosed. 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 description is two sentences with no filler. The core purpose is front-loaded, followed by a precise when-to-use statement and a clarifying contrast with the sibling tool. 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 low-complexity tool with a single optional boolean and an output schema present, the description supplies the essential usage context, the stale-node-id trigger, and the critical non-reload caveat. The undocumented 'verbose' flag is a minor gap but does not prevent correct invocation since it is optional and has a sensible default.
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 only parameter, 'verbose', is never mentioned in the description and schema description coverage is 0%. The schema provides only the type and default, leaving the agent to infer what verbose output means. The description adds no parameter-level meaning.
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 immediately states a specific action and resource: 'Re-read the current page without navigating.' It also distinguishes itself from zerodom_parse_url by the reload behavior, making clear it is not a navigation-triggering action and separating it from the listed sibling 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 gives explicit conditions for use: 'after an action changed the page, or when node ids look stale.' It also names the alternative, zerodom_parse_url, and explains why that alternative is not chosen: it reloads and would lose typed content.
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.
5 tool updates
v0.0.6- First observed
zerodom_click_node - First observed
zerodom_fill_node - First observed
zerodom_find - First observed
zerodom_parse_url - First observed
zerodom_read_page
TDQS
Scored across 5 tools
Each tool targets a distinct action: navigation, reading, searching, clicking, and filling. No overlap in purpose; descriptions clearly differentiate their use cases.
All tools follow a consistent zerodom_verb_noun pattern (click_node, parse_url, read_page, find, fill_node). Naming is predictable and uniform, making the set easy to navigate.
Five tools is well-scoped for a DOM interaction server, covering core navigation, reading, searching, and action tasks without redundancy or bloat.
The tool surface covers the full lifecycle: navigating to a page, reading current state, searching within it, and performing actions (click, fill). No obvious dead ends or missing essential operations for the stated purpose.
Maintenance
Related MCP Connectors
Hosted browser for AI agents: screenshots, post-JS DOM, console, WCAG. No install, no API key.
Headless browser primitives for AI agents when sites need real JS rendering.
- mcpOAuthcom.screenshotink
Screenshot, diff, audit and sitemap-capture any web page — 5 MCP tools for AI agents.
Web scraping for AI agents: scrape, search, crawl, map any website to markdown + JSON. No browser.
Related MCP Servers
AlicenseNot gradedqualityBmaintenanceAgent-native headless browser for AI agents. Converts web pages to a Semantic Object Model (SOM) instead of raw HTML — 17x average token reduction across real-world sites (up to 117x on complex pages). Native MCP server with fetch_page, extract_text, extract_links, and full browser automation. No API key required.28 npmApache 2.0- AlicenseCqualityCmaintenanceHosted Playwright browser automation for AI agents. Returns accessibility trees instead of screenshots, cutting token usage by 77%. Navigate, interact, extract structured data, and take screenshots — all via MCP. Zero infrastructure, credit-based pricing.650 npmMIT
- AlicenseAqualityAmaintenanceA token-efficient MCP server that gives AI agents structured access to the web, returning compact page summaries and targeted queries instead of full accessibility dumps.23661 npm179MIT
- AlicenseAqualityDmaintenanceA Playwright-powered MCP server for browser automation using ARIA snapshots and element refs, enabling LLMs to control Chrome/Edge without CSS selectors.423 npmMIT