Use the MCP server

Day-to-day use of dblift mcp: set the server up once against a read-only login, then ask for state, a validation verdict, and the pending SQL before a human applies. Pro and Enterprise 4.5.0 add a second loop that writes files for review. Neither loop applies, undoes, or changes data. Flags, argument tables, and error contracts are on the MCP Server reference. This page is the sequence.

Day-to-day use of dblift mcp: set the server up once against a read-only login, then ask for state, a validation verdict, and the pending SQL before a human applies. Pro and Enterprise 4.5.0 add a second loop that writes files for review. Neither loop applies, undoes, or changes data. Flags, argument tables, and error contracts are on the MCP Server reference. This page is the sequence.

The review loop below is OSS 4.9.0. The authoring loop is what Enterprise 4.5.0 registers. The paid 4.5.0 packages pin dblift==4.5.0. On a Pro or Enterprise 4.5.0 install, validate has no strict, migrate_dry_run has no show_sql, and the server prints no startup line. Those need OSS 4.9.0 on its own. A tool your licence does not cover is an MCP error naming that tier, the same refusal the CLI prints.

1. Set it up once

The server and the agent's shell share dblift.yaml, environment variables, and secrets. Restriction flags only change what this server lists. The database role is the boundary.

A role that can only read one schema

Create the schema-history table with the role that applies migrations (dblift migrate or dblift baseline from the CLI). Then hand the agent a login that can SELECT and nothing else. On PostgreSQL, info, validate, and migrate_dry_run need USAGE on the schema and SELECT on its tables once that table exists. Until then, info and validate try to create the history table. GRANT SELECT ON ALL TABLES covers only tables that already exist, so grant it after the history table exists. ALTER DEFAULT PRIVILEGES FOR ROLE dblift_app covers tables the migrator role (dblift_app) creates.

CREATE ROLE dblift_reader LOGIN PASSWORD '...';
GRANT USAGE ON SCHEMA public TO dblift_reader;
-- After the history table exists. ALL TABLES does not include tables created later.
GRANT SELECT ON ALL TABLES IN SCHEMA public TO dblift_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE dblift_app IN SCHEMA public GRANT SELECT ON TABLES TO dblift_reader;

Grant that role only the one schema the server should see. The root --db-schema flag can retarget any schema the role can read, and no restriction flag fences it.

On a database that has no history table yet, this role has no CREATE, so info and validate fail instead of creating the table. migrate_dry_run neither fails for that reason nor creates the table: a dry run skips creating it, reports every script as pending, and writes nothing.

Pin an environments.agent block

An environment is deep-merged over the root sections. Put the reader on its own block so the agent never runs with the deploy login. See Environments.

database:
  type: postgresql
  host: localhost
  port: 5432
  database: app
  username: dblift_app
  password: "${DBLIFT_APP_PASSWORD}"

environments:
  agent:
    database:
      username: dblift_reader
      password: "${DBLIFT_READER_PASSWORD}"

Keep the login that can CREATE, DROP, or TRUNCATE in the pipeline that runs dblift migrate, not in this process.

Install and register

pip install "dblift[mcp]"

A bare pip install dblift does not install the MCP SDK. dblift mcp shipped in OSS 4.1.0; the review loop on this page is PyPI 4.9.0.

--env goes before mcp and applies to every tool call. No tool argument can change it.

{
  "mcpServers": {
    "dblift": {
      "command": "dblift",
      "args": ["--env", "agent", "mcp"]
    }
  }
}
ClientWhere that block goes
Claude Code.mcp.json in the project root, or claude mcp add dblift -- dblift mcp
Cursor.cursor/mcp.json
GitHub Copilot (VS Code).vscode/mcp.json
Windsurf~/.codeium/windsurf/mcp_config.json

Optional limits, still not a substitute for the role. --read-only skips tools that declare read_only=False. --tools is an exact-name allowlist; an unknown name, or --tools "", refuses to start. --mode review withholds the same writers and tells the session to read and report. The default mode is author, which restricts nothing.

{
  "mcpServers": {
    "dblift": {
      "command": "dblift",
      "args": ["--env", "agent", "mcp", "--tools", "info,validate,migrate_dry_run"]
    }
  }
}

Read the startup line

