← stitchkitDocumentation

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 via stitchkit/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

A complete runnable app is in packages/starter.