Skip to main content
willy.im / kit

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
TypeScript + zodReact Router, OpenAPI, MCPMIT
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 }),
  ),
}))
  • UIctx.services.notes.create({ title })React Router loaders and actions, in process and fully typed.
  • HTTPPOST /api/notes.createPlus /openapi.json and /llms.txt, filtered per caller.
  • MCPtoMcpServer(app, ctx)The tool notes_create, with MCP Apps views when you want UI.
  • Agentstools(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/kitApps, services, methods, permissions, row policies, tools(), and the HTTP handler with OpenAPI 3.1 and llms.txt.
@willyim/kit/mcpAn MCP server from your app, with MCP Apps views.
@willyim/kit/reactShow or hide UI by the caller's grants.
@willyim/kit/idpLogin, API keys and bearer tokens through the willy.im IdP.
@willyim/kit/logStructured logs, one line per call by default.
@willyim/kit/auditSemantic audit events, stored in D1 or Postgres.