WebMCP tools
Offer an agent in the reader's browser the actions a page already offers — search, open a page, open the privacy settings — with useWebMcpTool from @voltro/web. Progressive enhancement; every call decoded by its Schema.
WebMCP lets a page register tools an AI agent in the browser can call: document.modelContext.registerTool(...), a W3C Web Machine Learning Community Group draft that Chrome ships behind an origin trial. useWebMcpTool from @voltro/web offers one tool for as long as the component that declares it is mounted.
import { Schema } from 'effect'
import { useNavigate, useWebMcpTool } from '@voltro/web'
export function SiteSearch() {
const navigate = useNavigate()
useWebMcpTool({
name: 'searchSite',
description: 'Search this website and show the results.',
input: Schema.Struct({ query: Schema.NonEmptyString }),
annotations: { readOnlyHint: true },
execute: ({ query }) => {
navigate(`/search?q=${encodeURIComponent(query)}`)
return 'Showing the results.'
},
})
return null
}What it does
- Nothing without the API. In a browser without WebMCP the hook registers nothing and the page is unchanged.
webMcpAvailable()says which case you are in. - The input is a Schema. The agent is shown the JSON Schema derived from
input, and every call is decoded with it beforeexecuteruns. A call that does not decode is answered with the reason, and nothing runs. - A tool lives with its component. It is registered on mount and unregistered (its
AbortSignal) on unmount, so an agent sees the tools for what is on the page now. Passnullto offer no tool in a state where it makes no sense. executeis always the latest render's. The tool is registered again only when its name, title, description, input or annotations change.- A refused registration leaves the page alone, for example a duplicate name or a
toolspermissions policy that does not allow it. A name the draft would refuse (outsideA–Z a–z 0–9 _ - ., over 128 characters) or an empty description throws, because that is a mistake in the code.
registerWebMcpTool(tool) is the same without React: it returns the function that unregisters the tool.
What to offer
Offer what the reader can already do on the page: search, filter a list, open a page from the navigation, open the privacy settings. Do not offer reading the page's text (the agent reads the page), a generic "call the API" or "submit any form", or anything from an editor or admin area.
Keep arguments to what the action needs. Page state, cookies and hidden fields do not belong in a tool's input. A tool that sends something (a form, an order) should leave the final step to the reader and set consequentialHint: true.
The browser's mediation is not an authorization check. What a tool causes on the server is decided by the server, exactly as for the reader's own click.