Getting started
stitchkit turns one contract into an HTTP API, MCP tools, AI-agent tools and a typed client. This page gets a working app running; the rest of the guide goes deep on each piece.
Requirements
- Bun
>= 1.2(recommended) or Node.js>= 22. Bun is first-class; Node is supported viastitchkit/node.
Install
bun add stitchkit zod
zod is a required peer — schemas are the source of truth everywhere. Other
peers are optional and pulled in only when you use the matching feature
(@modelcontextprotocol/sdk for MCP, ai for agents, socket.io* for
realtime, @tanstack/react-query + react-query-kit for React). See
deps below.
Entrypoints
stitchkit ships eight entrypoints. Each is import-safe for one environment —
keeping server-only code (Bun.serve, the MCP SDK) out of browser bundles.
| Import | Use in | Holds |
|---|---|---|
stitchkit |
browser and server | defineContract, createClient, createHttpClient, createSocketIOClient, parseSSE, the error model |
stitchkit/contract |
browser and server | the contract layer alone — defineContract, errors, pagination |
stitchkit/server |
server (Bun) | createServer, implement, hooks, auth, Socket.IO server, server primitives |
stitchkit/node |
server (Node ≥ 22) | serveNode + the runtime-agnostic core — the Node mirror of /server |
stitchkit/tools |
server | createMcpHandler, mountMcp, mountAgent, the OAuth provider, native tools |
stitchkit/cli |
server | createCli — the CLI transport, light (no MCP SDK / ai) |
stitchkit/observability |
server | the audit layer — createAuditHook, trace context, sanitisation |
stitchkit/react |
browser | createCursorQuery, createCacheBridge |
Rule of thumb: browser code imports stitchkit and stitchkit/react; server
code adds stitchkit/server (or stitchkit/node on Node) and stitchkit/tools.
The full export list of each is in the API reference.
Project layout
A contract is shared by both sides, so it lives in its own folder:
src/
├── shared/contracts.ts the contract — imported by server and client
├── server/index.ts implement() + createServer()
└── client/api.ts createClient()
A first app
1. Define the contract
// src/shared/contracts.ts
import { defineContract } from 'stitchkit'
import { z } from 'zod'
const Note = z.object({ id: z.string(), text: z.string() })
export const notes = defineContract({ prefix: 'notes' }, {
list: { method: 'GET', path: '/', desc: 'List notes', output: z.array(Note) },
create: { method: 'POST', path: '/', desc: 'Create note', input: z.object({ text: z.string() }), output: Note },
get: { method: 'GET', path: '/:id', desc: 'Get a note', params: z.object({ id: z.string() }), output: Note },
})
2. Implement and serve
// src/server/index.ts
import { implement, createServer } from 'stitchkit/server'
import { notes } from '../shared/contracts'
const service = implement(notes, {
list: () => db.list(),
create: (ctx) => db.create(ctx.input.text), // ctx.input is typed
get: (ctx) => db.get(ctx.params.id), // ctx.params is typed
})
createServer({ services: [service], port: 3000 })
3. Call it, typed
// src/client/api.ts
import { createClient, createHttpClient } from 'stitchkit'
import { notes } from '../shared/contracts'
export const api = createClient(notes, createHttpClient({ baseUrl: '/api' }))
await api.list() // GET /notes → Note[]
await api.create({ text: 'hi' }) // POST /notes → Note
await api.get({ id: '1' }) // GET /notes/1 → Note
Run the server with bun run src/server/index.ts. The contract is the single
source of truth — change it and both the handler and the client are re-typed at
once.
On Node
createServer is Bun’s Bun.serve. On Node ≥ 22, swap it for serveNode
(from stitchkit/node, built on srvx) — the contract, implement and the
client are identical:
import { serveNode } from 'stitchkit/node'
serveNode({ services: [service], port: 3000 })
Add @types/bun as a dev dependency on Node (an optional peer — it types the
shared stitchkit/server surface). See deployment.
Dependencies
ky is the only bundled runtime dependency. Everything else is an optional
peer — your app installs only what the features it uses need, and owns the
version (one shared instance, no dual-version skew). This matrix is the install
map — feature → packages:
| Feature you use | Install |
|---|---|
| anything (validation) | zod |
createServer (Bun) |
— (uses Bun.serve) |
serveNode (Node ≥ 22) |
srvx (+ @types/bun dev) |
MCP tools (stitchkit/tools) |
@modelcontextprotocol/sdk |
| MCP Apps UI widgets | @modelcontextprotocol/ext-apps |
agent tools (stitchkit/tools) |
ai |
React data layer (stitchkit/react) |
@tanstack/react-query react-query-kit |
| Socket.IO server on Bun | socket.io @socket.io/bun-engine |
| Socket.IO server on Node | socket.io |
| Socket.IO client | socket.io-client |
bun add socket.io @socket.io/bun-engine # e.g. the Socket.IO server on Bun
If an optional peer is missing, the feature that needs it fails with an
actionable error naming the package and the install command — not a bare
Cannot find module.
Next
- Contracts — every endpoint field, in depth.
- HTTP server —
createServer, hooks, raw routes, primitives. - Typed client — the client, React data layer, SSE.
- MCP & agents — contracts as AI tools.
- Realtime — Socket.IO and the cache bridge.
- Auth & errors — scopes, auth hooks, the error model.
- Testing & deployment.
- API reference — every export, by entrypoint.
A complete runnable app is in packages/starter.