Back to Browse

Dbt Plan MCP Server

Data & AnalyticsLow Risk10.0MCP RegistryLocal
Free

Server data from the Official MCP Registry

Predicts the DDL a dbt change will execute, before you run it. Reads files, not the warehouse.

About

Predicts the DDL a dbt change will execute, before you run it. Reads files, not the warehouse.

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (1 strong, 2 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.

7 files analyzed · 1 issue found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

Shell Command Execution

Runs commands on your machine. Be cautious — only use if you trust this plugin.

file_system

Check that this permission is expected for this type of plugin.

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-presentjay-dbt-plan": {
      "args": [
        "dbt-plan"
      ],
      "command": "uvx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

dbt-plan

Static analysis tool that warns about risky DDL changes before dbt run.

Like terraform plan for dbt, and used the same way: you run it before the thing that changes your warehouse, not only in CI afterwards.

Analyzes compiled SQL files from warehouses including Snowflake, BigQuery, Redshift, Postgres and DuckDB through one code path. SQL dialect support varies; see analysis limits.

What It Looks Like

$ dbt-plan check

dbt-plan -- 4 model(s) changed
  dialect: snowflake (default; adapter: unknown)
  baseline: unknown revision, unknown snapshot time


DESTRUCTIVE  int_order_enriched (incremental, sync_all_columns)
  ADD COLUMN  billing_method
  ADD COLUMN  shipping_city
  DROP COLUMN  billing_info
  DROP COLUMN  shipping_info
  Downstream: dim_customers, fct_daily_sales (2 model(s))
  >> BROKEN_REF  fct_daily_sales: reads dropped column(s): shipping_info

SAFE  dim_customers (table)
  CREATE OR REPLACE TABLE

SAFE  dim_publishers (table)
  CREATE OR REPLACE TABLE

SAFE  fct_daily_sales (incremental, append_new_columns)
  ADD COLUMN  total_sales

dbt-plan: 4 checked, 3 safe, 0 warning, 1 destructive, 1 cascade risk(s)

What It Does

dbt-plan analyzes compiled SQL diffs to catch dangerous schema changes at PR time:

  • Column changes: detects ADD/DROP COLUMN from SQL diff
  • Risk assessment: judges safety based on materialization x on_schema_change rules
  • Cascade analysis: finds downstream models broken by a dropped column — the ones that name it, resolved against the project's own schema rather than matched as text; the ones that select * and lose it without their own file changing; and the tests whose fixtures pin it down. Names the exposures whose owners need telling
  • Contracts: reports a change an enforced contract will reject, in either direction
  • Config changes: detects materialization or on_schema_change policy changes
  • Type changes: compares explicit CAST types between revisions
  • SELECT * resolution: reads the columns from the CTEs of the same statement, and follows a ref() into the referenced model's compiled SQL

The analysis reads files, compares them, and warns; it does not connect to a warehouse or simulate dbt run. The optional dbt-plan run workflow invokes your compile command, which can require credentials and execute macros.

Quick Start

pip install dbt-plan
dbt-plan run               # compile baseline → compile current → check

dbt-plan run does the whole thing in one command, and needs whatever credentials your dbt compile normally needs.

The loop it is built for

Once you have a baseline, the inner loop is a single sub-second command. Edit a model or a macro, recompile, and see what dbt run would do — before running it:

dbt-plan snapshot          # once, on the revision you are changing from
                           # ... edit models, edit macros ...
dbt compile && dbt-plan check

dbt-plan check reads local compiled artifacts. Its latency is separate from dbt compile, which can depend on your adapter, macros, and warehouse. See the reproducible CLI benchmark for measured workloads, raw samples, and commands to measure your machine.

Working with a coding agent

An agent editing models cannot eyeball a diff and hesitate. Give it the check and the reasons behind it:

dbt-plan agent-setup       # writes dbt-plan guidance into your AGENTS.md
dbt-plan check --format json

The guidance leads with what an agent most often gets wrong: adding a model to ignore_models, or downgrading on_schema_change from sync_all_columns to ignore, silences a real finding without making the change safe.

Or give it the check as an MCP tool:

pip install 'dbt-plan[mcp]'
dbt-plan-mcp                # stdio MCP server exposing `plan` and `snapshot`

plan returns the verdict, the per-model operations, and — separately — a refusals list naming everything dbt-plan declined to judge. That separation is the point: a person reading "safe" may still glance at the diff, an agent reading it proceeds, so a non-empty refusals must never be collapsed into the verdict.

Both tools accept target_dir when dbt writes artifacts outside the default target/, for example plan(project_dir=".", target_dir="build").

The server is a separate package from the analysis core. The core is offline and synchronous by design and tests/test_invariants.py fails the build on an asyncio or network import anywhere inside it; an MCP server is both, so keeping them apart is what keeps that guarantee provable.

Registry entry — the line below is how the MCP registry verifies that whoever publishes the entry also owns this PyPI package, so it has to stay in the README that ships:

mcp-name: io.github.PresentJay/dbt-plan

More commands

dbt-plan init              # Generate .dbt-plan.yml config + update .gitignore
dbt-plan stats             # Analyze project readiness
dbt-plan ci-setup          # Generate GitHub Actions workflow
dbt-plan check --format github   # GitHub markdown output
dbt-plan check --format json     # JSON for CI pipelines
dbt-plan run --against main           # compare with where this branch left main
dbt-plan check --select fct_orders    # one model
dbt-plan check --select fct_orders+   # it and everything downstream

--select accepts model names and optional upstream/downstream + operators; comma-separated terms form a union. In the next minor release, unsupported or unknown selections fail with exit 3. Use explicit version names such as fct_orders_v2. See the selection contract and examples.

Scope

dbt-plan is a static analysis warning tool, not a runtime simulator.

In scopeOut of scope
Column ADD/DROP detection from compiled SQLdbt run simulation
materialization × on_schema_change risk rulesWarehouse connection
Cascade: broken refs, build failures, inherited column lossseed / source change detection
Config change detection (materialization, osc)pre_hook / post_hook DDL analysis
Unit test fixtures and exposure owners downstreamseed / source fixtures dbt-plan cannot read
Enforced-contract violations: names, and types by familyContract types compared more finely than family
Explicit CAST type changesType changes on uncast columns
SELECT * resolved through CTEs and ref()SELECT * over a source or a raw table
CI exit codes + structured outputfull_refresh mode judgment

Design principle: false warnings are OK, false safe is never OK.

When to use it

dbt-plan answers a narrower question than the warehouse-connected tools (Recce, SQLMesh, data-diff) and costs nothing to run, so it works as the cheap gate in front of them — and on the Fusion engine, which compiles without a warehouse connection, that includes fork pull requests where they cannot run at all. See use cases for the comparison, real timings, and what it gets wrong.

Deliberately Not Planned

Ideas that look useful but contradict what this tool is:

IdeaWhy not
INFORMATION_SCHEMA queryRequires a warehouse connection. dbt-plan reads files and nothing else, which is what lets it run wherever its input exists — including a fork's pull request, once the project compiles on Fusion.
Type changes on columns with no explicit CASTThe type is whatever the warehouse assigned, so seeing a change would mean asking it. Columns that are cast explicitly on both sides are compared — see below.

DDL Prediction Rules

Materializationon_schema_changePredicted DDLSafety
tableanyCREATE OR REPLACE TABLESAFE
viewanyCREATE OR REPLACE VIEWSAFE
ephemeralany(no physical object)SAFE
snapshotanyREVIEW REQUIREDWARNING
incrementalignoreno schema DDL; existing targets may fail on column changesWARNING on changed/unknown columns
incrementalfailbuild failureWARNING
incrementalappend_new_columnsADD COLUMN onlySAFE
incrementalsync_all_columnsADD + DROP COLUMNDESTRUCTIVE if columns removed
any(model removed)MODEL REMOVEDDESTRUCTIVE
any(unknown osc)UNKNOWN on_schema_changeWARNING
materialized_view / custom(not set by you)UNKNOWN materializationWARNING
materialized_view / custom(you set one)follows the incremental rulesper osc
any(contract: {enforced: true})CONTRACT VIOLATIONWARNING

Under a contract, a column's declared data_type is compared with its explicit CAST, by family -- text against number against date/time against boolean. varchar and text are the same family and not a finding; varchar and integer are a build failure. Comparing more finely means a per-adapter type table, and a wrong answer about a type is worse than no answer.

"Not set by you" means the author wrote no on_schema_change, in the model or in dbt_project.yml. dbt resolves one for every model regardless, so the resolved value asserts nothing; an explicit setting is a claim about how that materialization behaves and is honoured. dbt-plan reads unrendered_config to tell them apart.

An enforced contract inverts the rules above: dbt requires every column to be declared, so a column added to the SQL fails the build just as a removed one does. Names only — dbt compares its declared data_type against the warehouse, which dbt-plan does not read.

CI Integration (GitHub Actions)

name: dbt-plan
on:
  pull_request:

jobs:
  plan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    env:
      # Whatever your profiles.yml reads. `dbt compile` connects; dbt-plan does not.
      UV_PROJECT_ENVIRONMENT: ${{ runner.temp }}/project-venv
      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_PRIVATE_KEY: ${{ secrets.SNOWFLAKE_PRIVATE_KEY }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # the base revision has to be in the clone
          persist-credentials: false
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install uv
      - run: uv sync --locked

      - uses: PresentJay/dbt-plan@v1
        with:
          compile-command: uv run --no-sync dbt compile

This example assumes a checked-in uv.lock with your dbt adapter included. The virtual environment lives outside the checkout so changing revisions cannot replace it. With pip/requirements.txt, install your adapter before the Action and use the default dbt compile command. Run dbt deps first if your project uses dbt packages.

Keep the pull_request trigger. Never switch it to pull_request_targetdbt compile runs Jinja and macros written in the pull request, so that would hand your warehouse credentials to code from any fork.

InputDefault
compile-commanddbt compileRuns twice, once per revision.
base-refthe PR baseThe revision to compare against.
project-dir.dbt project directory.
target-dirtargetdbt artifact directory relative to project-dir; set this when dbt writes to a custom path such as build.
dialectemptyPreserve CLI configuration and manifest adapter detection, then fall back to snowflake. Set only to override the project.
versionlatestPin a dbt-plan release.
fail-ondestructiveOr warning, or never.
summarytrueWrite the report to the job step summary.

Outputs verdict (safe / destructive / warning), exit-code, and report (path to the JSON report), so a later step can comment on the PR or open a ticket.

For a workflow you own outright rather than a wrapped action, dbt-plan ci-setup generates one with credential wiring and least-privilege notes inline. The next-minor generator supports pyproject.toml/uv.lock and requirements.txt, installs dbt and dbt-plan in one environment, and separates summary rendering from its gate. The default FAIL_ON: destructive allows warnings but always blocks execution errors. Regenerate existing workflows to adopt it; see docs/ci-integration.md.

Exit codes

The next minor release separates execution failures from review findings (a breaking change from 0.15.x). This contract is implemented on main and is not yet released.

CodeMeaning
0No blocking findings under the configured policy.
1Destructive findings.
2Review required: SQL uncertainty or potential build/test failures.
3Execution failed: invalid input, missing artifacts, compile/recovery failure, or an internal error. No completed verdict.

warning_exit_code still controls review findings; its default remains 2. Setting it to 0 does not suppress execution errors. Code 3 is reserved and cannot be used for warnings. The Action fails on execution errors even with fail-on: never. See the migration guide before upgrading a CI script or pinned Action.

How It Works

flowchart TD
    A[dbt-plan snapshot] --> B[Save compiled SQL + manifest.json]

    C[dbt-plan check] --> D[diff_compiled_dirs]
    D --> E[base compiled SQL]
    D --> F[current compiled SQL]
    E --> G[extract_columns]
    F --> H[extract_columns]
    G --> I[base columns]
    H --> J[current columns]
    I --> K[column diff]
    J --> K
    K --> L[predict_ddl + manifest config]
    L --> M{Safety?}
    M -->|SAFE| N[exit 0]
    M -->|WARNING| O[exit 2]
    M -->|DESTRUCTIVE| P[exit 1 — block merge]
    L --> Q[find_downstream]
    Q --> R[format_text / format_github]

Contributing

See CONTRIBUTING.md for development setup, TDD workflow, and coding rules.

First contribution? Choose a small, ready task: SQL fixtures, documentation or a sample-script fix, with exact expected results and no warehouse credentials required.

CI feedback for contributors: PRs to this repository get one automatically updated status comment with the tested commit, approval waits, and links to failed jobs. Comment /ci to refresh it, or /ci retry to retry a transient failure. External contributions still need a maintainer's execution approval; retrying does not bypass it. See commands and limits. This helper is for contributions to dbt-plan itself, separate from the GitHub Action you install in your own dbt project.

Architecture

src/dbt_plan/
├── columns.py      # SQLGlot column extraction (multi-dialect)
├── config.py       # .dbt-plan.yml + env var configuration
├── predictor.py    # DDL risk assessment rules + cascade analysis
├── manifest.py     # manifest.json parsing + downstream BFS
├── diff.py         # compiled SQL directory comparison
├── formatter.py    # text / GitHub markdown / JSON output
└── cli.py          # CLI: snapshot, check, init, stats, run, ci-setup

How to Contribute

Where to start: the open issues, particularly those labelled good first issue. Each starter issue has an agreed scope, expected results and validation commands. Check its assignee and recent comments, then confirm ownership before starting.

Design decisions: See docs/design-notes.md.

Supported

  • dbt-core 1.7+, and the dbt Fusion engine (verified against 2.0.0-preview.218)
  • Snowflake, BigQuery, Redshift, Postgres, DuckDB and other mapped adapters: dialect inferred from the manifest. Unmapped adapters fall back to Snowflake; see analysis limits.
  • Python 3.10+
  • CTE, UNION ALL, QUALIFY, window functions, VARIANT access

License

Apache-2.0

For adapter dialect fallback, dbt Mesh consumers, and stars over sources or seeds, see analysis limits. For CLI compile_command argv semantics, the Action's shell command input, and dbt deps setup, see compile commands.

Reviews

No reviews yet

Be the first to review this server!