Skip to content
Agentic-Ready

KavoTurn models into APIs

Define an entity once and get a complete REST, GraphQL, and MCP CRUD API with filtering, sorting, pagination, realtime events, and generated routes. Vibe code it in minutes, on a fraction of the tokens.

Works with your stack

Frameworks
NestJSNext.js
ORMs
TypeORMPrismaMikroORMMongoose
Protocols
GraphQLMCP

Stop writing repetitive CRUD endpoints.

Without Kavo77 lines

No pagination, filtering, sorting, or field selection.

ts
@Controller("books")
export class BooksController {
  constructor(private readonly repository: BookRepository) {}

  @Post()
  create(@Body() body: { title: string; author: string }) {
    return this.repository.create({
      data: {
        title: body.title,
        author: body.author,
      },
    });
  }

  @Get()
  findAll() {
    return this.repository.findMany({
      where: {
        deletedAt: null,
      },
      orderBy: {
        id: "desc",
      },
    });
  }

  @Get(":id")
  findOne(@Param("id", ParseIntPipe) id: number) {
    return this.repository.findOne({
      where: {
        id,
        deletedAt: null,
      },
    });
  }

  @Patch(":id")
  update(@Param("id", ParseIntPipe) id: number, @Body() body: { title?: string; author?: string }) {
    return this.repository.update({
      where: {
        id,
        deletedAt: null,
      },
      data: {
        title: body.title,
        author: body.author,
      },
    });
  }

  @Delete(":id")
  remove(@Param("id", ParseIntPipe) id: number) {
    return this.repository.delete({
      where: {
        id,
      },
      data: {
        deletedAt: new Date(),
      },
    });
  }

  @Patch(":id/restore")
  restore(@Param("id", ParseIntPipe) id: number) {
    return this.repository.restore({
      where: {
        id,
      },
      data: {
        deletedAt: null,
      },
    });
  }
}
With Kavo3 lines

Pagination, filtering, sorting, field selection, and more, all included.

ts
@Kavo(Book)
@Controller("books")
export class BooksController {}

Built for agentic development

Built with Claude Code, and shipped with skills so Claude, Codex, Antigravity, and other coding agents move just as fast.

npx skills add kavo-labs/kavo

Fewer tokens, ship faster.

Everything a CRUD API needs

One decorator, the full surface, configurable at global, entity, operation, and per-call scope.

01FilteringA full operator grammar (eq, gt, in, isNull, and more) parsed straight from the query string.
02Sorting & paginationMulti-field sort and limit/offset paging with consistent envelope fields on every list route.
03Nested includesPull related entities into the response with field-path recursion, capped for safety.
04Field selectionAsk for exactly the fields you need and leave the rest off the wire.
05Per-operation schemasOptional create, update, patch, query, item, and list shapes, derived or hand-written.
06And moreSoft delete, transactions, problem-details errors, GraphQL binding, and the rest of the surface.

Expose your API to agents with MCP

The same engine behind REST and GraphQL exposes every entity as an MCP toolset, with no second registry and no hand-written schemas.

MCP client: Archive

Every standard operation, for every @Kavo entity, unconditionally: an agent gets the same filtering, pagination, and soft-delete semantics a REST or GraphQL client does, because it calls the same engine.

The query grammar, on the wire

Filtering, sorting, pagination, and includes are all driven by the query string. No extra code to write.

Request
http
GET /books
  ?filter[status][eq]=published
  &filter[publishedAt][gte]=2020-01-01
  &sort=-publishedAt
  &include=author
  &limit=2
http
GET /books/42
  ?include=author,reviews.user
http
GET /books
  ?select=id,title,status
  &select[author]=id,name
http
GET /books
  ?sort=title
  &limit=25
  &offset=50
http
GET /books
  ?withDeleted=true
  &filter[status][eq]=archived
Response
json
{
  "items": [
    {
      "id": 42,
      "title": "The Left Hand of Darkness",
      "status": "published",
      "author": { "id": 7, "name": "Ursula K. Le Guin" }
    },
    {
      "id": 41,
      "title": "Kindred",
      "status": "published",
      "author": { "id": 3, "name": "Octavia E. Butler" }
    }
  ],
  "limit": 2,
  "offset": 0,
  "total": 128
}
json
{
  "id": 42,
  "title": "The Left Hand of Darkness",
  "author": { "id": 7, "name": "Ursula K. Le Guin" },
  "reviews": [
    {
      "id": 101,
      "rating": 5,
      "user": { "id": 3, "name": "Alex Chen" }
    }
  ]
}
json
{
  "items": [
    {
      "id": 42,
      "title": "The Left Hand of Darkness",
      "status": "published",
      "author": { "id": 7, "name": "Ursula K. Le Guin" }
    },
    { "id": 41, "title": "Kindred", "status": "published", "author": { "id": 3, "name": "Octavia E. Butler" } }
  ],
  "limit": 20,
  "offset": 0,
  "total": 128
}
json
{
  "items": [
    { "id": 63, "title": "Annihilation", "status": "published" },
    { "id": 88, "title": "Binti", "status": "published" }
  ],
  "limit": 25,
  "offset": 50,
  "total": 128
}
json
{
  "items": [
    {
      "id": 17,
      "title": "The Dispossessed",
      "status": "archived",
      "deletedAt": "2026-03-14T09:22:00.000Z"
    }
  ],
  "limit": 20,
  "offset": 0,
  "total": 1
}

Realtime, without a second system

Every create, update, patch, and delete already flows through one engine, so publishing it as an event is a config flag, not a new pipeline. SSE ships today; WebSocket, RabbitMQ, and Kafka plug into the same RealtimeTransport interface.

Where teams put it to work

Client-facing UI
Live dashboardsBadge countsPresenceScoped inventory viewsJob progress
Service-to-service
Cache invalidationSearch index syncAudit trailsAnalytics pipelines
External-facing
Partner webhooksMobile push

Not a replacement for your ORM

Kavo doesn't own your data model. It sits on top of the entity you already defined.

Your ORMEntity, migrations, and relations, unchanged.
+
KavoReads the entity's metadata, generates the CRUD surface.
=
REST + GraphQL APIFiltering, sorting, pagination, includes, schemas, errors.

Swap ORMs later and the API surface doesn't move. RepositoryAdapter is the only seam that changes.