One method.
Every surface.
Write a capability once as a typed method. Call it from your UI, your HTTP API, MCP and any agent, with permissions built into the contract.
View on GitHub
$npm install @willyim/kit zod
export const notes = declareService((ctx) => ({
create: method(
{
summary: "Create a note",
permission: "notes:write",
input: { title: z.string() },
output: { id: z.string() },
},
async ({ title }) => ctx.notes.insert({ title }),
),
}))- UI
ctx.services.notes.create({ title })React Router loaders and actions, in process and fully typed. - HTTP
POST /api/notes.createPlus/openapi.jsonand/llms.txt, filtered per caller. - MCP
toMcpServer(app, ctx)The toolnotes_create, with MCP Apps views when you want UI. - Agents
tools(app, ctx)One neutral tool list for the Claude Agent SDK, AI SDK or anything else.
Permissions are part of the contract
Every method declares who may call it. kit checks it on every surface before your function runs, and discovery only lists what the caller can call, so a hidden method never reveals that it exists.
GET /llms.txt # notes > Personal notes. ## Methods - notes.get: Get one note by id - notes.list: List notes in the workspace - notes.create: Create a note - notes.delete: Delete a note
POST /api/notes.create200Returns { id }, stripped to the output contract.What ships in the package
One install. Each subpath is optional.
@willyim/kit | Apps, services, methods, permissions, row policies, tools(), and the HTTP handler with OpenAPI 3.1 and llms.txt. |
@willyim/kit/mcp | An MCP server from your app, with MCP Apps views. |
@willyim/kit/react | Show or hide UI by the caller's grants. |
@willyim/kit/idp | Login, API keys and bearer tokens through the willy.im IdP. |
@willyim/kit/log | Structured logs, one line per call by default. |
@willyim/kit/audit | Semantic audit events, stored in D1 or Postgres. |