Two layers sit outside React, and we own them too. On top, your visitor's agent gets the same buttons your visitor has. Underneath, nginx decides how quickly each byte lands.
Agent-ready: WebMCP
WebMCP is a W3C draft. A page tells the agent in the visitor's browser which actions it offers, as tools, and the agent stops guessing at selectors. Each tool here calls the function the button calls, so there is one behaviour to reason about.
Declarative · three attributes
The guestbook form names itself as a tool and describes its fields. That is the whole integration. The agent fills the form in; the visitor still presses Sign.
<form toolname="sign_guestbook"
tooldescription="Leave a short public note in the guestbook.">
<input name="name" maxlength="40"
toolparamdescription="Display name, max 40 characters" />
<input name="message" maxlength="140"
toolparamdescription="The note, max 140 characters" />
<button>Sign</button>
</form>
Sign it yourself on /guestbook
Imperative · one island
Search answers as you type, so there is no form to annotate. A small island registers search_feed instead. Its execute calls searchFeed, the same server function the search box calls. Browsers without WebMCP skip it, and leaving the page unregisters the tool.
document.modelContext?.registerTool({
name: 'search_feed',
execute: async (input) => ({ items: await searchFeed(str(input.query, 80)) }),
}, { signal })
Search the feed on /search
An agent's input deserves the same care as anyone's. Both server functions narrow and cap what they receive on their first line: str(value, max), one helper, no validation library.
see the tools: Chrome 149+, enable chrome://flags/#enable-webmcp-testing, then run await document.modelContext.getTools() in the console