loading…
Search for a command to run...
loading…
An MCP server that acts as a gatekeeper for database and configuration changes, requiring proposals, dry-run simulations, and confirmation tokens before applyin
An MCP server that acts as a gatekeeper for database and configuration changes, requiring proposals, dry-run simulations, and confirmation tokens before applying any modifications.
Your AI coding agent can silently destroy your production database.
A misplaced DROP COLUMN, a missing WHERE, a one-character .env typo — Claude Code, Cursor, OpenClaw, and every other coding agent will cheerfully execute the change and report success while your data quietly vanishes.
This was born from watching an OpenClaw agent break its own config file trying to make a "small" change. The fix turned out to be universal: any agent that can edit anything should have to slow down, show its work, and ask first.
Safe Migrations MCP is the gate they have to pass through first. Every proposed schema change or config edit is diffed, risk-flagged, snapshotted, and requires a one-time confirmation token from a fresh simulate_impact call before a single byte is written.
Works with any MCP-capable agent. Local-first. Zero cloud dependency.
The mandatory checkpoint between the agent and your disk:
proposal_id plus a redacted preview and
SHA-256 hash of the SQL/edit and its rollback. Full payload is stored
server-side, never echoed back.confirmation_token bound
to the proposal's fingerprint.confirmation_token. Snapshots the
file or DB first. Logs everything to an append-only audit trail.~2k LOC of Python, hardened against the usual footguns (symlink writes, silent SQLite creation, MySQL DDL auto-commit, token replay, secret leakage in diffs).
pip install safe-migrations-mcp # or: pipx install safe-migrations-mcp
safe-migrations-mcp # speaks MCP over stdio
Or from a local clone:
git clone https://github.com/possibly6/safe-migrations-mcp
cd safe-migrations-mcp
pip install -e '.[all]'
safe-migrations-mcp
Optional extras (Postgres / MySQL drivers):
pip install 'safe-migrations-mcp[postgres]' — adds psycopgpip install 'safe-migrations-mcp[mysql]' — adds PyMySQLpip install 'safe-migrations-mcp[all]' — bothSQLite, YAML, JSON, .env, and Prisma/Drizzle schema-file parsing work with
zero extra deps.
~/.claude/claude_desktop_config.json (or ~/.config/Claude/claude_desktop_config.json on Linux):
{
"mcpServers": {
"safe-migrations": {
"command": "safe-migrations-mcp"
}
}
}
~/.cursor/mcp.json:
{
"mcpServers": {
"safe-migrations": { "command": "safe-migrations-mcp" }
}
}
Point the agent at the safe-migrations-mcp binary on stdio. The tool names
match the list below.
| Tool | Purpose |
|---|---|
inspect_schema(connection) |
Current DB/ORM schema. Postgres, MySQL, SQLite, plus schema.prisma and Drizzle TS files. Cached 60s. |
inspect_config(path) |
Parse YAML / JSON / .env / Prisma / TOML; return keys and shape. |
propose_migration_or_edit(kind, …) |
Create a proposal from natural language, raw SQL, or a new config file. Returns proposal_id plus redacted previews and hashes. Nothing is written. |
simulate_impact(proposal_id) |
Dry-run the proposal. DB: rolled-back transaction + affected-row count + SQL/rollback previews. Config: unified diff + key delta. Always returns risk flags. |
generate_rollback(proposal_id | sql) |
Exact undo SQL (or the config-snapshot restore path). |
apply_change(proposal_id, confirmation) |
Requires the fresh confirmation_token from simulate_impact. Snapshots first, then executes in a transaction. |
get_change_history(limit) |
Append-only audit log of every applied change. |
cleanup_state(max_age_days) |
Prune old proposal metadata, secret payloads, and snapshots from local state. |
You: "Add a
last_logintimestamp column tousersin./demo.db."
Agent (silently):
propose_migration_or_edit(kind="db", connection="sqlite:///./demo.db", request="add column last_login of type TEXT to users")→
proposal_id = p_9a1b2c3d4e,sql_preview = ALTER TABLE "users" ADD COLUMN "last_login" TEXT;,rollback_preview = ALTER TABLE "users" DROP COLUMN "last_login";
Agent (silently):
simulate_impact("p_9a1b2c3d4e")→
risk_level = "low",dry_run.ok = true,affected_rows = 0
Agent (to you):
I'd like to run:
ALTER TABLE "users" ADD COLUMN "last_login" TEXT;Risk: low. Rollback available. Confirm?
You: "yes, confirm"
Agent (silently): copies the
confirmation_tokenfromsimulate_impact(...)and callsapply_change("p_9a1b2c3d4e", "<that token>")→ done, audit logged.
And the dangerous case:
You: "drop the orders table."
Agent (silently):
propose_migration_or_edit(kind="db", ..., sql="DROP TABLE orders;")→simulate_impact(...)returnsrisk_level: "critical", flag: "Drops an entire table (all data lost)".
Agent (to you):
🛑 This is a critical destructive change on 3 rows. Rollback can recreate the table but row data cannot be auto-restored without a backup. Are you sure? Reply with the exact
confirmation_tokenfrom the simulation step to proceed.
The agent physically cannot execute until that token is passed through — the server enforces it, not the model.
Claude / Cursor / OpenClaw:
Use the safe-migrations MCP. Inspect sqlite:///./examples/demo.db.
Then propose adding a `phone` TEXT column to users (nullable).
Show me the proposal + simulated risk. Wait for my approval before applying with the confirmation token from simulate_impact.
Use safe-migrations to edit ./examples/config.yaml: set app.log_level to "debug".
Show the diff and risk first. Only apply after I confirm.
Use safe-migrations to audit recent changes — call get_change_history and
summarize the last 10 entries.
git clone https://github.com/possibly6/safe-migrations-mcp
cd safe-migrations-mcp
pip install -e .
python examples/seed_demo.py # creates examples/demo.db
safe-migrations-mcp # start the server
Then, from your MCP-connected agent:
inspect_schema("sqlite:///./examples/demo.db")inspect_config("./examples/config.yaml")propose_migration_or_edit(kind="db", connection="sqlite:///./examples/demo.db", request="create index on orders(status)")simulate_impact(...) → apply_change(..., "<confirmation_token>")SQL risk rules (severity):
DROP TABLE / DROP DATABASE / DROP SCHEMA — criticalDROP COLUMN, TRUNCATE, DELETE/UPDATE without WHERE — highALTER COLUMN … TYPE, RENAME TO, ADD COLUMN NOT NULL without DEFAULT, GRANT/REVOKE — mediumConfig risk rules:
database|db|auth|secret|token|production|prod|migration — highpassword|secret|api_key|token|private_key|database_url|dsn|credentials — mediumAnything ≥ medium sets confirmation_required: true.
High-risk config changes are blocked from direct apply so the agent has to reduce scope or hand the edit back for manual review.
All local, all visible:
~/.safe-migrations-mcp/
├── proposals/ # redacted proposal metadata as JSON
├── secrets/ # private proposal payloads (0600), pruned with cleanup_state
├── snapshots/ # pre-change backups
└── audit.jsonl # append-only log of applied changes (redacted)
Override with SAFE_MIGRATIONS_HOME=/path.
✅ Use Safe Migrations MCP when:
.env files❌ Use a real migration framework (Alembic / Prisma Migrate / Flyway) when:
This is a gatekeeper, not a migration framework. They're complementary — author your migrations with Alembic, let agents propose runtime tweaks through this.
In scope: SQLite, Postgres, MySQL inspection / DML; YAML, JSON, .env, Prisma, Drizzle (best-effort). Natural-language intents for common DDL (add/drop/rename column, create index, drop table). Raw SQL pass-through with automatic best-effort rollback.
Out of scope (on purpose): MySQL DDL apply, full SQL dialect coverage, online schema migrations, arbitrary DSL translation. Safety and clarity beat breadth here — if a proposal is outside what we can analyze, we say so instead of guessing.
Q: How is this different from Alembic, Prisma Migrate, or Flyway? A: Those are migration frameworks — they track, version, and apply schema changes you author by hand. This is a gatekeeper — it sits between an AI agent and your DB/configs and forces every change through diff → simulate → confirm → apply. Use both.
Q: Can the agent bypass the confirmation token?
A: Not from inside this server. The token is generated server-side, bound to a SHA-256 fingerprint of the proposal, and expires in 15 minutes. Editing the proposal invalidates the token. The agent must call simulate_impact to get a fresh one — and the client UI (Claude Code's prompt, Cursor's confirm dialog) is what makes a human actually read the proposal before that token gets passed back.
Q: Does this work in CI? A: It's the wrong tool for CI. The whole point is a human checkpoint. In CI, use your normal migration framework with version-controlled SQL. Reach for this server when an agent (Claude Code, Cursor, OpenClaw) is the one proposing the edit.
Q: What happens if the apply fails halfway through?
A: SQLite changes run inside a transaction and roll back on error. Postgres applies via psycopg with explicit commit/rollback. Failed applies are marked apply_failed, not applied, and audited as a separate event so you can find them later.
Q: Why is MySQL DDL apply intentionally blocked? A: MySQL auto-commits DDL — there's no real "dry-run inside a transaction" or rollback. This server refuses to pretend it has a safety net it doesn't have. MySQL DML (INSERT/UPDATE/DELETE) and inspection still work fine.
Q: Does it know about my Postgres triggers, views, or functions? A: Not in v0.1 — inspection covers tables and columns. Triggers/views/functions are out of scope for now.
Q: My agent has a raw write_file tool too. Can't it just bypass the MCP?
A: Yes — the safety is opt-in at the agent's tool level. If you wire safe-migrations in and leave a raw filesystem write tool enabled for the same paths, the agent can route around the gate. The intended pattern is: route DB and config edits through this server, and keep raw filesystem writes scoped to other paths.
pip install -e '.[all]'
pip install pytest
pytest -q
MIT.
Выполни в терминале:
claude mcp add safe-migrations-mcp -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.