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, and generated routes. Vibe code it in minutes, on a fraction of the tokens.

NestJSTypeORMPrismaMikroORMMongooseGraphQL

Works with your stack

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 your agent moves just as fast.

/plugin marketplace add kavo-labs/kavo
/plugin install kavo-skills@kavo-marketplace

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 DTOsOptional 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 — no second registry, 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 — all driven by the query string, no extra code.

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
  ?fields=id,title,status
  &fields[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" },
    { "id": 41, "title": "Kindred", "status": "published" }
  ],
  "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
}

Customize it to fit your needs

Every setting is optional at every scope — start with the defaults and override only where your app needs to differ.

GlobalEntityOperationPer-call

No config at all still works — built-in defaults cover every setting until you decide to change one.

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, DTOs, errors.

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