WebMCP
WebMCP lets a page expose tools (named actions with a JSON-schema input) to an AI agent running in the browser. Catalyst ships it as catalyst-core/webmcp.
WebMCP support is experimental. Its API, including useTool, WebMcpProvider, the route tool field, the webmcp:* document events, and inspect/invokeTool, may change in a minor release.
Not the same as MCP Integration. That page is about a Node-side MCP server that helps AI coding tools understand your project. WebMCP runs in your app's browser tab and exposes the app's own actions to agents.
Status. Catalyst targets the opt-in DOM shim (
catalyst-core/webmcp/shim). Native browserdocument.modelContextis intentionally unsupported for now, because no agent can drive it yet.
Setup
Install the shim once, before the provider mounts, and wrap your app in WebMcpProvider inside RouterDataProvider:
import { WebMcpProvider } from "catalyst-core/webmcp"import { installShim } from "catalyst-core/webmcp/shim"import routes from "./routes"installShim()export const App = ({ children }) => (<WebMcpProvider routes={routes} filterNavigable={(route) => !route.path.startsWith("/docs/")}>{children}</WebMcpProvider>)
routes is your own route table. filterNavigable is optional and keeps large generated page sets out of the built-in navigate tool's enum.
The provider registers three built-in tools for you: navigate, get_current_route and get_page_info (built from the page's setMetaData).
Three ways to author tools
1. Declarative, on the route. Add tool to a route and Catalyst derives the name, the input schema (path params required, searchParams optional) and a navigating execute. These are available app-wide.
{path: "/products",component: ProductList,tool: {description: "Browse the catalogue, optionally filtered.",searchParams: { category: { type: "string", enum: ["footwear", "apparel"] } },},}
2. Imperative, in a component. useTool registers an action tool for the component's lifetime and removes it on unmount or when the user leaves the route.
useTool({name: "add_to_cart",description: "Add the current product to the cart.",inputSchema: { type: "object", properties: { quantity: { type: "number", minimum: 1 } } },execute: async ({ quantity = 1 }) => {dispatch(addItem({ id, quantity }))return { added: true }},})
3. Stateful, with deps. Pass a deps array when the description or schema depends on state (for example an enum of cart item ids). Catalyst refreshes the registration in place, so a call already in flight still completes.
useTool({ name: "remove_from_cart", /* schema built from cartItemIds */ execute }, [cartItemIds.join()])
Errors
Throw WebMcpError with a code from WEBMCP_ERROR_CODES (for example INVALID_ARGS, TOOL_NOT_FOUND) so the agent gets something to act on. Arguments listed in the schema's required array are checked before execute runs.
Design lessons
- Do side effects in the tool, not in a hint. Agents routinely ignore "now call X" hints in a result. If navigating, highlighting or saving matters, the tool must do it itself.
- Mark intent. Use
readOnlyHintfor reads andconsequentialHintfor irreversible actions such as checkout. - Keep names unique. Route tool names derive from the path and must not collide with
navigate,get_current_routeorget_page_info. Collisions log a warning and the later tool is dropped.