WebMCP surface: three of eight tools are plain HTML forms using the declarative toolname/toolautosubmit API - no JavaScript tool registration for those three.

Inspiration

A customer with a fuzzy need - "my new headphones crackle, return or warranty?" - doesn't know which form applies. We wanted the site's own forms to answer that, instead of a chatbot re-explaining policy in prose.

What it does

The agent reads orders and policy, says which form applies and why, and fills it through the form itself. Three of the desk's eight tools are <form toolname tooldescription> elements whose controls carry toolparamdescription - Chrome derives the JSON schema straight from the markup and fills the controls; the page never writes those values. Whether a form submits itself is controlled by one attribute: request_return has no toolautosubmit, so a human must press Submit; file_warranty_claim and schedule_pickup carry it, because a click undoes either one.

How we built it

One file, vanilla JavaScript. The page handles submit with event.preventDefault(), and when event.agentInvoked is true, responds via event.respondWith(Promise). Five imperative read tools share the same invoke() chokepoint the three forms' Submit handler uses. A getTools() probe shims any form Chrome doesn't register natively, so every judge's browser gets all eight tools regardless of native declarative support, and a status pill says which mode is active.

Challenges we ran into

Chrome fires toolactivated around a declarative tool call, but its exact target and bubbling behavior are undocumented - we ended up listening in two places (a capture listener on window and the identical handler on document.modelContext) to catch it reliably either way. Separately, the very shim that covers non-native browsers had its own bug: getTools() can't distinguish a real native registration from the shim's own echo of itself, which caused a silent register/unregister oscillation - measured at roughly 145,000 cycles in three seconds before it was caught and fixed with an occurrence-count comparison instead of a name-presence check.

Accomplishments that we're proud of

A confirmation boundary expressed in markup - the presence or absence of one HTML attribute - rather than in a prompt instruction a model could ignore or a developer could forget to re-add.

What we learned

The declarative API pushes real complexity into event-timing edge cases (activation ordering, shim-vs-native detection) that don't exist in the imperative registerTool path - worth it for forms that need zero JavaScript, but not free.

What's next

Extending the declarative pattern to the pickup-scheduling calendar itself, so date/slot selection is also derivable straight from markup instead of a small script.

How we built it

Challenges we ran into

Accomplishments that we're proud of

What we learned

What's next for Dhwani - a support desk whose forms are the tools

Part of a seven-app portfolio

Each entry proves a different WebMCP capability: Tablemap (dynamic tool registration), Signoff (a tool that only exists once a human approves it), Paperwork (structurally human-only fields), Shortlist (a tool schema generated live from the catalog), Jhola (genuinely cancellable long-running tools), Almari (quarantined untrusted content).

Built With

Share this project:

Updates

Submission history