Open source

stitchkit

Contract-first backend framework for Bun and Node

Define your API once. Get an HTTP API, MCP tools for Claude and Cursor, AI-agent tools, a CLI and a fully-typed client — all from a single contract that cannot drift out of sync.

Install
bun add stitchkit
  • TypeScript
  • Bun · Node ≥ 22
  • MIT
  • ~8 500 LOC
  • Zero HTTP-framework deps
Version
v0.24.0
Commits
36
Last commit
28 Jul 2026
License
MIT

One contract, five surfaces

Below is a contract. Pick a surface and see what it turns into — the declaration on top never changes.

stitchkit mascot connected to HTTP, MCP and AGENT badges
shared/contracts.ts
export const users = defineContract({ prefix: 'users' }, {
  list:   { method: 'GET',  path: '/',    output: z.array(UserSchema) },
  create: { method: 'POST', path: '/',    input: CreateUserSchema, output: UserSchema },
})

Plain REST routes on Bun.serve() or srvx — no Hono, no Express.

server/index.ts
const service = implement(users, {
  list:   (ctx) => db.users.findMany(),
  create: (ctx) => db.users.create({ name: ctx.input.name }),
})

createServer({ services: [service], port: 3000 })

// GET  /users   → User[]
// POST /users   → User

The problem it solves

A modern backend exposes the same operations several ways — an HTTP API for the app, MCP tools for assistants, tool definitions for agents, a CLI for scripts. Written by hand, that is one surface described many times: many places to drift, many places to keep in sync. stitchkit collapses them into a single contract.

One contract file expands into an HTTP API, MCP tools, agent tools and a typed client
Define once — every surface follows from the same declaration.
Without stitchkit the same API is hand-written for every surface; with stitchkit one contract drives them all
The same operations, described once instead of three times.
  • 01HTTP APIPlain REST routes on Bun.serve() or srvx — no Hono, no Express.
  • 02MCP toolsExposed to Claude, Cursor and any Model Context Protocol client.
  • 03AI-agent toolsTool definitions for the Vercel AI SDK, ready for generateText.
  • 04CLIEvery endpoint callable from scripts and Skills.
  • 05Typed clientInferred from the contract — no codegen, no build step.

Working API in four steps

From nothing to a typed endpoint. MCP tools, agent tools and the CLI come from the same contract afterwards — nothing to rewrite.

  1. 1

    Install

    One package. Zod comes as a peer so your app and stitchkit share one instance.

    terminal
    bun add stitchkit zod
  2. 2

    Describe an endpoint

    Plain data — no decorators, no code generation, no build step.

    shared/contracts.ts
    import { defineContract } from 'stitchkit'
    import { z } from 'zod'
    
    export const users = defineContract({ prefix: 'users' }, {
      list: { method: 'GET', path: '/', output: z.array(UserSchema) },
    })
  3. 3

    Implement and serve

    Handlers are typed from the contract; the server has no HTTP framework under it.

    server/index.ts
    import { implement, createServer } from 'stitchkit/server'
    
    const service = implement(users, { list: () => db.users.findMany() })
    createServer({ services: [service], port: 3000 })
  4. 4

    Call it

    The client is inferred from the same contract — rename a field and the call site breaks at compile time.

    client/api.ts
    import { createClient, createHttpClient } from 'stitchkit'
    
    const api = createClient(users, createHttpClient({ baseUrl: '/api' }))
    await api.list()   // → User[]

Full walkthrough — getting started guide.

How it compares

CapabilitystitchkittRPCts-restHono / Elysia
Contract is plain data — no decorators, no codegen
Inferred typed client
Plain HTTP REST routes
MCP tools from the same contract
AI-agent tools from the same contract
No HTTP-framework dependency

Batteries that ship with it

