WebMCP surface: the apply_proposal tool doesn't exist in the registry until a human approves the agent's proposal - enforcement lives in tool existence, not a runtime permission check.
Inspiration
Working a refund queue is judgment sandwiched between drudgery: read the ticket, weigh the amount against the risk, write a defensible reason, repeat forty times. The judgment is the human's job. The reading, weighing, and drafting shouldn't be.
What it does
The agent reads a written refund policy and every request's evidence, proposes approve / deny / partial decisions with a written reason each, and stages them as cards next to the requests they concern. Nothing applies until a human clicks Approve - and apply_proposal, the tool that actually applies a decision, is registered only while at least one approved-but-unapplied proposal exists. Approve nothing, and the tool to apply anything simply isn't in the agent's list.
How we built it
One file, vanilla JavaScript, no framework. Every tool and every UI button call the same mutation through one invoke() chokepoint that also writes the shared activity log. apply_proposal dynamically registers/unregisters via an AbortController tied to whether a pending approved proposal exists - a permission boundary expressed as which tools exist, not as an instruction a model could ignore.
Challenges we ran into
Two rounds of adversarial testing against real Chrome (not headless mocks) found two distinct bugs in that dynamic-registration path: first, a self-abort race where the tool's own disarm logic cancelled the very call it was running under; then, after fixing that with a deferred abort, a second race where a fast Approve → Revoke → re-Approve cycle could silently desync the tool from Chrome's real registry - no error, but the tool had actually vanished. Root-caused via page.mouse.click() (real coordinate clicks reproduced it 12/15 times; Playwright's polling clicks never did), then fixed by tracking and synchronously draining any stale pending abort before re-arming.
Accomplishments that we're proud of
An approval boundary enforced by the browser's own tool registry, not by prompt instructions - and proven against the actual WebMCP bridge under adversarial, rapid-clicking conditions, not just the happy path.
What we learned
"It passed headless Playwright" and "it's correct against real Chrome" are different claims. The riskiest bugs in a dynamic-tool-registration pattern only show up under real, synchronous, same-tick timing.
What's next
A written audit trail export per decision, and richer risk-signal explanations sourced from order history across the whole account, not just the single request.
Part of a seven-app portfolio
Each entry proves a different WebMCP capability: Tablemap (dynamic tool registration), Paperwork (structurally human-only fields), Shortlist (a tool schema generated live from the catalog), Jhola (genuinely cancellable long-running tools), Almari (quarantined untrusted content), Dhwani (declarative, no-JavaScript tools).
Built With
- chrome
- css3
- html5
- javascript
- playwright
- webmcp
Log in or sign up for Devpost to join the conversation.