The open-source developer stack for WebMCP.
Today: codegen. It generates a safe, reviewable agent surface from the contract you already have, and it is built to grow into the rest of the lifecycle.
WebMCP lets a website expose typed tools that AI agents can call in the browser, with the signed-in user's session and no server or build step. What you expose to agents matters: the caller is a model acting as your user, possibly while reading attacker-influenced page content. webmcp-stack is the tooling for it: generate the surface from the API contract you already have, review it, and keep it honest in CI.
@webmcp-stack/codegen turns an OpenAPI spec, or your zod/valibot/arktype/TypeBox schemas, into WebMCP tools in your own codebase:
npx @webmcp-stack/codegen generateNo install, no config for the first run. It detects your app, writes one .webmcp.ts file per tool, and adds the registration call to your entry file (additive edits, always reported; if it can't find the entry point, it prints the two lines for you to paste).
It generates from the contract you maintain, not by scanning your app and guessing at intent. A vague spec or schema makes vague tools, so the source is the part worth getting right. If you have an OpenAPI spec or maintained schemas, this is built for you; if not, writing that contract is the first step, and the schema source lets you declare tools by hand in the meantime.
npx @webmcp-stack/codegen dev # local dashboard: browse, edit, toggle, and test tools
npx @webmcp-stack/codegen verify # check the tool set against the standard; exits 1 on errorsNo install yet? The playground runs this same pipeline on a spec you paste into the browser, and shows the tools it finds in the same dashboard.
You can, and it works. What you get back is different every time, and nothing checks it. Each tool needs the same small decisions made correctly: read or write, registered or hidden, user confirmation or not, trustworthy output or not. Across 40 endpoints that is hundreds of decisions, easy to forget and tedious to apply by hand. The generator makes each one once, from rules, and applies it to every tool on every run, so you review a diff and gate it in CI. It also knows the spec trivia: a rejected execute reaches the agent as a bare UnknownError with your message discarded, so generated tools return readable errors instead of throwing.
- Decides what agents may do. Every endpoint is classified read, write, or destructive from the HTTP verb, corrected when the name disagrees (
POST /orders/{id}/cancelis destructive,POST /searchis a read). Reads work immediately. Everything else is generated but not registered, so enabling a write is a deliberate edit. Webhooks are skipped, auth and admin endpoints are flagged, and anything it cannot classify starts disabled. - Asks the user before any mutation. Write and destructive tools confirm each call with the user (a plain dialog you can replace). The confirmation lives in the generated region of the file, so it cannot be edited away and survive regeneration.
- Writes the text agents read. Constraints become sentences ("A number from 30 to 600."), names come from intent (
generate-story, notpost-trips-trip-id-story-generate), and every description says what the tool returns. Your own text always wins; machine-written text is marked and flagged in the audit. Chrome's character budgets are treated as guidance, not law: machine text is composed to fit, author text is never silently shortened, andverifywarns on an overrun. - Groups actions the API split. A begin/end pair like
request-uploadplusuploads/{uploadId}/completeis one action, not two. The generator detects the pair, threads the first response into the second by exact name, and adds one withheld coarse tool next to the members. A pair it cannot thread is skipped with a note. - Scaffolds journeys and an agent skill.
journey.webmcp.tsplusjourneys/*.webmcp.tsexpress a multi-step flow: a page-scoped draft, one tool per step, and a submit gate that refuses until every step is done and confirms with the human before the real write. The barrel registers them andverifylints the files..agents/skills/webmcp-tools/SKILL.mdteaches your own coding agent the rules. - Says what can be trusted. Free-text outputs get
untrustedContentHint; PII-looking fields are named in the report and in a comment in the file; mutating tools on authenticated endpoints carry a session warning;executecalls your real endpoint, so your own validation still runs. - Annotates real forms. A tool that maps to a visible
<form>can annotate it in place, so the agent fills the same controls the user sees, and the user reviews and submits the write.
A real tool from a real app: create-trip, one of 70+ tools generated for beenthere.page from its OpenAPI spec, shortened for the README:
// --- webmcp-codegen: generated. Do not edit this region. ---
/**
* Create a new trip. Returns the trip.
* Source: POST /v1/trips/ (openapi). Risk: write-confirm.
* Starts withheld: not registered until you enable it.
*/
export const createTripTool = {
name: "create-trip",
title: "Create Trip",
description: "Create a new trip. Returns the trip.",
inputSchema: createTripInputSchema, // title, dates, location, theme, field notes
annotations: {
readOnlyHint: false,
untrustedContentHint: true,
consequentialHint: false,
},
};
// Journeys and your own code compose this raw caller; executeCreateTrip wraps it
// in the agent-facing result shape.
export async function fetchCreateTrip(input: CreateTripInput, signal?: AbortSignal) {
const data = await callApi("/v1/trips/", { method: "POST", body: { ... }, signal });
return data;
}
// A write tool, so it is withheld: the registration is generated but commented
// out, including the built-in user confirmation, until you enable it:
// const confirmed = await requestUserConfirmation(
// "Allow the agent to: Create a new trip. Returns the trip.",
// );
// --- webmcp-codegen: end generated. Your code below survives regeneration. ---
export async function executeCreateTrip(input: CreateTripInput, signal?: AbortSignal) {
return toolDisabled("create-trip.webmcp.ts");
// Uncomment to go live, and uncomment the registration above:
// return toolResult(await fetchCreateTrip(input, signal));
}The bar the ecosystem is converging on (Chrome's WebMCP best-practices and tool-security docs, plus the writeups around them), and where the generator stands against it:
| The bar | Today |
|---|---|
| Verb-first, intent-shaped names, 30 characters or fewer | Enforced on every run |
| Descriptions say what the tool does and when, positively, within 500 characters; every parameter described within 150 | Assembled on every run; author text is never shortened, and verify warns on overruns |
Outputs within 1.5K characters; errors that help recovery; user-written content marked untrustedContentHint |
Errors, annotations, and the output cap enforced; the cap lives in the generated runtime |
Exposure as a decision: readOnlyHint, registration only where usable, exposedTo origin scoping |
Annotations and withheld-by-default enforced; exposedTo is a config pass-through to registration |
| The human in the loop: visible page effects, confirmation on consequential actions | Confirmation enforced, in the generated region where it can't be edited away; visible-effect hook scaffolded |
| No steering: descriptions never instruct the agent or encode flow control | Flagged in the audit |
| The schema is not the security boundary; the app still validates at run time | Stated in the output; execute calls your real endpoint |
verify measures this locally and exits 1 on errors, so the bar is a CI gate, not a hope.
.webmcp-codegen.json: per-tool overrides for descriptions, enabled state, and field text. Applied last, so your text always wins.- Dashboard (
dev): edit descriptions, toggle tools, run tools against real endpoints. Writes back to the overrides file. - Audit: problems reported in plain language every run: missing descriptions, mislabeled verbs, PII in outputs, agent-instruction smells, oversized surfaces. Errors block file writing; warnings do not.
verify: the standard, checked locally. Built for CI.- Skill file:
.agents/skills/webmcp-tools/SKILL.mdteaches your own coding agent the rules (naming, description budgets, journeys). Regenerated on everygenerate; add your own skill directory to stack project-specific rules on top.
Codegen first:
- More sources. OpenAPI and validation schemas today, tRPC on the list. The rule holds: contracts, not codebases, and the CLI never scans app code.
- The dashboard becomes the review surface. A browse-and-score report over the generated surface, with the editing UI kept for the overrides it writes. Parked until the audit package exists.
- Skill-file evals. Shipped at
packages/codegen/evals/skill/: a prompt set, a generated fixture, deterministic graders, and a control case that runs the sharpest prompt with the skill removed, so you can tell when a model has absorbed the rules.
Then the stack around it: audit (point it at a URL, get a report on the surface a visiting agent would find) and telemetry (how agents actually use your tools). The goal is one stack where each tool covers one stage of the lifecycle and they compound. The bet underneath: websites are growing an agent-facing surface the way they grew APIs, and that surface needs the same kind of tooling, with higher stakes, because the caller is a model acting as your user, inside your page.
The guarantees hold through all of it: the repo pins the WebMCP draft it targets and watches for spec drift; your overrides, execute bodies, and review decisions survive every regeneration; breaking changes print the exact fix. Deterministic where it can be, honest always.
- The generated code belongs to you. Real files in your repo, no runtime dependency. Regeneration rewrites only the contract region of each file; your
executebody is written once and then it's yours. Inspect it, modify it, delete the generator, keep the files. - Safety first. An agent-facing surface is a security surface. The tools help you decide what to expose, then enforce the decision.
- Open-source first. Everything here is genuinely useful on its own and self-hostable. Any future cloud offering adds convenience, never a gate.
Repository layout
| Path | npm name | What it is |
|---|---|---|
packages/codegen |
@webmcp-stack/codegen |
The CLI and the generation pipeline: sources (OpenAPI, validation schemas), outputs, the safety audit, the dev dashboard. |
examples/openapi-petstore |
private | Example app with tools generated from the Petstore OpenAPI spec. |
site/ |
private | Landing page and documentation (Next.js + Fumadocs). |
docs/ |
- | About, design specs (specs/), decision notes (notes/), research (research/), and reviews (reviews/). |
scripts/ |
- | Committed git hooks (lint on commit, lint + typecheck + test before push). |
brand/ |
- | Logo and brand assets. |
Development
pnpm install
pnpm build # turbo run build
pnpm test # turbo run test
pnpm typecheck
pnpm lint:fixpnpm --filter example-openapi-petstore dev # example app
pnpm --filter site dev # landing page & docs on :3001MIT