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.