One window, two drivers
paniolo browser opens a Chromium window on a dedicated profile that a
human and an agent share: you drive with the mouse and keyboard, the agent attaches
over the Chrome DevTools Protocol and sees the same DOM, console, and session.
The ten browser_* tools ship on the Paniolo MCP
server, so from @paniolo/cli 0.5.76 there is nothing
extra to install.
Launch, drive, hand it back
paniolo browser launch https://example.comThat starts a Chromium-family browser with a dedicated profile at
~/.paniolo-browser, a remote-debugging port on
loopback, and --enable-features=WebMCPTesting. A second
launch against a running instance reuses it and opens the URL there —
racing the same profile is how the debugging flags get silently dropped.
Two knobs when the defaults miss: --port runs a
second instance when one browser genuinely is not enough — one port per browser is the
unit, there is no multiplexing — and PANIOLO_BROWSER_BIN
picks the executable. The search covers Chrome and Edge on Windows, Chrome and Chromium
on macOS, and the usual /usr/bin and snap paths on
Linux.
paniolo browser tabs # what the human has open
paniolo browser teardown # stop the instance, keep the profileteardown --wipe-profile discards the profile,
sign-ins included. Never taskkill /IM chrome.exe —
that closes the human's other windows too.
See the page, then act on it
Four reads and six writes. Reads are never gated; writes ask on real origins — the consent model is below. The catalog entry lives on the MCP server page.
| Tool | Kind | What it does |
|---|---|---|
browser_tabs | read | List open tabs — what the human has in front of them, before acting. |
browser_console | read | Drain console output, logs, and failed requests since the last call. The first call subscribes and returns no history. |
browser_screenshot | read | Capture a tab as a base64 PNG. |
browser_webmcp_list | read | List the tools the current page declares about itself via WebMCP. Same-origin only. |
browser_navigate | write | Point a tab at a URL — it moves a page someone may be using. |
browser_click | write | Click an element as the signed-in user. |
browser_fill | write | Set a form field, dispatching the input and change events frameworks listen for. |
browser_type | write | Type as real keyboard input, for fields that only react to genuine typing. |
browser_eval | write | Evaluate JavaScript in a tab and return its value. chrome:// pages are refused. |
browser_webmcp_call | write | Run a tool the page declares. Executes site code as the signed-in user. |
There is no snapshot-and-click-by-id model. Targets are CSS selectors —
browser_eval is how an agent discovers them on an
unfamiliar page: list a form's fields and their names before filling anything. There is
also no new-tab tool; paniolo browser launch <url>
opens a URL in the running instance and browser_navigate
moves an existing tab.
Writes ask, once, on real sites
Every write tool refuses on an origin that is not loopback, file:
or about:, returning confirmation_required
and naming the origin. The agent shows the human what it is about to do, gets a yes, and
retries with confirm: true. Loopback is ungated on
purpose — a flag everyone always passes has stopped being a gate, so the friction lands
where it matters: a local dev server is free, production asks.
The gate exists because the debug port is session-equivalent access:
evaluating an expression against a signed-in production tab is a different act from the
same expression against a dev server, and cookies are readable over CDP even when marked
httpOnly. That is also why the profile is a boundary
rather than a convenience — the browser never attaches to the human's everyday profile,
and Chrome ignores the debugging flag on a default profile anyway.
Pages that describe themselves
A page that declares WebMCP tools exposes them through
document.modelContext.
browser_webmcp_list returns each declared tool's name,
description, input schema and annotations; browser_webmcp_call
runs one with an input object:
{ "tool": "fill_contact_form", "input": { "name": "Example Person" } }When a call is gated, the refusal reports what the page claims about the tool — its annotations. That claim is page-supplied data: a page can omit it or lie, so it is shown to help a human decide and never used to decide whether to ask. Fill-type tools are usually safe; submit-type tools send, pay, delete or publish. Sites that gate submission behind a bot check may declare a wait-for-verification tool — use it before the submit tool rather than polling the DOM.
If the list stays empty after the app is ready and a bounded wait, fall back to DOM
actions — browser_click,
browser_fill, browser_type
— for the user's ordinary task. Ordinary sites do not need WebMCP. For the standard on
this site's own contact form, see Agent-First.
The failure modes worth knowing
| Symptom | What is going on |
|---|---|
The agent has no browser_* tools | A host problem, not a browser problem. If query works and browser_tabs does not, the host is pre-0.5.76; if neither works, the Paniolo server is not loaded. A responding debug port does not prove otherwise. |
| Window opens, port closed | A launch against a running instance on the same profile forwards to it and drops the new flags. paniolo browser launch reuses the live instance instead of racing it; if flags are still missing, teardown and relaunch. |
| A normal Chrome window will not answer | Opening a URL through the OS default browser hits a different profile with no debug port. Always paniolo browser launch or browser_navigate. |
| A fill looks applied but submits empty | Setting a value without dispatching input and change leaves the framework holding old state. browser_fill dispatches both; driving the DOM through browser_eval does not. |
| Input vanished | Navigating or relaunching reloads the page and unsent form input is lost — refill after any restart. |
| Submission silently discarded | Pages hide honeypot fields for bots to fill and humans to skip. Do not fill them. |
| Sign-in confusion | The human signs in inside the shared window — signing in on another profile does not authenticate the browser the agent controls. Then verify with browser_tabs and inspect the page. |
The browser is a surface, not a separate product
The tools are siblings of the CLI commands, served by the same stdio server as retrieval, wiki, and staleness — see MCP server for the transport, the read/write split, and the full 39-tool catalog. For what happens when a page declares its own tools — including the origin trials and the three silent bugs we fixed on our own contact form — read Agent-First.