AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Migrationpilot

mcp-mickelsamuel-migrationpilot · by mickelsamuel

PostgreSQL migration linter. Blocks unsafe migrations before merge: 112 rules, the real Postgres parser, lock analysis, auto-fix. CLI, GitHub Action, MCP.

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add mcp-mickelsamuel-migrationpilot

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-mickelsamuel-migrationpilot)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
today

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Migrationpilot? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

MigrationPilot

[](https://www.npmjs.com/package/migrationpilot) [](https://www.npmjs.com/package/migrationpilot) [](https://github.com/mickelsamuel/migrationpilot/actions/workflows/ci.yml) [](https://nodejs.org) [](https://marketplace.visualstudio.com/items?itemName=migrationpilot.migrationpilot) [](https://opensource.org/licenses/MIT)

Block unsafe Postgres migrations before merge.

Local, deterministic analysis for PostgreSQL migrations. Uses PostgreSQL's parser, checks 112 rules, and exits non-zero in CI. No account required. MIT.

npx migrationpilot analyze migration.sql

Try it in your browser · [GitHub Action](#github-action) · Documentation

Benchmark

| Tool | Strict detection | False positives | |---|---:|---:| | MigrationPilot | 30/33 (90.9%) | 1/17 (5.9%) | | Squawk | 20/33 (60.6%) | 1/17 (5.9%) | | pgfence | 25/33 (75.8%) | 3/17 (17.6%) |

56 labelled files. Author-built corpus. Tools pinned.

[Methodology](bench/RESULTS.md) · [Corpus](bench/corpus) · [What MigrationPilot missed](bench/RESULTS.md#what-migrationpilot-missed) · Reproduce: pnpm build && node bench/run.mjs

A finding

-- migration.sql
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
$ migrationpilot analyze migration.sql

  ✗ MigrationPilot —  RED  Score: 80/100
  migration.sql
  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─  ─
  1 statement · 2 critical · rollback GREEN

┌─────┬──────────────────────────────────────────────────┬─────────────────────────┬────────┬───────┐
│ #   │ Statement                                        │ Lock Type               │ Risk   │ Long? │
├─────┼──────────────────────────────────────────────────┼─────────────────────────┼────────┼───────┤
│ 1   │ ALTER TABLE users ADD CONSTRAINT users_ema...    │ ACCESS EXCLUSIVE        │  YELL… │ YES   │
└─────┴──────────────────────────────────────────────────┴─────────────────────────┴────────┴───────┘

  Violations:

  ✗ [MP004] CRITICAL (line 1)
    DDL statement acquires ACCESS EXCLUSIVE lock without a preceding SET lock_timeout. Without a timeout, this statement could block the lock queue indefinitely if it can't acquire the lock, causing cascading query failures.

    Safe alternative:
    -- Set a timeout so DDL fails fast instead of blocking the queue
    SET lock_timeout = '5s';
    ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email)
    RESET lock_timeout;

    Why: Without lock_timeout, if the table is locked by another query, your DDL waits indefinitely. All subsequent queries pile up behind it in the lock queue, causing cascading timeouts across your application. GoCardless enforces a 750ms lock_timeout for this reason.
    Docs: https://migrationpilot.dev/rules/mp004

  ✗ [MP027] CRITICAL (line 1)
    Adding UNIQUE constraint "users_email_unique" on "users" scans the entire table under ACCESS EXCLUSIVE lock. Create the index concurrently first, then use USING INDEX.

    Safe alternative:
    -- Step 1: Create the unique index concurrently (non-blocking)
    CREATE UNIQUE INDEX CONCURRENTLY users_email_unique_idx ON users (...);

    -- Step 2: Add the constraint using the pre-built index (instant)
    ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE USING INDEX users_email_unique_idx;

    Why: ALTER TABLE ADD CONSTRAINT UNIQUE builds a unique index while holding ACCESS EXCLUSIVE lock, blocking all reads and writes for the entire scan. Instead, create the unique index concurrently (non-blocking), then attach it as a constraint with USING INDEX.
    Docs: https://migrationpilot.dev/rules/mp027

  Risk Factors:
    Lock Severity        ██████████ 40/40 — ACCESS EXCLUSIVE (long-held)
    Rule Violations      ████████░░ 80/100 — 2 critical

  112 rules checked in 11ms

Exit code is 2. The headline is RED because critical violations fired; the per-statement Risk column stays YELLOW because table size and query frequency are unknown without a database connection. See [Production context](#production-context).

Contents

[Install](#install) · [AI coding agents](#ai-coding-agents) · [CI](#ci) · [What it checks](#what-it-checks) · [Beyond one file](#beyond-one-file) · [Configuration](#configuration) · [Output](#output) · [Production context](#production-context) · [Comparison](#comparison) · [Pricing](#pricing) · [Architecture](#architecture) · [API](#programmatic-api)

Install

npx migrationpilot analyze migration.sql   # no install
npm install -g migrationpilot              # global

Node 22 or newer. The PostgreSQL parser ships compiled in, so there is nothing else to set up. Exit codes are the same everywhere: 0 clean, 1 warnings under --fail-on warning, 2 critical.

Packaged builds land with v1.6.0, including single-file executables for Linux, macOS and Windows on the release page for machines without Node:

brew install mickelsamuel/migrationpilot/migrationpilot
docker run --rm -v "$PWD:/work" ghcr.io/mickelsamuel/migrationpilot:v1 analyze /work/migration.sql

AI coding agents

Agents write migrations now. They are good at SQL and bad at knowing which statement takes an ACCESS EXCLUSIVE lock on a table with 40 million rows, and by then the outage has already happened.

MCP server. Seven tools, the important one being check_before_apply: a pass/fail gate the agent calls before it writes or runs DDL. It resolves your .migrationpilotrc.yml exactly like the CLI does, so its verdict is the verdict CI will give.

{
  "mcpServers": {
    "migrationpilot": { "command": "npx", "args": ["migrationpilot-mcp"] }
  }
}

| Tool | Purpose | |---|---| | check_before_apply | {sql, pgVersion?, configPath?} returns {verdict: pass\|fail, failOn, violations[], summary} | | analyze_migration | Violations, risk score and lock analysis for one migration | | analyze_migration_dir | Per-file results plus an aggregate for a whole folder | | get_rule | What a rule reports, why it matters, whether it auto-fixes | | suggest_fix | Auto-fixed SQL plus the violations that need a human | | explain_lock | The lock one DDL statement takes and what it blocks | | list_rules | The full catalogue |

Claude Code plugin. [integrations/claude-code/](integrations/claude-code/) pairs a skill that tells Claude to check migrations with a PreToolUse hook that blocks the tool call when it doesn't. It fails open on purpose: a missing install, unparseable SQL, or a timeout lets the call through with a note on stderr, because a guardrail that breaks your workflow when it can't run gets uninstalled.

claude plugin install ./integrations/claude-code

Cursor and Copilot. Copy [integrations/cursor/migrationpilot.mdc](integrations/cursor/migrationpilot.mdc) into .cursor/rules/, or paste [integrations/copilot/copilot-instructions-snippet.md](integrations/copilot/copilot-instructions-snippet.md) into .github/copilot-instructions.md. Both tell the agent when to run MigrationPilot and that suppressing a rule to get past a violation is the user's call, not the agent's.

CI

GitHub Action

# .github/workflows/migration-check.yml
name: Migration Safety Check
on: [pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mickelsamuel/migrationpilot@v1
        with:
          migration-path: "migrations/*.sql"
          fail-on: critical

Posts a report as a PR comment, fails the check on critical violations, and writes SARIF for Code Scanning.

| Input | Description | Default | |---|---|---| | migration-path | Glob for SQL files (required) | | | github-token | Token for PR comments | ${{ github.token }} | | pg-version | Target PostgreSQL version | 17 | | fail-on | critical, warning, irreversible, never | critical | | exclude | Comma-separated rule IDs to skip | | | config-file | Path to .migrationpilotrc.yml | auto-detected | | database-url | Connection for production context | | | license-key | Org plan license key | |

Outputs: risk-level, violations, sarif-file.

Pre-commit

migrationpilot hook install writes a plain git hook and is Husky-aware. With the pre-commit framework instead:

repos:
  - repo: https://github.com/mickelsamuel/migrationpilot
    rev: v1.6.0
    hooks:
      - id: migrationpilot
        args: [--fail-on, warning]

Clean files print nothing. Only migrations with violations are reported.

GitLab CI

include:
  - remote: 'https://raw.githubusercontent.com/mickelsamuel/migrationpilot/v1.6.0/integrations/gitlab/.gitlab-ci-migrationpilot.yml'

migrationpilot:
  variables:
    MIGRATIONPILOT_PATH: db/migrate

Runs on merge requests that touch migrations, keeps the JSON report as an artifact, and annotates the MR diff through GitLab Code Quality.

What it checks

112 rules: 34 critical, 78 warning, 20 auto-fixable with --fix. Ten that matter most:

| Rule | Fix | What it catches | |---|:--:|---| | MP001 | Yes | CREATE INDEX without CONCURRENTLY blocks writes for the whole build | | MP002 | | SET NOT NULL scans the full table. Use the validated CHECK pattern | | MP003 | | ADD COLUMN with a volatile DEFAULT rewrites the table and its indexes | | MP007 | | ALTER COLUMN TYPE rewrites the table under ACCESS EXCLUSIVE | | MP008 | | Several DDL statements in one transaction compound the lock duration | | MP025 | Yes | CONCURRENTLY inside a transaction is a runtime ERROR, not a warning | | MP027 | | UNIQUE constraint without USING INDEX scans the table under an exclusive lock | | MP055 | | Dropping a primary key breaks logical replication | | MP070 | | A failed concurrent build leaves an invalid index the retry silently inherits | | MP097 | | Dropping the index behind a constraint is rejected and aborts the migration |

Browse all 112 rules, or run migrationpilot explain MP027 for one. [The handbook](docs/handbook/README.md) is 20 chapters on why each hazard bites and what to do instead.

Rules adapt to --pg-version (9 through 20): REINDEX CONCURRENTLY from 12, DETACH PARTITION CONCURRENTLY from 14, the native NOT NULL ... NOT VALID path from 18.

Beyond one file

analyze --fix rewrites the 20 fixable violations in place. The rest of the surface:

| Command | What it does | |---|---| | check | Whole directory, plus [cross-file sequence analysis](docs/sequence-analysis.md) | | [simulate](docs/simulate.md) | Runs the migration against an ephemeral in-process PostgreSQL 18 (PGlite) and reports what actually happened | | [plan-fix](docs/plan-fix.md) | Step-by-step expand-contract plan for violations with no one-line fix, with deploy boundaries | | [mutation-test](docs/mutation-testing.md) | Mutates passing migrations into dangerous near-neighbours to find holes in your config | | predict | Duration estimate for an operation, calibrated by --row-count and --size | | template | Generates expand-contract SQL for renames, type changes, NOT NULL, and more | | plan | Visual execution timeline: lock, duration, blocking impact, transaction boundaries | | rollback | Reverse DDL, [graded by how recoverable it is](docs/rollback-grading.md) | | drift | Diffs two live schemas | | precommit | Multi-file entry point the pre-commit framework calls |

Twenty-four commands in total. migrationpilot --help lists them.

Sequence analysis is what a per-file linter cannot see. Three migrations that each look fine can still take one table down together:

$ migrationpilot check migrations/

  ⚠ [SQ001] WARNING cumulative-lock-budget
    "orders" is locked for an estimated 2m across 2 statements in 2 files — over the 1m budget for one deploy.
  ⚠ [SQ002] WARNING hot-table-multi-touch
    "orders" is locked by 3 files in this sequence. Each one queues behind live traffic on its own — fold them into one migration so the table takes the hit once.

Tune it with --lock-budget and --hot-table-threshold , turn it off with --no-sequence, and make it blocking with --fail-on-sequence.

--fail-on irreversible is stricter than critical: it also blocks migrations that destroy data with no down file.

Configuration

Zero-config is the default. check with no directory detects your framework, finds its migrations, and analyzes them in apply order. Fourteen are supported: Flyway, Liquibase, Alembic, Django, Knex, Prisma, TypeORM, Drizzle, Sequelize, goose, dbmate, Sqitch, Rails, Ecto. Force one with --framework prisma, or pipe any generator through --from-command:

migrationpilot check --from-command "python manage.py sqlmigrate myapp 0042"
# .migrationpilotrc.yml
extends: "migrationpilot:strict"
pgVersion: 16
failOn: warning
rules:
  MP037: false                 # off
  MP004: { severity: warning } # downgrade
  MP013: { threshold: 5000 }   # retune
ignore:
  - "migrations/seed_*.sql"

Five presets: recommended (default), strict, ci, startup, enterprise. Inline, -- migrationpilot-disable MP001 suppresses a rule for the next statement and -- migrationpilot-disable-file MP001 does it for the whole file. Name no rule and it suppresses all of them.

Ed25519 license keys validate client-side. --offline skips update checks and every other network call. There is no telemetry.

Output

--format text (default), json, sarif, or markdown, plus --quiet for one gcc-style line per violation and --verbose for per-statement pass/fail.

{
  "$schema": "https://migrationpilot.dev/schemas/report-v1.json",
  "version": "1.6.0",
  "file": "migrations/001.sql",
  "riskLevel": "RED",
  "riskScore": 80,
  "violations": []
}

SARIF feeds GitHub Code Scanning, VS Code and IntelliJ: migrationpilot analyze migration.sql --format sarif --output results.sarif.

Production context

Pass --database-url and MigrationPilot opens one read-only connection to read pg_class, pg_stat_statements and pg_stat_activity. It reads no user data and runs no DDL.

That turns risk scoring from a guess into a measurement, and gives three rules the numbers they have nothing to say without: MP013 (DDL on a high-traffic table), MP014 (long-held locks on a table with millions of rows), MP019 (ACCESS EXCLUSIVE while connections are piling up).

| Factor | Weight | Needs --database-url | |---|---|:--:| | Lock severity | 0-40 | No | | Table size | 0-30 | Yes | | Query frequency | 0-30 | Yes |

GREEN is 0-24, YELLOW 25-49, RED 50-100.

Comparison

| | MigrationPilot | Squawk | Atlas | |---|:---:|:---:|:---:| | Rules, all free | 112 | 40 | 50+ analyzers, none free since v0.38 | | Auto-fix | 20 rules | 0 | 0 | | Cross-file sequence analysis | Yes | No | No | | Real execution against ephemeral PG | Yes | No | Yes, needs Docker | | MCP server for agents | Yes | No | No | | Framework detection | 14 | 0 | 0 | | Config presets | 5 | 0 | 0 | | SARIF for Code Scanning | Yes | No | No | | License | MIT | Apache-2.0 / MIT | Apache-2.0 core, no free lint |

Squawk: 40 rules as of v2.62.0 (Aug 2026). Atlas moved migrate lint to Pro-only in v0.38 (Oct 2025) and later removed it from the Community Edition, so it could not be benchmarked without a paid account. The [methodology](bench/RESULTS.md#why-atlas-is-not-in-the-table) records the exact command and its refusal.

Pricing

Everything the linter does is free and unmetered: all 112 rules including the production-context ones, auto-fix, sequence analysis, simulate, every output format, the GitHub Action, the MCP server. No account, no seat count, no telemetry, MIT.

The $499/year Org plan turns the free linter into an enforc

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.