After the server is built on OSS 4.9.0, one line goes to stderr, never to the stdio channel the client reads. It names the environment and a masked database target. Check it before the first tool call. A Pro or Enterprise 4.5.0 install prints no startup line. --offline and skipped tools add further stderr lines beyond that one.

dblift mcp: environment agent; database postgresql dblift_reader@localhost:5432/app

A missing or unreadable configuration file does not stop the start: the line says configuration not loaded at start, and every tool call loads the configuration itself. No connection is opened at start. With the default text log format, one log file is reused for the whole server process.

2. Review a new migration

Ask in ordinary language. The assistant calls the tools. You read the results. Nothing in this loop writes a migration or applies one.

What has been applied, and what is still pending? Do not apply anything.

info returns the same JSON as dblift info --format json. dblift://pending is the migrations array from a dry run, not the whole payload.

{
  "success": true,
  "error": null,
  "current_schema_version": "1.0.0",
  "migrations": [
    {
      "script": "V1_0_0__create_users.sql",
      "version": "1.0.0",
      "description": "create users",
      "status": "SUCCESS"
    }
  ]
}
[
  {
    "script": "V1_0_1__add_note.sql",
    "version": "1.0.1",
    "description": "add note",
    "status": "PENDING"
  }
]

Examples are abbreviated. Every row also carries type, checksum, installed_on, installed_by, execution_time and error, which are null when unset. A pending resource that cannot run is an error. It does not come back as [].

Validate the scripts. Use strict, because a file that used to be applied might have been deleted.

validate checks the files on disk (duplicate versions, unsupported formats) and, once history has applied rows, checksums. strict defaults to false, matching the CLI --strict flag. strict: true also fails when a previously applied migration is missing from disk and requires strict version order. That missing-file check compares successful applied history rows with files on disk, so it needs a history table that already has applied rows. With no applied rows there is nothing missing to report. validate does not parse or lint SQL.

A checksum problem is a verdict: isError is false, success is false, and error equals issues[0]. An assistant that only checks isError will treat that as success. Check success.

{
  "success": false,
  "error": "Migration script V1_0_0__create_users.sql has been modified since it was applied. Database checksum: 100, Filesystem checksum: 200",
  "error_count": 1,
  "issues": [
    "Migration script V1_0_0__create_users.sql has been modified since it was applied. Database checksum: 100, Filesystem checksum: 200"
  ]
}

A clean run sets error to null, not "". A missing applied file is reported only when strict is true, and the issue text is Applied migration '<script>' is missing from the migration directory.

Show the exact SQL of everything pending, including placeholders. Do not apply it.

migrate_dry_run always forces --dry-run. Pass show_sql: true to add show_sql and a sql array. Without show_sql, the result has no sql key. The tool needs a live connection. dblift mcp --offline refuses the call. It does not read the SQL out of the files alone.

{
  "success": true,
  "dry_run": true,
  "dry_run_count": 1,
  "show_sql": true,
  "sql": [
    {
      "script": "V1_0_1__add_note.sql",
      "version": "1.0.1",
      "description": "add note",
      "statements": [
        "ALTER TABLE users ADD note TEXT",
        "ALTER TABLE ${APP_SCHEMA}.users ADD note TEXT"
      ]
    }
  ]
}

Placeholders that resolve are substituted, so a placeholder value that is a secret appears in statements. A placeholder with no value stays visible as ${APP_SCHEMA}. Read the statements before proposing a change.

Unknown argument names are rejected before the command runs. The dry-run argument is target_version, not target.

3. A human applies

The server does not apply, undo, or clean a migration, and it does not run data apply or data undo. Those stay on the CLI.

dblift migrate

In GitHub Actions, dblift/action@v1 installs the pip package and runs migrate, validate, or info. See Gate a PR in CI.

4. When a call fails

Two shapes. Learn them apart.

A command that fails before it produces a result is an MCP error (isError: true) carrying the CLI message. The server keeps running. Typical cases:

