Two ways to expose a form to an agent.
The W3C WebMCP draft defines four declarative attributes - toolname, tooldescription, toolautosubmit, toolparamdescription - that turn an ordinary <form> into a WebMCP tool. There are two ways to put them on a form: let cf-webmcp inject them at the edge from one TOML block, or hand-stamp them in your origin HTML. Same contact form, both approaches, side by side below.
Contact form cf-webmcp injects
This first form has no WebMCP attributes in the origin HTML. The publisher kept their template clean. cf-webmcp stamps the four attributes onto the <form> and onto each <input> at the edge using a [[forms]] block in webmcp.toml. View source on this page to see the injected attributes appear inline on the form below.
What cf-webmcp does: matches the form by CSS selector at request time, sets toolname, tooldescription, optional toolautosubmit, and a toolparamdescription on each declared input. Honours the publisher's hand-stamps if any already exist (does not overwrite).
What cf-webmcp does not do: generate the form, change its layout, alter the submit handler, or hide the form from non-agent visitors. It only adds attributes the browser interprets as a WebMCP tool registration.
Contact form hand-stamped
This second form is identical in shape, but the publisher has stamped the WebMCP attributes directly into the HTML themselves. No TOML block is needed - the browser reads the attributes from origin and registers the tool. This is the path for teams that prefer to manage agent-exposed surfaces from inside their CMS or template.
What cf-webmcp does: nothing extra for this form. It passes the attributes through cleanly along with the rest of the response.
What cf-webmcp does not do: modify or remove the attributes. The publisher owns this form's WebMCP surface end to end.
How to test
You can verify each form three ways:
- View source on this page. Both forms show their attributes inline. The first form's attributes are stamped by the Worker; the second form's attributes came from origin. Same end result in the HTML.
- List tools from devtools. Enable
chrome://flags/#enable-webmcp-testingand run in the console on this page:
Look forawait navigator.modelContextTesting.listTools()contact(from the injected form) andcontact_alt(from the hand-stamped form) alongside the imperative tools registered by the cf-webmcp bootstrapper. - Invoke one as an agent. Still in devtools:
The page's submit handler catches the agent-invoked submission viaawait navigator.modelContextTesting.executeTool( "contact", JSON.stringify({ name: "Ada", email: "ada@example.com", message: "hi" }) )SubmitEvent.agentInvokedand responds with a canned JSON envelope. Nothing is actually posted to a server - this is a pure demonstration. A real publisher would replace the canned response with their own server-side handling.
What the publisher gets
- Zero JS work to register a form as a tool. The browser parses the attributes during HTML parse and registers the tool automatically. No
navigator.modelContext.registerToolcall needed; that is for the imperative path. - Edge-side or origin-side, your choice. Use the TOML
[[forms]]block to add WebMCP to existing CMS forms without editing templates. Or hand-stamp the attributes in your HTML if you prefer that control. Both forms above work the same way once rendered. - Native browser input validation runs first.
required,type="email", and friends fire before the tool is invoked. The agent never sees an invalid submission. - Non-agent visitors see the form unchanged. The four attributes are inert to humans; the form is just a form.
- Agent-invoked submissions are distinguishable. The W3C draft adds
SubmitEvent.agentInvoked(read-only boolean) andSubmitEvent.respondWith(promise)so a singlesubmithandler can serve both audiences from one code path.