Build the product once.
Let their agents use it too.
Launchpad is a small project-management app. Its six operations are defined once, with one schema, one middleware chain and one run() — then projected to the browser, to progressive-enhancement forms, to in-page agents, to signed remote callers, and to MCP tools at /mcp. Same business rules, by construction.
The product
<Form capability> posts to the capability endpoint — with a form-encoded fallback when JavaScript never arrives.
The loader
invokeCapability() from SSR runs the same validation and middleware chain, no HTTP round trip.
The tab
An in-browser agent gets the capability as a page tool, acting as the signed-in user in their own session.
The network
A remote agent signs its request with RFC 9421 and gets a verified identity, policy checks, and an audit event.
The tool list
POST /mcp serves the same capabilities as MCP tools over stateless Streamable HTTP — minus the destructive one.
export default defineCapability({
title: "Archive project",
input: { type: "object", properties: { projectId: { type: "string" } } },
effect: "destructive", // → webmcp/mcp exposure is a build error
expose: { http: true }, // → prepare/commit + a human approval
async run({ input }) { … },
});The security model is the product
Four things the framework refuses to let you get wrong
Private by default
No loader and no API route is ever inferred as a tool. A capability without an explicit expose is unreachable over the network.
Destructive is gated, not annotated
A destructive capability cannot be exposed to WebMCP or MCP at all, and its HTTP dispatch is refused until a person approves the proposal.
Identity is verified, not claimed
Web Bot Auth checks an Ed25519 signature over the request before context.agent exists. Unverifiable means null, never a partial identity.
Widening shows up in review
pracht plan marks a new exposure, a dropped required field or a raised bound with a ! — the diff answers 'did this let agents reach more?'
Still a web framework
Every route renders the way it should
This page
Pre-rendered at build. Zero server cost.
Pricing
Wants hourly revalidation — see the note in routes.ts.
Dashboard
Per-request, personalised, always current.
Settings
Client-only. Shell paints instantly, no SEO needed.
One manifest, one build, one deployment — and the same manifest is where the capability graph, the trust config and the machine-enforced constraints live.