dendra
DocsnpmGitHub
Next.js & Web-standard runtimesZero-dependency coreMCP both directions

dendraOne procedure.
Every surface.

dendra is a typed RPC/REST framework for Next.js and TypeScript where every procedure is also an LLM tool and an MCP server. Define it once — get a typed client, REST, OpenAPI, streaming, and tools an agent can call. No codegen, no decorators.

$ npm create dendra-app@latest

One definition, many surfaces

A typed client, REST + OpenAPI 3.1, streaming, an in-process caller, an LLM tool, and an MCP server — all projected from a single procedure. They can’t drift.

No codegen, no decorators

The client type comes from typeof appRouter — pure inference. Nothing to generate, nothing to run, nothing to keep in sync.

AI is a byproduct

Mark a procedure .ai() and it becomes a tool the model can call — with the same input validation and the same request context as your HTTP calls.

MCP, both directions

Expose your procedures as an MCP server, and consume external MCP servers — merged into one toolset your agent (or Claude) can use.

Zero-dependency core

The core ships with no runtime dependencies. runAgent, useAssistant, and persistent memory are opt-in subpaths you add only when you want them.

Context, managed

Persistent recall and lazy tool loading (search_tools) keep a model’s window lean even when you connect big MCP connectors.

one definition, many surfaces

Your router is one object of procedures. dendra projects it into every surface — they can’t drift, because they all derive from the same spec.

dendra architecture: a router of procedures projects into a typed client, HTTP adapters (Next/Fetch/Node), REST + OpenAPI, an in-process caller, and a tool layer that becomes an MCP server and consumes external MCP

define → serve → call

router.ts
// server/router.ts — define once
import { initDendra, s, type RouterRecord } from 'dendra'
const { route } = initDendra<Ctx>()

export const appRouter = {
  notes: {
    list: route.get('/notes')
      .handler(({ ctx }) => ctx.db.notes.all()),

    create: route.post('/notes')
      .input(s.object({ title: s.string(), body: s.string() }))
      .ai({ description: 'Create a note' })   // ← also an LLM / MCP tool
      .handler(({ input, ctx }) => ctx.db.notes.insert(input)),
  },
} satisfies RouterRecord
export type AppRouter = typeof appRouter

// lib/api.ts — typed client, inferred, no codegen
const api = createClient<AppRouter>('', { prefix: '/rpc/' })
await api.notes.create({ title, body })   // fully typed

how dendra compares

A typed-API framework and the AI/MCP world in one layer — instead of assembling the pieces yourself.

tRPCMCP SDKdendra
Typed client, no codegen✓—✓
REST + OpenAPI 3.1——✓
Your API is also LLM tools—by hand✓
MCP server (expose procedures)—✓✓
Consume external MCP——✓
Context budget (memory + lazy tools)——✓