Skip to main content
ClaudeWave

The authorization layer for AI agents. Give your LLM agent access to your database. Control exactly what it can see and do.

SubagentsRegistry oficial3 estrellas0 forksTypeScriptMITActualizado today
Install as a Claude Code subagent
Method: Clone
Terminal
git clone https://github.com/valv-dev/valv && cp valv/*.md ~/.claude/agents/
1. Clone the repository and copy the agent .md definitions into ~/.claude/agents (or .claude/agents inside a project).
2. Start a new Claude Code session to load the agents.
3. Delegate work to them with the Task/Agent tool or by name.
Casos de uso

Resumen de Subagents

# valv

**Let agents query your database. Just not all of it.**

[![npm](https://img.shields.io/npm/v/@valv/core?label=%40valv%2Fcore)](https://www.npmjs.com/package/@valv/core) [![npm](https://img.shields.io/npm/v/@valv/clickhouse?label=%40valv%2Fclickhouse)](https://www.npmjs.com/package/@valv/clickhouse) [![npm](https://img.shields.io/npm/v/@valv/prisma?label=%40valv%2Fprisma)](https://www.npmjs.com/package/@valv/prisma) [![npm](https://img.shields.io/npm/v/@valv/mcp?label=%40valv%2Fmcp)](https://www.npmjs.com/package/@valv/mcp) [![license](https://img.shields.io/npm/l/@valv/core)](./LICENSE)

valv gives an agent structured tools to **read** your database — and, opt-in, to **write** to it. The model emits a **structured query** (or insert/update/delete) — never SQL — and valv validates it against your schema, scopes it to the current user with policies you write in code, compiles it to your database's SQL, and runs it.

The model's query is treated as fully untrusted. It can't read a column you hid, a row the user isn't allowed to see, call a function you didn't allow, write a column you didn't permit, or escape its tenant on a write — not because the prompt asks nicely, but because the query is rebuilt and re-checked on the server before a single byte of SQL is generated.

```ts
const valv = await createValv(client, { schema: "introspect", defaultPolicy: "deny-all" })

valv.policy("orders", (ctx) => ({
  read:   { tenant_id: ctx.tenant.id },   // every read is scoped to this tenant
  fields: { deny: ["internal_notes"] },   // this column never reaches the model
}))

const tools = await valv.tools.aisdk(ctx)  // hand to your agent — it queries safely
```

---

## Two ways to use it

- **In your app.** Configure valv in code, write policies against your request context, and hand the tools to your agent — Vercel AI SDK, Anthropic, OpenAI, or Gemini. Or expose those same tools over MCP with [`@valv/mcp-sdk`](packages/mcp-sdk), scoped per request.
- **With a coding agent.** Point [`@valv/mcp`](packages/mcp) at a database and a tool like Claude Code queries it safely — no code required.

---

## Quick start

Install an adapter for your database (it pulls in `@valv/core`):

```bash
npm i @valv/clickhouse @clickhouse/client     # ClickHouse
# or
npm i @valv/prisma @prisma/client             # Postgres / MySQL / SQLite
```

Wire it up — connect, write a policy, hand the tools to an agent:

```ts
import { createValv } from "@valv/clickhouse"
import { generateText, stepCountIs } from "ai"

// 1. Connect — introspect the live schema (or pass a hand-defined one).
const valv = await createValv(client, { schema: "introspect", defaultPolicy: "deny-all" })

// 2. Policy — what this caller may read, resolved from your context.
valv.policy("orders", (ctx) => ({ read: { tenant_id: ctx.tenant.id } }))

// 3. Tools — bound to the request's context, formatted for your provider.
const ctx = { user: { id: "u1", role: "analyst" }, tenant: { id: "acme" } }
const { text } = await generateText({
  model,
  system: await valv.instructions(ctx),  // how to drive the tools + the caller's resources
  tools: await valv.tools.aisdk(ctx),
  stopWhen: stepCountIs(6),
  prompt: "What's our revenue per order status this month?",
})
```

The agent gets four tools — `list_resources`, `search_resources`, `describe_resource`, and `query` — discovers your schema, and runs a query. valv scopes it to `acme`, compiles it to ClickHouse SQL, runs it, and hands back rows.

---

## What the agent can express

One `query` tool covers the whole read surface. The grammar is **Prisma-idiomatic** — a shape models already know cold — and desugars server-side into a checked query:

```jsonc
{
  "from": "orders",
  "select": {
    "status": true,                       // a plain column
    "orders": { "count": true },          // count(*) — the key names the output
    "revenue": { "sum": "total" }         // an aggregate
  },
  "where": { "created_at": { "gte": "2026-06-01" } },
  "groupBy": ["status"],
  "orderBy": { "revenue": "desc" },
  "take": 10
}
```

That's enough for real analytics — **filters** (`{ field: value }` equality, operator objects like `{ gte, lt, in, contains }`, and `AND`/`OR`/`NOT` trees), **aggregates**, **time-series** (bucket with a function and group by the alias), **top-N** (order by an aggregate), and **conditional aggregation** (`countIf`, `sumIf`). ClickHouse adds dialect functions like `quantileTiming` and `toStartOfInterval`; every function is type-checked and its literals parameterized.

### Joins

To read a related resource, reference its column with a **dotted path** from the root. The model can only follow relations declared in your schema; valv derives the joins, picks the keys, and **composes the policy of every table it touches** — each joined table is scoped by its own policy and field allowlist, so a join can never reach a hidden column or another tenant's rows.

```jsonc
{
  "from": "orders",
  "select": {
    "customer_name": { "col": "customer.name" },        // one hop: orders → customer
    "region": { "col": "customer.region.name" },        // multi-hop: → customer → region
    "revenue": { "sum": "total" }
  },
  "groupBy": ["customer.name"]
}
```

`belongsTo` and `hasMany` relations are supported; join depth, table count, and fan-out are capped, and every query runs under a statement timeout. Relations are auto-introspected on Prisma and declared in the schema on ClickHouse.

---

## Usage

### Connect

`createValv` is async — it loads the schema on construction, so the instance is ready to use. Call it **once** at startup.

```ts
// ClickHouse — introspect, or hand-define a schema
const valv = await createValv(clickhouseClient, { schema: "introspect", database: "analytics" })

// Prisma (Postgres / MySQL / SQLite / Cockroach) — schema comes from your .prisma file
import { createValv } from "@valv/prisma"
const valv = await createValv(prismaClient)
```

`defaultPolicy: "deny-all"` (recommended) makes a resource invisible until you write a policy for it.

### Policy

A policy is a function of your context. It decides what the caller may read, per resource:

```ts
valv.policy("orders", (ctx) => ({
  read:   { tenant_id: ctx.tenant.id },   // row filter — AND-injected into every query
  fields: { deny: ["internal_notes"] },   // hide columns
}))

valv.policy("users", (ctx) => ({
  read:   { tenant_id: ctx.tenant.id },
  fields: ctx.user.role === "support" ? { deny: ["email"] } : undefined,
}))
```

| `read` value | Meaning |
|---|---|
| `true` / `false` | allow / deny outright |
| `{ field: value }` | a row filter, AND-ed into the query server-side |

The row filter can't be widened or overridden by the model — it's injected *after* the model's query is parsed, into the `WHERE` clause, before SQL is emitted. Fields are denied two ways: `fields.deny` (a blacklist) or `fields.allow` (a whitelist). Denied and unknown columns fail with the same message, so the model can't probe for hidden columns. Use `"*"` as the resource name for a default policy.

The same policy object carries the write axes — `create`, `update`, `delete` (and `write` as a shorthand for create+update) — which default to denied. See [Writes](#writes).

### Tools

`valv.tools.<format>(ctx, options)` returns provider-ready tools, bound to that context. Discovery is **policy-filtered** — `list`/`search`/`describe` only surface what the caller may read.

```ts
valv.tools.anthropic(ctx)                              // Anthropic Messages API
valv.tools.openai(ctx)                                 // OpenAI / compatible
valv.tools.gemini(ctx)                                 // Google Gemini
await valv.tools.aisdk(ctx)                            // Vercel AI SDK (async; needs `ai`)
valv.tools.neutral(ctx)                                // raw, framework-agnostic

valv.tools.anthropic(ctx, { list: false, search: false })  // drop discovery tools individually
```

The `aisdk` format returns self-executing tools (the SDK runs them). The provider formats (`anthropic`/`openai`/`gemini`) return tool **definitions** for the API request; you dispatch a tool call with `runTool`:

```ts
const result = await valv.runTool(call.name, call.input, ctx)
```

The discovery tools (`list`/`search`/`describe`) are on by default; the write tools (`create`/`update`/`delete`) are **off** by default — turn them on per call:

```ts
valv.tools.aisdk(ctx, { search: false, create: true, update: true })
```

### System prompt

`await valv.instructions(ctx)` returns a drop-in system-prompt block: how to drive the tools (discover → describe → query, filters are scoped server-side) plus the resources **this caller** may read — so the model can skip the opening `list_resources` round-trip. Put it in your `system` prompt alongside the tools. The static text is also exported as `AGENT_INSTRUCTIONS` if you'd rather compose the resource list yourself.

```ts
const system = await valv.instructions(ctx)
```

```
You answer questions by querying a set of resources through the provided tools. Access is
enforced server-side: every query is scoped to what the current caller may read, so you never
need to add tenant/owner/permission filters yourself — a query returns only permitted rows.

Workflow:
1. Find the resource: use list_resources / search_resources; you often already have the list below.
2. Before querying an unfamiliar resource, call describe_resource to get its exact column names,
   types, and relations. Don't guess column names.
3. Query with the `query` tool. Do the work in the query — filter with `where`, aggregate with
   functions, `groupBy`, `orderBy`, `take` — rather than pulling raw rows and reducing yourself.
4. The grammar is Prisma-like. `select` is an object keyed by output name: `true` for a plain
   column, { "col": "path" } to rename or reach a joined column, { fn: args } to aggregate (e.g.
   { "revenue": { "sum": "amount" } }). `where` uses { field: value } for equality and

Lo que la gente pregunta sobre valv

¿Qué es valv-dev/valv?

+

valv-dev/valv es subagents para el ecosistema de Claude AI. The authorization layer for AI agents. Give your LLM agent access to your database. Control exactly what it can see and do. Tiene 3 estrellas en GitHub y se actualizó por última vez today.

¿Cómo se instala valv?

+

Puedes instalar valv clonando el repositorio (https://github.com/valv-dev/valv) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar valv-dev/valv?

+

valv-dev/valv aún no ha sido auditado por nuestro agente de seguridad. Revisa el repositorio original en GitHub antes de usarlo en producción.

¿Quién mantiene valv-dev/valv?

+

valv-dev/valv es mantenido por valv-dev. La última actividad registrada en GitHub es de today, con 0 issues abiertos.

¿Hay alternativas a valv?

+

Sí. En ClaudeWave puedes explorar subagents similares en /categories/agents, ordenados por popularidad o actividad reciente.

Despliega valv en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

Featured on ClaudeWave: valv-dev/valv
[![Featured on ClaudeWave](https://claudewave.com/api/badge/valv-dev-valv)](https://claudewave.com/repo/valv-dev-valv)
<a href="https://claudewave.com/repo/valv-dev-valv"><img src="https://claudewave.com/api/badge/valv-dev-valv" alt="Featured on ClaudeWave: valv-dev/valv" width="320" height="64" /></a>

Más Subagents

Alternativas a valv