If you haven't come across WebMCP yet, the quick version is this: it's a W3C proposal that lets a web page register callable actions directly in the browser using document.modelContext. Think of it as an in-browser MCP server. Instead of an AI agent scraping your UI or talking to a separate backend, your page declares what it can do, and the agent calls those actions directly with the user's active session and current state already in scope. Sylwia Lasek wrote a great first-look piece on it here if you want to understand the fundamentals before going further.
The problem we noticed is that the specification addresses how tools run in the browser, but it doesn't address discovery. An agent visiting a page can query document.modelContext.getTools() once it gets there, but it has no way of knowing what tools are available on any given site before loading it. That gap matters for agents that need to plan ahead, inject tool schemas into a system prompt, or decide which sites are worth visiting at all.
So we built two things.
The WebMCP Registry is a public directory at webmcp-registry.dev where developers register their domains and tool contracts. It's queryable by keyword, category, or domain, it has a verification system based on DNS TXT records, and it exposes a public HTTP API so agents can look up what a site can do before visiting it. The registry also registers its own tools using WebMCP, which felt like the right move.
@webmcp-registry/kit is the npm package that handles the developer side. You get defineTool for describing a tool with a Zod schema and a handler, React hooks for registering tools from components, and a CLI called
webmcpfor syncing your tool contracts to the registry automatically.
Here's what a tool definition actually looks like:
import { defineTool } from '@webmcp-registry/kit'
import { z } from 'zod'
export const addTodoTool = defineTool({
name: 'add-todo',
description: "Add a new item to the user's active todo list",
kind: 'write',
input: z.object({
text: z.string().describe('The text of the todo item'),
}),
handler: ({ text }) => {
const todo = addTodo(text)
return { added: todo }
},
})
defineTool generates the JSON Schema from your Zod input, wraps validation and the { content: [...] } response format the WebMCP spec expects, and gives you back a plain object that the browser can register. No manual schema writing.
To register it, you drop it into a component using the provided hook:
import { useWebMCPTools } from '@webmcp-registry/kit/react'
import { todoTools } from './todo.tools'
function TodoApp() {
useWebMCPTools(todoTools)
// rest of the component
}
The hook registers your tools when the component mounts and cleans up when it unmounts. If document.modelContext isn't available (which is still the case in most browsers today since WebMCP is currently behind a flag in Chrome), it's a silent no-op in production and a named console warning in development so you know what's waiting on the platform.
One thing that the kit handles that's less obvious: some tool handlers can't live at module scope because they need to close over component state. A useState setter, a useReducer dispatch. For those cases you split the schema from the handler using defineToolContract, which gives the CLI something to statically discover while keeping the handler where it actually needs to live:
// draft.tools.ts
export const setDraftContract = defineToolContract({
name: 'set-draft-text',
description: 'Set the text in the new todo input without submitting',
kind: 'write',
input: z.object({ text: z.string() }),
})
// App.tsx
useWebMCPTool(setDraftContract, ({ text }) => {
setDraft(text)
return { draft: text }
})
Once your tools are defined, syncing them to the registry is one command:
npx webmcp sync --domain yoursite.com --api-key $WEBMCP_REGISTRY_KEY
The CLI scans your project for *.tools.ts files, imports them in an isolated Node context, hashes each tool contract, and pushes only what changed. A tool that no longer exists in source gets tombstoned rather than deleted outright so the history stays intact. Run it on every deploy, it's a no-op when nothing changed.
The package is on npm as @webmcp-registry/kit, the registry source is at github.com/WebMCP-Registry/kit. WebMCP isn't broadly available yet, but the point is to have the infrastructure ready so when it does land, developers aren't starting from scratch.
If you're building a web app and want to take it for a spin, define a tool, push it to the registry, see what the agent-side lookup actually looks like. Would be interested to hear how it fits different types of apps.
Top comments (0)