Thin over what you already use. stitchkit owns the contract and the transport — it does not reinvent your WebSocket layer or your data-fetching hooks.

  • Auth & scopes

    createAuthHook() — scope-aware auth driven by the contract.

  • SSE streaming

    streamSSE() / parseSSE() — async generator ↔ server-sent events.

  • WebSocket

    Socket.IO wrappers, typed both ways. Not a competing engine.

  • Cache bridge

    Sync socket events into the TanStack Query cache.

  • Events

    createEventBus<EventMap>() — typed in-process pub/sub.

  • Multipart

    parseMultipart() — file upload with field validation.

  • Rate limiting

    createRateLimiter() — token bucket, per key.

  • Errors

    AppError, notFound(), badRequest(), unauthorized().

Requirements

Runtime
Bun ≥ 1.2First-class target — the server runs on Bun.serve().
Runtime (alternative)
Node ≥ 22Supported via the stitchkit/node entrypoint, built on srvx.
Language
TypeScriptTypes are inferred from contracts; no code generation step.
Required peer
zodSchemas are the source of truth. A peer so your app and stitchkit share one instance.
Optional peers
@modelcontextprotocol/sdk · ai · @tanstack/react-query · socket.ioEach pulled in only by the entrypoint that needs it.
Bundled runtime dep
ky~13 KB fetch-based HTTP client behind the typed client — the only thing installed for you.
Status
Pre-1.0Core is stable and tested; the public API may shift between minor versions.
License
MIT

When to reach for it — and when not to

Good fit

  • A backend that serves both an app and an AI assistant
  • Exposing existing endpoints as MCP tools without rewriting them
  • Small teams where the API and the client are maintained by the same person
  • Projects where type safety across the wire matters more than framework features

Look elsewhere

You need a full-stack framework
stitchkit owns the contract and the transport — not routing, not rendering, not your database. Pairing it with Next or SvelteKit is the intended setup, replacing them is not.
The API has exactly one consumer, forever
The whole point is one declaration feeding several surfaces. If there will only ever be an HTTP API and nothing else, a plain router costs less.
You are on a runtime older than Node 22
Bun is first-class, Node 22+ works via stitchkit/node. Older runtimes are out of scope.
You want a stable 1.0 API today
Pre-1.0. The core is stable and covered by tests, but the public API can still shift between minor versions.
You need GraphQL
The contract describes HTTP operations and tool calls. GraphQL is a different shape and is not on the roadmap.

Frequently asked

Does stitchkit work with Node, or is it Bun only?

Both. Bun is the first-class target and the server runs on Bun.serve(). Node 22 and newer is supported through the stitchkit/node entrypoint, which uses srvx instead. The contract, the client and the tool layer are identical on either runtime.

How is it different from tRPC?

tRPC gives you RPC-style calls typed from a router, and its contract is the router type itself. In stitchkit the contract is plain data, so the same declaration also produces MCP tools for assistants, tool definitions for AI agents and a CLI — none of which tRPC covers. stitchkit also emits ordinary REST routes rather than an RPC endpoint.

Do I have to give up my HTTP framework?

There is nothing to give up — stitchkit has no HTTP framework under it. It calls Bun.serve() or srvx directly, so it does not compete with Hono, Elysia or Express; it replaces the layer where you would otherwise hand-write routes.

Is there a code generation or build step?

No. Contracts are plain objects and every type is inferred from them at compile time. There is no schema compiler, no generated client, and nothing to re-run after editing an endpoint.

What exactly are MCP tools here?

Model Context Protocol tools — the way assistants like Claude and Cursor call into external systems. Mark an endpoint with expose: ['MCP'] and createMcpHandler turns it into a tool, with the same validation and auth as the HTTP route. Your application never imports the MCP SDK itself.

Can I use it with React Query?

Yes, and that is the intended setup. stitchkit does not ship its own hook engine — you pair the typed client with react-query-kit. For cursor-paginated lists it provides createCursorQuery, which injects the cursor and wires up getNextPageParam for you.

Is it production ready?

It is pre-1.0: the core is stable and covered by tests, and it is used in production in the author's own projects. Until 1.0 the public API may still change between minor versions, so pin your version if that matters to you.

Start with one contract

Install it, describe a couple of endpoints, and every surface follows from there. Pre-1.0: the core is stable and covered by tests, the public API may still shift between minor versions.

Install
bun add stitchkit