ZeroDOM
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| zerodom_parse_urlA | Navigate to a URL and return its interaction graph. Returns the compact text graph: 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. Set viewport_only=True on long feed/infinite-scroll pages (a social feed,
a video site's homepage) where most of the graph is scrolled off-screen
and you only need what's currently visible — this can cut node count by
more than half on pages like that. On by default for interactive agents;
costs a getBoundingClientRect() per element. Sticks for the rest of this
session (every click/fill re-read honors it too) until the next
zerodom_parse_url call changes it — same lifetime as Set check_occlusion=True when clicks keep failing with Playwright's
"element intercepts pointer events" — a modal backdrop, an open dropdown,
or a cookie banner is covering nodes that are still in the DOM and still
listed. This filters them out at parse time instead of at click time,
same sticky-for-the-session lifetime as |
| 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. Honors whatever frames/viewport_only/check_occlusion zerodom_parse_url last set. |
| zerodom_findA | Search the current page's graph for nodes matching 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. |
| zerodom_hidden_fieldsA | List hidden Hidden inputs are deliberately NOT graph nodes — an agent acting on a page has no reason to click them, so listing them as nodes would be noise. But they carry the payload a form submission needs (CSRF tokens, per-view state like a draft id or a sort order), which is exactly what an agent re-submitting a POST after a stale parse is missing. Returns each field's name, value and CSS selector, so the values can be fed straight into a zerodom_fill_node call if the target form expects them. |
| zerodom_crawlA | BFS-walk an app's in-scope routes and return one JSONL record per page:
final URL, forms, in-scope links, the XHR/fetch API calls it fired, hidden
fields, and Recon, not action: it navigates and reads, never clicks or submits. A link
matching |
| zerodom_scanA | Run zerodom's surface rules over the ACTIVE page and return findings as
JSONL: the interaction-graph rules, DOM-XSS sinks and the callable API surface
(method + body shape) read from the page's same-origin JS (js=True), and
CSP/CORS response-header posture. Recon over the page you are on — navigate
first with zerodom_parse_url. (Cookie-flag and CSRF correlation are CLI
|
| zerodom_repeatB | Send one request over the active authenticated session (in-page fetch) and return {status, body} — the Repeater for crafting or verifying a request by hand, e.g. replaying a crawl-discovered POST with a tweaked body. |
| zerodom_raceA | Fire a concurrent burst over the active session — the TOCTOU probe, run as
the browser's own multiplexed fetch (the tightest client-side engine). |
| zerodom_probeA | Replay an endpoint with a tampered body over the active session and diff vs
baseline — the mass-assignment / tampering check. |
| zerodom_click_nodeA | Click a node and return what changed on the page. Returns a diff — A same-page click reporting "no structural change" shortly after navigation is ambiguous — could be a real no-op, could be a server- rendered control (Next.js/Remix/Nuxt) whose framework hasn't finished attaching its handler yet. Flagged, not retried automatically: a false retry risks a real double-submit on a control that did fire. Suppressed when the click triggered a network request even without a DOM change yet — an in-flight fetch/GraphQL mutation (auth actions routinely take 800ms-2s to resolve) is itself evidence the handler did fire, just hasn't finished. |
| 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. |
| zerodom_hoverA | Hover over a node and return what changed on the page. Reveals hover-triggered menus and tooltips — content that a click alone would never surface, and that isn't in the graph until this fires. |
| zerodom_press_keyA | Press a key while a node is focused and return what changed. key uses Playwright's key names ("Enter", "Escape", "Tab", "ArrowDown", ...). For "press Enter to submit" forms, "Escape to close a modal", and keyboard-only widgets a click/fill can't drive. |
| zerodom_upload_fileA | Set a file input's value to a local file path and return what changed. path is resolved on the machine driving the browser — the same trust boundary as zerodom_eval_js, not a new one. |
| zerodom_dragA | Drag source onto target and return what changed. Drag-to-reorder lists, drag-and-drop upload zones, sliders — anything a click/fill pair can't express because the gesture itself is the input. |
| zerodom_scrollA | Scroll the page and return what's newly visible. direction: "up" or "down". amount: pixels, roughly one screenful is 800.
Dispatches a real wheel event at the viewport center rather than
|
| zerodom_new_tabA | Open a new tab and make it the active one — every other tool (read, click, fill, scroll) then acts on it until you zerodom_switch_tab away. In an attached (real-browser) session the new tab lands in the same "zerodom" tab group as every other tab this session touches. |
| zerodom_list_tabsA | List every open tab, marking the active one with |
| zerodom_switch_tabA | Make another open tab active and return its interaction graph. Node ids are per-tab, so re-read (this returns the graph already) before clicking or filling anything on the tab you just switched to. |
| zerodom_close_tabA | Close a tab — the active one by default. Refuses to close the last tab. |
| zerodom_screenshotA | Full-page screenshot of the active tab, saved to disk; returns the path. report.py has a screenshot path already, but it's wired to the CLI's own
throwaway sync browser (playwright_wrapper.py's sync/async split), not
this attached async session — this is that same capability for here.
Pass |
| zerodom_set_viewportA | Resize the active tab's viewport for responsive-design testing. Not the real browser window — that's zerodom_resize_window. This is Emulation.setDeviceMetricsOverride: the page's own rendered layout at a given width, without touching the chrome around it. Use this one for "how does this render at width X"; use zerodom_resize_window for an actually different-sized window on screen. |
| zerodom_resize_windowA | Resize the real browser window (not just the page's viewport). chrome.debugger's CDP surface has no browser-level window-management grant, but chrome.windows.update is a plain extension API, entirely unrelated to chrome.debugger — so this genuinely works despite that. Sent as Browser.setWindowBounds (a real CDP method name Playwright's own driver will actually transmit — a made-up method name gets rejected client-side before reaching the relay at all) and repurposed server-side; see docs/DECISIONS.md D16. Only meaningful for an attached (real-browser) session; a launched headless session has no window to resize. On a tiling window manager (i3/sway/Hyprland/bspwm-style setups), this call succeeds but the window won't visibly move or resize — on Wayland compositors specifically this isn't a WM being uncooperative, it's the protocol itself: clients are deliberately not allowed to force their own geometry. Works normally on a floating window. Confirmed live on Hyprland. |
| zerodom_get_stylesA | Computed styles and box-model dimensions for a node — for design/CSS review, not just interaction. Runs getComputedStyle() in the real page over the same chrome.debugger connection everything else here uses (also reachable ad hoc via zerodom_eval_js; this is the purpose-built version with a curated property list instead of getComputedStyle()'s full ~300-property dump). |
| zerodom_get_cookiesA | List cookies visible to the active tab's origin. Reads via CDP's Network domain (Playwright's context.cookies()), which sees httpOnly cookies too — unlike a content script's document.cookie, which httpOnly exists specifically to hide them from. |
| zerodom_get_storageA | List localStorage and sessionStorage keys the active tab's page holds. Session state that never touches the network — an issued draft id, a collapsed sidebar preference, a half-typed form — lives here. Same trust boundary as zerodom_eval_js (page context), but purpose-built and read-only, so it returns keys + values without the power of the eval tool. Values that look like a JWT (three base64url segments) get their claims (sub/role/exp/iss/aud) decoded into a second line. |
| zerodom_eval_jsA | Run arbitrary JavaScript in the active tab's real page context and return the result — Runtime.evaluate over the same chrome.debugger connection everything else here uses, the same power as typing into DevTools' own console. |
| zerodom_network_logA | Requests/responses the active tab has made since it opened (or since this was last called with clear=True) — method or status, and URL. Passive visibility only, capped at the most recent 200 entries — not interception or modification of traffic (that needs page.route(), a bigger, stateful feature; zerodom_eval_js can already override window.fetch/XMLHttpRequest from the page side for ad hoc cases). |
| zerodom_set_scopeA | Constrain the hunt to authorized targets — enforced in code, not on trust.
An operator can instead lock scope before the agent starts by setting the ZERODOM_SCOPE env var to a YAML file; a locked scope can't be widened here. |
| zerodom_create_groupA | Create a named, colored tab group the agent is sandboxed to, and make it the target for new tabs. Persists across a browser restart (stored by the extension). Once any agent group exists the agent may ONLY drive tabs inside its groups —
attaching to any other tab is refused. |
| zerodom_list_groupsA | List the agent's named tab groups (its sandbox), as remembered across restarts. No groups → the sandbox is off and the agent can drive any tab. |
| zerodom_use_groupA | Make |
| zerodom_close_groupB | Close an agent group: ungroup its tabs and forget it. If it was the last one, the sandbox turns off (the agent can drive any tab again). |
| zerodom_adopt_groupB | Hand an existing Chrome tab group (that you created yourself, by title) to the agent: it joins the agent's sandbox and becomes the active group. Lets you pre-arrange tabs and then scope the agent to exactly them. |
| zerodom_restore_groupsA | Reopen the remembered tabs of every agent group (best-effort) after a browser restart, re-grouping them under their saved name and color. A tab whose URL now hits a login wall or is otherwise unreachable is skipped, not fatal. |
| zerodom_add_identityA | Register a second identity (e.g. user B) for cross-tenant IDOR testing.
Once two identities exist, zerodom_compare_identities(url) fetches the same URL as each and flags a byte-identical response — the cross-tenant IDOR tell. |
| zerodom_replayA | Replay an HTTP request under a chosen identity — Burp Repeater for the agent. Sends |
| zerodom_compare_identitiesA | Fetch one URL as each identity and diff — the cross-tenant IDOR check. Sends |
| zerodom_statusA | Diagnose the current connection: relay reachability, whether a browser session is attached yet, which tab is active, and the tail of the relay's own log — the single-call version of the manual WebSocket-probing and log- tailing this project's own debugging needed before this tool existed. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 39 tools
Most tools are well differentiated by their descriptions (parse_url vs read_page vs find, set_viewport vs resize_window are explicitly contrasted). However, zerodom_repeat ('send one request over the active session') and zerodom_replay ('replay an HTTP request under a chosen identity') are near-duplicates that an agent could easily misselect, and the probe/race/compare_identities cluster requires careful reading to separate. Overlap exists but the verbose descriptions mostly rescue it.
All 39 tools share the zerodom_ prefix and mostly follow a verb_noun pattern (click_node, fill_node, switch_tab, close_group, compare_identities). Minor deviations exist: bare-verb tools (scan, crawl), noun-only names (status, screenshot, hidden_fields), and a get_ prefix subset (get_styles, get_cookies, get_storage) mixed with direct verbs like network_log.
39 tools is well past the comfortable range, and while the domain is broad, several clusters could be consolidated — six tab-group tools, four tab tools, and five overlapping HTTP-replay/security tools could each collapse into a single parameterized tool. The surface is genuinely large but not all of it earns a distinct endpoint.
The surface is unusually thorough: interaction (click/fill/hover/drag/press/upload/scroll), navigation, tab and group management, and a deep security recon set (scan, crawl, repeat, race, probe, replay, compare_identities, set_scope). Gaps are minor but real: no explicit wait_for or select-option tool for native <select>, and cookie/storage are read-only with network interception explicitly absent.