Turn any Postgres database into MCP tools for LLM agents — plus a PostgREST-compatible REST API and an OpenAPI 3.0 document — from one Rust engine.
Status:
v0.1.0· early development · Postgres only · SQLite and MCP-over-SSE are on the roadmap. The Postgres REST / MCP / OpenAPI path works today, and the build-from-source quick start below is real and runnable.
Point pgvis at a database. It introspects the schema once at startup and then serves that schema three ways from a single pipeline — one query parser, one planner, one SQL builder:
- MCP — every table and function becomes a typed Model Context Protocol tool an LLM agent can call. No glue code, no hand-written tool schemas.
- REST — a PostgREST-compatible HTTP API: the same query DSL, the same
Prefersemantics, the samePGRST*error codes, so existing PostgREST clients work unchanged. - OpenAPI — an OpenAPI 3.0 document generated from the same introspected schema.
Because all three surfaces lower into the same ApiRequest → plan → SQL
pipeline, their behavior never diverges. The core engine
(pgvis-core) does no I/O — database drivers
implement a single Backend trait — which is what makes pgvis embeddable as a
library and backend-agnostic by design (SQLite is planned with no core
rewrite).
How it differs from PostgREST: an I/O-free backend-agnostic core, a
first-class MCP surface for LLM agents, an embeddable Rust library (not just a
server), and schema-in-URL routing (/api/{schema}/{table}) with a
PostgREST-compatible flat mode for drop-in replacement.
Prerequisites: a stable Rust toolchain (Rust edition 2024 — see rust-toolchain.toml) and a running PostgreSQL instance.
# Build the CLI (the `mcp` feature is on by default)
cargo build --release --bin pgvis
# Point it at your database and serve
export PGVIS_DSN="postgres://user@localhost/mydb"
./target/release/pgvis serve --bind 0.0.0.0:3000
# First request — list rows from a table in the `public` schema
curl "http://localhost:3000/api/public/your_table"Nix users can nix build or drop into a dev shell with nix develop (see
flake.nix; the dev shell pre-sets PGVIS_DSN and RUST_LOG).
The CLI is the source of truth for flags and subcommands (crates/pgvis-server/src/main.rs):
pgvis --dsn <DSN> [--config <FILE>] <COMMAND>
serve Start the HTTP server (REST + optional MCP over Streamable HTTP)
--bind <ADDR> default 0.0.0.0:3000 (env PGVIS_BIND)
--schema <NAME> repeatable / comma-sep (env PGVIS_SCHEMAS)
--mcp-http also serve MCP at /mcp
mcp Run an MCP server over stdio (for Claude Desktop / agents)
openapi Print the OpenAPI 3.0 document and exit
inspect Dump the introspected schema cache as JSON
env: PGVIS_DSN (required), PGVIS_CONFIG
# stdio transport, e.g. for a Claude Desktop MCP server entry
pgvis --dsn "postgres://user@localhost/mydb" mcp
# or expose MCP over Streamable HTTP at /mcp alongside the REST API
pgvis --dsn "postgres://user@localhost/mydb" serve --mcp-httpTools are generated per table/function — list_<table>, create_<table>,
update_<table>, delete_<table>, call_<function> — and discovery
resources (pgvis://schemas, pgvis://{schema}/schema) describe the
available surface. Tool calls run the same plan → SQL path as REST. See
crates/pgvis-mcp/src/tools.rs.
Routes default to /{prefix}/{schema}/{table} with prefix = "api" (a flat
PostgREST-compatible mode is available via routing config). Examples, all
exercised by the integration tests in
crates/pgvis-server/tests/query.rs:
# Select specific columns
curl "http://localhost:3000/api/public/items?select=id,name,price"
# Filters: equality, comparison, pattern, null, set membership, negation
curl "http://localhost:3000/api/public/items?name=eq.Widget"
curl "http://localhost:3000/api/public/items?price=gte.99.99"
curl "http://localhost:3000/api/public/items?name=ilike.*widget*"
curl "http://localhost:3000/api/public/items?description=is.null"
curl "http://localhost:3000/api/public/items?category=in.(gadgets,toys)"
curl "http://localhost:3000/api/public/items?name=neq.Widget"
# Ordering and pagination
curl "http://localhost:3000/api/public/items?order=category.asc,price.desc"
curl "http://localhost:3000/api/public/items?order=id.asc&limit=3&offset=3"
# Combined
curl "http://localhost:3000/api/public/items?select=name,price&category=eq.gadgets&order=price.desc"
# Call a function (RPC)
curl "http://localhost:3000/api/public/rpc/your_function"Writes use POST / PATCH / DELETE on the same routes; the Prefer header
controls return representation, count strategy, and transaction behavior.
pgvis --dsn "postgres://user@localhost/mydb" openapi # prints the 3.0 specThe spec is also served at the API root for clients sending
Accept: application/openapi+json.
pgvis-lib is the single authoritative way to construct the stack — the CLI
uses it too (crates/pgvis-lib/src/lib.rs):
use pgvis_lib::Builder;
// REST + MCP-over-HTTP router, ready for `axum::serve`
let router = Builder::new("postgres://localhost/mydb")
.schemas(vec!["public"])
.with_mcp_http()
.build()
.await?;
// Or a stdio MCP server
let mcp = Builder::new("postgres://localhost/mydb")
.schemas(vec!["public"])
.build_mcp_server()
.await?;
pgvis_lib::pgvis_mcp::serve_stdio(mcp).await?;Configuration is the Config struct in
crates/pgvis-core/src/config.rs (PostgREST
config keys map directly — see the table in that file). Most-used fields:
| Field | Purpose | Default |
|---|---|---|
schemas |
Schemas exposed as routes / tools | ["public"] |
jwt_secret / jwt_algo |
JWT verification (symmetric or asymmetric) | none (anonymous) |
anon_role |
Role used for unauthenticated requests | none |
aggregates_enabled |
Allow sum()/avg()/… in select |
disabled |
max_rows |
Server-side cap on returned rows | unlimited |
routing.prefix |
URL prefix | "api" |
routing.schema_in_path |
/{prefix}/{schema}/{table} vs flat |
true |
Today the CLI uses built-in defaults plus
PGVIS_*env vars.--config/PGVIS_CONFIGis wired but the TOML layering is still stubbed (load_configreturnsConfig::default()); full file-based config lands in a later release.
- Database → LLM tools with zero glue. Every table and function becomes a typed MCP tool automatically — keep the schema as the contract.
- One engine, three surfaces. REST, OpenAPI, and MCP share the same parser, planner, and SQL builder, so behavior can't drift between them.
- PostgREST-compatible. Same query DSL,
Prefersemantics, andPGRST*error codes — existing PostgREST clients work unchanged. - Backend-agnostic, I/O-free core. A single
Backendtrait; SQLite is planned with no core rewrite (dialect capability flags drive feature gating). - A library, not just a server. Add a database API to any Rust app with
the
pgvis-libBuilder. - Safe by construction. Parameterized SQL, JWT auth, role switching / RLS, statement timeouts, and hot schema reload.
pgvis is a six-crate Rust workspace; all dependencies point inward to the I/O-free core.
| Crate | Role |
|---|---|
| pgvis-core | I/O-free engine: query parser, plan layer, SQL builder, schema cache, Backend/Dialect/Config |
| pgvis-postgres | Postgres Backend: connection pool, introspection, execution |
| pgvis-router | axum REST router + OpenAPI generator |
| pgvis-mcp | MCP tools & resources (stdio + Streamable HTTP) |
| pgvis-lib | Builder facade — the one way to assemble the stack |
| pgvis-server | The pgvis CLI binary |
The authoritative architecture reference lives in arch/:
- Overview — goals, principles, request lifecycle
- Core pipeline — parse → plan → SQL
- Backends and dialects
- Surfaces — REST, OpenAPI, MCP
- Schema cache and introspection
- Errors, configuration, preferences
- Design decisions
- Future scope and known gaps
Note: the
arch/docs predate a rename and may refer topgvis-rest/pgvis-embed; in the code these arepgvis-router/pgvis-lib.
| Milestone | Scope |
|---|---|
| 0.1 (current) | REST + OpenAPI on Postgres; MCP tools wired; Postgres execution implemented |
| 0.2 | MCP over stdio hardened |
| 0.3 | SQLite backend |
| 0.4 | MCP over SSE |
| 1.0 | Stable embed API |
Apache-2.0 OR MIT — see LICENSE.