MCP server

@valv/mcp is a zero-config Model Context Protocol (MCP) server. Point it at a connection string and it serves your database to a coding agent like Claude Code, with the same validation and policy enforcement as the library. No code required.

By default it serves the four read tools and is read-only across all tables. You narrow access with environment variables or a policy file.

Guided setup

The fastest way in probes your database, lets you choose access, and writes the config for you:

npx @valv/mcp init

It prompts for a connection string, confirms the dialect, connects and shows your tables, offers read-only, pick-tables, or a policy-file stub, and writes the .mcp.json entry.

Manual setup

Add the server to your .mcp.json (or your Claude Desktop config) and point it at a connection string:

{
  "mcpServers": {
    "db": {
      "command": "npx",
      "args": ["-y", "@valv/mcp"],
      "env": { "DATABASE_URL": "postgresql://user:pass@localhost:5432/app" }
    }
  }
}

For ClickHouse, use the HTTP URL and a database name:

"env": { "DATABASE_URL": "http://localhost:8123", "VALV_DATABASE": "analytics" }

Configuration

Configure the server with environment variables:

Variable Description
DATABASE_URL Connection string (required; or pass as the first CLI argument).
VALV_PROVIDER postgresql, mysql, sqlite, or clickhouse. Inferred when omitted.
VALV_DATABASE Database name (ClickHouse).
VALV_TABLES Comma-separated allowlist; only these tables are exposed.
VALV_EXCLUDE Comma-separated denylist, applied after the allowlist.
VALV_POLICY_FILE Path to a policy module for tenant scoping or hidden fields.
VALV_CONTEXT JSON context the policy reads, such as {"tenant":{"id":"acme"}}.
VALV_HTTP_PORT Serve over Streamable HTTP on this port instead of stdio.

Policy file

Without a policy file, access is read-only across all tables. To restrict which tables and columns the agent sees, point VALV_POLICY_FILE at a module that receives the configured valv instance:

// valv.policy.cjs
module.exports = (valv) => {
  valv.policy("orders", () => ({
    read: true, // allow reads (or { column: value } to filter rows)
    fields: { deny: ["internal_notes"] }, // hide columns from the agent
  }))
  // Tables without a policy are denied.
}

For per-request row scoping by real user or tenant identity, use MCP in your app, where your code supplies identity per request.

The valv skill

The /valv skill adds a slash command to a coding agent that queries your database through the MCP server and turns the result into a chart, written as a self-contained HTML file. Install it once:

npx skills add https://github.com/valv-dev --skill valv

With the MCP server configured, you can then ask the agent something like /valv revenue by plan this month. It runs a query through valv, scoped by your policy, and renders the rows as a chart.

The skill also learns your database as you use it. The first time it describes a table, works out the dialect’s time-bucket function, or maps a term like “revenue” to sum(total) on orders, it records that in .valv/notes.md in your working directory — so later sessions skip the rediscovery and start warm. The notes hold schema and semantics only, never query results, and the file is plain markdown you can read, edit, or pre-seed yourself.

Next steps

  • MCP in your app: serve MCP from your own server with per-request context.
  • Policies: the policy rules a policy file can use.