Why dryrun?
AI coding assistants write SQL blind. They don't know your schema, your
indexes, or your constraints, so they generate migrations that take
ACCESS EXCLUSIVE locks on busy tables, add varchar(255) columns when
you use text, or create indexes that already exist.
Most PostgreSQL MCP servers answer this by asking for a database connection, and the administrative tools push you toward a superuser role. That trades one problem for a worse one. We have already seen where it leads: production databases wiped by agents, and SQL injection in MCP servers that were supposed to be read-only.
The model does not need to query your database. It needs to understand it. That knowledge is structural: it changes when you deploy a migration, not between queries. So capture it once, put it in a file, and hand the file around.
Capture once, work offline
dryrun is a CLI and an MCP server, deliberately separate. The CLI is
the only part that ever touches PostgreSQL. It introspects the catalog
and writes a snapshot. Everything after that reads the snapshot.
One person with database access captures. Everyone else, humans and agents alike, gets full schema intelligence without a connection string. CI runners included.
# The person with credentials
dryrun init --db "$DATABASE_URL"
dryrun snapshot take
dryrun snapshot push --to-path ./snapshots --all
# Everyone else
dryrun snapshot pull --from-path ./snapshots --all
dryrun lint
Snapshots are content-addressed, so pushing the same capture twice is a no-op and a shared history deduplicates. A shared directory works, as does a dedicated git repo. So does any OCI registry, and that is usually the better choice: GitHub Container Registry, Google Artifact Registry, Amazon ECR, or a self-hosted one already handles authentication, retention, and access control, so there is no server to run.
dryrun remote add ghcr --ref ghcr.io/myorg/dryrun --default
dryrun snapshot take --push
Consumers pull only the latest capture by default, so a cold CI runner stays cheap no matter how much history the registry holds.
One capture is a fact, a series is a trend
The snapshot carries planner statistics and activity counters, not just DDL. Once you have more than one, you can answer offline the questions that usually need a monitoring stack.
snapshot diff compares any two captures across schema, planner
statistics, or activity, scoped to a schema or a single table. detect
reads the captured counters for stale statistics, indexes that no longer
take scans, tables and indexes accumulating bloat, and per-node activity
anomalies. When you do want a live connection, drift tells you whether
the database still matches the snapshot you have been working against.
A single \d+ dump gives you none of this.
Use Cases
AI-assisted database development
Install dryrun as an MCP server and your assistant gains schema awareness. It stops hallucinating column names, suggests indexes, and reads the same statistics you do.
claude mcp add dryrun -- dryrun mcp-serve
With no install at all, point the client at npx instead:
claude mcp add dryrun -- npx -y @boringsql/dryrun mcp-serve
The server auto-discovers the project's snapshot, so there is nothing further to configure.
Reviewing a schema you inherited
lint gives you the convention and structural problems in one pass:
tables without primary keys, foreign keys with no covering index,
timestamp where you meant timestamptz, serial where you meant
identity, gaps in range partitions. detect adds the statistical layer:
which indexes are not being used, what is bloated, where statistics have
gone stale.
Pre-deploy migration review
Run the DDL through the lock and rewrite check before it reaches a
review, and again in CI. It flags the operations that behave differently
than they read: a column type change that rewrites the table, a
constraint added without NOT VALID, an index built without
CONCURRENTLY, a SET NOT NULL that scans every row. Each one comes
back with the lock it takes and a safer way to do it.
Schema governance across teams
Enforce naming conventions, require timestamps, ban varchar in favor
of text, and flag tables without primary keys. One dryrun.toml keeps
the whole team aligned, and the rules run without a database, so they
run in CI.