What you seeWhat to check
ConnectionError: Connection failed: ...Host, port, database name, and that the reader password resolved. A privilege failure after connect uses the database's own text (permission denied for schema … on PostgreSQL), not this wording.
ConnectionError: Could not create the schema-history table: ...info or validate on an empty schema whose role cannot CREATE. Apply or baseline once with the migrator role, then point the server back at the reader.
dblift mcp: the <tool> tool opens a database connection and this server was started with --offline.The tool declares connects=True. Restart without --offline, or use a tool that runs from files. show_sql is one of the calls this refuses.
... requires a PRO license (current: …). Upgrade at https://dblift.com/upgrade (exit code 4)The commercial package is installed but the licence does not cover the tool. Same message shape for Enterprise.
<kind> '<path>' already exists; pass overwrite to replace itAn existing output or output_file (including the undo script diff_to_sql writes beside it). Pass overwrite: true, or pick another path inside the project directory. Input paths are only confined.
<kind> '<path>' resolves to '...', which is outside the project root '...'Agent-named paths are confined to the directory the server was launched in, symlinks included.

dblift://history and dblift://pending fail the same way as the tools they wrap. They do not return an empty list.

A command that ran to a verdict is a normal result even when the verdict failed. Checksum issues stay isError: false with success: false.

5. Author a change (Pro and Enterprise 4.5.0)

Paid tools are registered by the commercial build through dblift.mcp_tools. Each tool's description ends with the licence its handler requires. Calling one without that licence is the error in the table above, not a silent skip.

--offline refuses a tool when it is called if that tool was registered with connects=True. It does not hide the tool at start. These do not open a connection, so --offline does not refuse them: validate_sql (Pro), plan, edit_model, validate_model, dblift://model-schema, and dblift://policy (Enterprise). diff is refused on every call, including one that passes base or desired_model, because connects is fixed on the tool and does not change per argument.

Paths an agent names (output, output_file, output_dir, model, files, snapshot_model, desired_model, base) must stay inside the project directory. An existing output or output_file is refused until overwrite is true. Input paths are only confined. diff with desired_model and generate_sql replaces dblift-diff.sql, and export_schema with output_dir replaces same-named files. output is the write path on export_schema, snapshot, data_plan, and edit_model. output_file, plus the undo script beside it, is the write path on diff_to_sql. edit_model defaults output to model, so an in-place edit always needs overwrite.

--read-only and --mode review skip tools registered read_only=False: export_schema, diff_to_sql, data_plan, snapshot, and edit_model. diff stays listed. A diff call that passes both desired_model and generate_sql still writes files.

Pro: drift, a SQL file, and a data plan

diff (Pro) compares the live database with the database-stored snapshot. The result is text mode: success and output (the console text). generate_sql: true without desired_model puts the synchronization script and its undo script in that text. It does not write a file. The CLI --output-file flag is not an argument of this tool on that path.

What does the live schema have that the migrations do not? Show the SQL. Do not write a file and do not apply it.

To write the file, use diff_to_sql (Pro). It runs diff --generate-sql --output-file. You name output_file. Unless no_undo, it also writes the paired undo script next to it (V<n>__x.sql becomes U<n>__x.sql, otherwise <name>.undo.sql). On Enterprise, diff_to_sql also writes or updates dblift-manifest.json. It reads the database and applies nothing.

export_schema (Pro) writes the live schema as SQL to output or output_dir. One of those is required. In output mode an existing file needs overwrite. In output_dir mode, files of the same name inside the directory are replaced and overwrite has no effect. This file is SQL, not a model. edit_model does not read it.

validate_sql (Pro) lints .sql files, or the migrations directory when files is omitted. It takes files, dialect, fail_on, and severity_threshold. It does not take a rules file, a profile, or a pack list: those come from the project's validation config. The security and schema packs are Pro. On Pro, individual rule IDs need Enterprise, even when the rule comes from the security pack. Any other pack, and any named profile, needs Enterprise. The result is the finding report (command, fail_on, checked_count, summary, metadata, findings, success). A finding has severity, code, and message, plus file, line, and details when they are set. success follows fail_on (default error). This is a verdict, not an MCP error, when the command runs. Abbreviated, one security-pack finding, metadata omitted:

{
  "command": "validate-sql",
  "fail_on": "error",
  "checked_count": 1,
  "summary": { "error": 1, "warning": 0, "info": 0 },
  "findings": [
    {
      "severity": "error",
      "code": "no_hardcoded_credentials",
      "message": "Hardcoded passwords/credentials should never appear in migration scripts",
      "file": "migrations/V1_0_1__add_note.sql"
    }
  ],
  "success": false
}

