Quick start
The golden path: open → list → register →
execute → close. Every step prints evidence.
export AGENT_WEBMCP_REGISTRY=$PWD/.agent-webmcp/registryH=$(agent-webmcp open --json https://example.com \ | python3 -c "import json,sys; print(json.load(sys.stdin)['handle'])")agent-webmcp list $Hagent-webmcp close $H --yesbashOpen and list
open attaches the page and prints a handle plus a
toolCount. That count is point-in-time: pages expose
tools as they load, so 0 means list again, not give up.
agent-webmcp open --json https://example.com# {"handle":"s_…","url":"…","toolCount":0,# "reapplied":[],"skipped":[]}agent-webmcp list $H# name — description rows on a TTY, JSON when pipedbashNever call a tool you have not listed. An empty list
means the page exposes nothing — say so instead of
guessing. Fetch one tool's full record (schema included)
before calling it:
agent-webmcp list $H <tool-name>bashAuthor your first tool
Pages that expose nothing still take authored tools. This
one answers pong; the fixture proves it before
anything persists:
agent-webmcp register $H \ '{"name":"ping","description":"Ping fixture tool", "inputSchema":{"type":"object","properties":{}}, "fixtureInput":{}}' \ '(async () => { return { content: [ { type: "text", text: "pong" }] }; })' \ --yesagent-webmcp list $H # ping reads authored:truebashWrites need --yes. Piped output is JSON. See
Custom tools before authoring
against a real page — authoring blind is forbidden.
Compose and close
search and execute live on MCP only. Over MCP the
same session reads:
{ "handle": "s_…", "code": "return await tools.ping();" }jsonexecute returns {value, spilled, toolCalls, perSession, origins, untrusted:true} — untrusted is
always true. Then release what you opened:
agent-webmcp close $H --yesagent-webmcp open --json https://example.com# reapplied:["ping"] — memory worksbashClose what you open. close kills only browsers the
engine launched, never foreign ones.