Schema-aware rules read snapshot.source when it names a file that can be read with no connection. With no such file they are skipped, and the report says so, instead of a quiet pass.

data_plan (Pro) plans audited corrections for dataset (default corrections) and returns the same JSON as dblift data plan --format json. Pass output to also write that file. It does not apply. data_status (Pro) reads the audit trail for the same dataset. data apply and data undo are not tools. See Data corrections.

Enterprise: edit a model, then render SQL

This loop starts from a model file, not from export_schema. snapshot (Enterprise) writes the database-stored snapshot to output. The tool always passes --source database-stored. It does not capture a live snapshot. Or start from a snapshot file the environment already published. See Snapshot models.

Read the project policy and the model schema, then export the stored snapshot to models/app.json. Do not apply anything.

dblift://policy is JSON: rules (profile, rule_ids, rules_file_set), zero_downtime.enforce, snapshot.max_snapshot_age, allow_drops (a list of object classes), and policy_hash. It does not include the database section, a rules file's contents, or fail_on. dblift://model-schema is the JSON Schema edit_model writes, validate_model checks, and diff reads through desired_model.

edit_model (Enterprise) applies an ordered list of typed operations to model and writes output (default: model). The list is applied entirely or not at all. There is no CLI command for this tool. An operation is an object with op and that operation's fields. The fourteen op values are create_table, drop_table, rename_table, add_column, alter_column, rename_column, drop_column, add_constraint, drop_constraint, create_index, drop_index, create_view, replace_view, and drop_view. Nested column, constraint, index, and view objects use the same keys as the model file. A typo is an error naming the field. The checks are structural (the table exists, the column does not, an index names real columns). Type legality is decided later, on the SQL diff renders.

Add a nullable integer column note to users with edit_model. Write the model back in place. Do not write SQL yourself.

{
  "model": "models/app.json",
  "overwrite": true,
  "operations": [
    {
      "op": "add_column",
      "table": "users",
      "column": { "name": "note", "data_type": "integer" }
    }
  ]
}
{
  "success": true,
  "output": "models/app.json",
  "operations": 1,
  "base": { "path": "models/app.json", "checksum": "sha256:…", "captured_at": "…" },
  "desired_model": { "path": "models/app.json", "checksum": "sha256:…", "captured_at": null }
}

validate_model (Enterprise) checks the file with no connection and writes nothing. A model that fails validation is a normal result, valid: false and errors. A path outside the project, or a missing licence, is an MCP error.

{ "valid": false, "errors": ["index 'users_note_idx' references unknown table 'users'"] }

diff with desired_model is Enterprise. A Pro licence is denied on that argument even though diff without it is Pro. base names a published snapshot to compare against instead of the live database and requires desired_model (--base requires --desired-model). diff does not accept a snapshot-model argument: the CLI rejects --snapshot-model on diff for every licence. Pass generate_sql: true with desired_model to write dblift-diff.sql beside the model, or inside output_dir when you set it, plus the undo script and, on an Enterprise install, dblift-manifest.json in that same directory. The tool result then reports the file written in output, rather than the script text. No DROP of a table, column, constraint, or index is rendered unless the project's allow_drops grants that class. There is no tool argument that changes it.

Diff models/app.json as the desired model, write the SQL and the undo next to it, and run validate_sql on the file that was written. Do not apply.

plan (Enterprise) takes a required snapshot_model and returns a finding report. It does not open a database: the argument is required, so the config path that would read a db: snapshot never runs. preflight (Enterprise) always passes --skip-replay, so it does not replay and does not start a container, but it does connect. diff_impact (Enterprise) returns the impact finding report; success follows fail_on.

fail_on on plan, preflight, and diff_impact, and max_snapshot_age on plan and preflight, may only tighten the value in dblift.yaml. A weaker value is refused, naming both. The check uses the server's own --env and --config.

The human still applies

Open the generated SQL, the undo script, and dblift-manifest.json when it was written. validate_sql, plan, or preflight are more checks. They are not an apply. When the file is the one you want in the tree, a person runs dblift migrate from the CLI or CI, as in the review loop above.

See dblift mcp for the server flags, and MCP Server for the tool contract.

DBLift is information technology / developer tools software. Contact: contact@dblift.com.