moveit-mcp
Supports Databricks as a source or target for table migrations, including optional Time Travel snapshot reads, bulk loading, identity mapping, and staged verified migrations with platform-specific limitations for defaults and foreign keys.
Supports MySQL as a source or target for table migrations, with consistent snapshot reads for InnoDB sources, staged loading, dry-run validation, verification, and rollback.
Supports PostgreSQL as a source or target for table migrations, including automatic snapshot reads, schema and type planning, staged loads, verification, and rollback. It can carry over primary keys, identity columns, defaults, indexes, and foreign keys where supported.
Supports Snowflake as a source or target for table migrations, including optional Time Travel snapshot reads, bulk loading via stage and COPY INTO, staged loads, verification, and rollback.
Supports SQLite as a source or target for table migrations, with optional snapshot reads, staged loads, type-conversion validation and warnings, verification, and rollback.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
moveit-mcp
Point your coding agent at a source and a target. It maps the schema, rehearses the move, and proves nothing was lost.
moveit-mcp is an MCP server and CLI that lets agents like Claude Code, Codex and Cursor migrate tables between databases without the usual "ran a script, hope it worked" step. Every migration is dry-run first, read from one consistent view of the source where the database supports it, loaded into hidden staging tables, read back and compared against the source, and only then swapped into place. If anything fails, your existing tables were never touched.
Supported: PostgreSQL · MySQL · Oracle · Snowflake · Databricks · SQLite · CSV, in any direction.
$ moveit migrate --source csv_in --target db --yes # real output: a 2,500-row CSV into SQLite
Plan plan_95fde23a37 (hash 03a90805522d5a02): 1 table(s)
customers -> customers 2500 rows, 9 columns
[warn] customers.mrr: SQLite stores numbers as 64-bit floats, so values beyond ~15 significant digits cannot be stored exactly; verification will fail if any do. Use a text type override for exact values.
dry run customers: 2500 rows, 0 failing
Run run_1fd56ce0: succeeded
customers: promoted, 2500 rows, verified
Report: ./.moveit/runs/run_1fd56ce0/report.mdHow it keeps data safe
Plan (
plan_migration) proposes column types, names, keys, indexes, defaults, identity columns and DDL, and flags conflicts. Writes nothing.Dry run (
dry_run) reads every source row and checks it converts to the target types: integer ranges, decimal precision and scale, string lengths,NOT NULL, impossible dates, invalid JSON. Failures are reported per column with the row keys, not the values.Run (
run_migration) needs the plan's hash, proving the exact plan was reviewed, and a complete passing dry run of that same plan. It reads every table from one point-in-time view of the source where the system supports it (PostgreSQL, MySQL/InnoDB, Oracle; opt-in elsewhere, see Consistency), and loads each table into a staging table.Verifies by reading the staged data back and comparing row counts, an order-independent whole-table digest and a per-column digest against what was sent. Inside a snapshot the source count and the rows read come from the same instant and must agree exactly; without one, the count is checked before and after the copy, so a source that changed mid-copy fails the run.
Promotes all tables together with metadata-only renames (or, for append, one insert per table after a key check). A failure at any earlier point leaves existing tables untouched.
Rolls back (
rollback) on request, undoing exactly what the run did. It never touches the source.
Every step is written to a hash-chained evidence log, and export_report turns it into a Markdown report. The chain makes edits and deletions detectable; it is tamper-evident, not tamper-proof, so record the head hash elsewhere if that matters to you.
Related MCP server: migrate-mcp
Modes: create, replace, append
plan_migration takes a mode:
Mode | What it does | Safety net |
| New tables only. Refuses if a target table exists. | Nothing existing is touched. |
| Loads and verifies a new table, then swaps it in. The old table is renamed to |
|
| Adds the source rows to a table that already exists. Rows are staged and verified first; every key is checked against the existing table before anything is written; a collision stops the run with nothing appended. |
|
Things worth knowing:
Replace refuses when a table outside the plan has a foreign key into a table being replaced (the constraint would silently follow the old table into the backup). Grants, views and triggers that depended on the old table are not carried over.
Append checks that every planned column exists in the target and that no other
NOT NULLcolumn would be left unfilled. Without a key the rows cannot be told apart, so duplicates cannot be detected and the run cannot be rolled back; the plan says so. After an append, the existing table's row count is checked, and the whole table is digested when it started empty. On PostgreSQL the table's serial/identity counter is moved past the appended keys; on other systems the plan warns that you must reset it.Append is not offered for CSV targets.
What is migrated besides columns
Carried over | Notes | |
Primary keys | yes | Named constraints; not enforced on Snowflake/Databricks, so duplicates are checked after staging. |
Identity / auto-increment | yes, for a single integer primary key | PostgreSQL |
Defaults | plain literals, | Anything else (sequence calls, UDFs, expressions) is reported, not guessed. Off by default on Databricks (needs a Delta table feature; set |
Indexes | yes, where the target has them | Created after the load. A failure is reported with its statement and never fails the run. Partial, expression, full-text, spatial and prefix indexes are listed as not migrated. |
Foreign keys | yes, between tables in the same plan | Added after every table is promoted (SQLite: declared in |
Views, triggers, sequences, routines | no | Listed in the plan ( |
Grants, check constraints | no |
Each of identity, defaults, indexes and foreign keys can be turned off with objects on plan_migration (CLI: --skip-objects indexes,foreign-keys,defaults,identity).
Consistency and resume
Snapshot reads. One transaction (or equivalent) is held for the whole staging phase, so every table, and every count, is read as of the same instant even while the source is being written to.
Source | How | |
PostgreSQL |
| automatic, tested live |
MySQL |
| automatic, not tested live |
Oracle |
| automatic, not tested live; a long run needs enough undo to avoid ORA-01555 |
SQLite | one read transaction | opt-in |
Snowflake, Databricks | Time Travel | opt-in |
CSV | none |
If the source changes after the snapshot, the run still succeeds and records a warning; reconcile later shows the drift. A run that is paused (maxBatches) and resumed takes a new snapshot, so the report marks it interrupted. A source that supports no snapshot keeps the strict rule: the row count must not change during the copy.
Resume after a crash. The checkpoint is written after each batch lands, so a crash between the two can leave rows in staging that the checkpoint does not know about. On resume, each staging table's row count is compared with the checkpoint. If they differ, that table is dropped and restarted from the beginning (with a warning); if they agree, loading continues where it stopped. Nothing is duplicated either way, and verification still compares everything at the end.
Speed. Reading the next batch from the source overlaps with writing the previous one (prefetch, default 2 batches; 0 turns it off). Snowflake and Databricks can also bulk load, see below.
Quick start
git clone <this repo> && cd moveit-mcp
npm install && npm run build # Node 22.13+
npm link # puts `moveit` on your PATH (or run: node dist/src/index.js ...)
moveit setup # writes moveit.config.json and prints MCP client settings
moveit doctor # checks drivers and connectionsKeep passwords in environment variables and reference them as "${NAME}". Mark sources "readOnly": true, ideally backed by a read-only database user.
{
"connections": {
"legacy_pg": { "type": "postgres", "url": "${LEGACY_PG_URL}", "readOnly": true },
"warehouse": { "type": "snowflake", "account": "myorg-acct", "username": "MOVEIT", "password": "${SNOWFLAKE_PASSWORD}",
"warehouse": "LOAD_WH", "database": "ANALYTICS", "schema": "RAW", "role": "LOADER" }
}
}Add it to your MCP client:
{ "mcpServers": { "moveit": { "command": "moveit", "args": ["mcp"], "env": { "MOVEIT_CONFIG": "/path/to/moveit.config.json" } } } }Then ask your agent: "Migrate the orders and customers tables from legacy_pg to warehouse."
Database drivers (pg, mysql2, oracledb, snowflake-sdk, @databricks/sql) are optional dependencies, loaded only when a connection of that type is used.
Connection options beyond the basics
Option | Systems | Effect |
| all | Refuse every write through this connection. |
| SQLite, Snowflake, Databricks (sources) | Read all tables as of one instant. |
| Snowflake (targets) | Load each batch with |
| Databricks (targets) | Upload each batch to a Unity Catalog volume and |
| Snowflake, Databricks (targets) | Copy auto-generated keys as plain values. |
| Databricks (targets) | Create column defaults (enables the |
| Databricks (targets) | For |
Tools
Tool | What it does | Writes? |
| Connection names, types, read-only flag. Never credentials. | no |
| Connect and ping. | no |
| Tables, columns, native and canonical types, keys, row counts. | no |
| A few rows, with emails, card numbers, SSNs, names and addresses redacted by default. | no |
| Per-column nulls, distinct counts, lengths, min/max, candidate keys. | no |
| Mapping, DDL, identity, defaults, indexes, foreign keys and conflicts. Takes | no |
| Show a plan and its dry-run status. | no |
| Validate every row against the plan. | no |
| Snapshot, stage, verify, promote. Supports | yes |
| Per-table progress. | no |
| Re-read both sides now and compare digests. Detects drift later. (Not applicable to append plans, where the target also holds older rows.) | no |
| Undo a run. Requires | yes |
| Drop the backups (replace) or staging tables (append) a run kept. Requires | yes |
| Markdown report plus evidence-chain integrity check. | report file |
Overrides on plan_migration let an agent fix what the dry run finds: target (table name), exclude, renames, typeOverrides (to any canonical type, with length, precision and scale) and primaryKey.
CLI equivalents: moveit migrate --source A --target B [--mode create|replace|append] [--skip-objects ...] --yes, moveit finalize <runId> --yes, moveit rollback <runId> --yes.
Support matrix: read this before pointing it at production
System | Source | Target | How it was tested |
SQLite | yes | yes | Automated end-to-end tests, including every feature above |
CSV | yes | yes (create, replace) | Automated end-to-end tests; tested in SQLite → CSV → SQLite round trips |
PostgreSQL | yes | yes | Automated tests against a live PostgreSQL 18: PostgreSQL → PostgreSQL, SQLite → PostgreSQL, and PostgreSQL → CSV → PostgreSQL paths; independent in-database hash comparison, identity, defaults, indexes, foreign keys, snapshots under a concurrent writer, crash-resume, replace, append, rollback, finalize |
MySQL | yes | yes | Unit tests of the SQL it generates and of how it reads catalog rows (identity, defaults, indexes, foreign keys). Never run against a live server. |
Oracle | yes | yes | Same: unit tests only. Never run against a live server. |
Snowflake | yes | yes | Same, plus tests of the bulk-load statements against a fake driver. Never run against a live account. |
Databricks | yes | yes | Same, plus tests of the bulk-load statements against a fake driver. Never run against a live workspace. |
The MySQL, Oracle, Snowflake and Databricks adapters follow each system's documented behaviour, and the staged-load-plus-verification design means a wrong guess about data should surface as a failed run rather than bad data. Index, foreign-key and identity DDL is different: a statement the server rejects is reported with its text instead of failing the run (identity and defaults, which are part of CREATE TABLE, fail the staging step instead, before anything is promoted).
Tested migration paths are not a full source/target matrix. The regular end-to-end suite exercises SQLite → SQLite and SQLite → CSV → SQLite. The live PostgreSQL suite also exercises PostgreSQL → PostgreSQL, SQLite → PostgreSQL, and PostgreSQL → CSV → PostgreSQL. These tests do not establish that every supported source can migrate directly to every supported target.
The optional live conformance suite, test/conformance.live.test.ts, runs SQLite → configured system → SQLite for each system you provide: digests are compared at every hop, schema objects are read back, and where supported it checks a snapshot read under a concurrent writer, identity and defaults on new rows, replace + finalize, and append + rollback. SQLite always runs; PostgreSQL runs when MOVEIT_TEST_PG is set; MySQL, Oracle, Snowflake, and Databricks run only when their corresponding MOVEIT_TEST_* variables are set. The README's current live-test status is listed in the table above; having a conformance test available does not mean that system has been tested against a live server/account/workspace. Give the suite a scratch database or schema (it only creates and drops tables named conf_*):
MOVEIT_TEST_MYSQL='{"type":"mysql","host":"127.0.0.1","user":"root","password":"${MYSQL_PW}","database":"scratch"}' \
MOVEIT_TEST_ORACLE='{"type":"oracle","user":"scratch","password":"${ORA_PW}","connectString":"localhost/FREEPDB1"}' \
npm run test:live(MOVEIT_TEST_SNOWFLAKE and MOVEIT_TEST_DATABRICKS work the same way.) Please open an issue with anything it finds.
Notable per-system behaviour the planner already handles or warns about:
Oracle cannot store
'': it becomesNULL. The dry run counts these and refuses them forNOT NULLcolumns.VARCHAR2is limited to 4,000 bytes, so longer values map toCLOB. Oracle has noTIMEor (before 23ai)BOOLEAN; they are stored asVARCHAR2(15)andNUMBER(1,0). Oracle foreign keys have noON UPDATE.Snowflake and Databricks accept
PRIMARY KEYbut do not enforce it, so duplicates are checked explicitly after staging. Databricks has noTIMEand does not enforceVARCHARlength; its identity columns must beBIGINT.SQLite stores numbers as 64-bit floats, so decimals beyond ~15 significant digits cannot round-trip. The plan warns and verification fails safely. A lone
INTEGER PRIMARY KEYis treated as an identity column (it is the rowid alias, which hands out the next value itself).MySQL has no timestamp-with-zone; values are normalised to UTC. Text, JSON and blob columns cannot carry a literal default or be indexed without a prefix length, so those are skipped with a note.
Decimals wider than the target's maximum (38 on Oracle, Snowflake and Databricks) are clamped with a warning, and the dry run checks every value fits.
A CSV has no types, so they are inferred from the first 10,000 rows (override with
typeOverrides). Unquoted empty fields areNULL; quoted""is an empty string, and the writer preserves the difference.
What it does not do
No upsert and no change-data-capture.
appendonly adds rows that do not already exist; it never updates or deletes existing ones. A snapshot is one instant, not a stream; to catch up afterwards, migrate again withreplace.Views, triggers, sequences, routines, grants, check constraints, and partial / expression / full-text indexes are not migrated. They are listed in the plan and report.
Append rollback needs a key, and append verification is a row count (plus a full digest when the table started empty), not a digest of the merged table.
Snapshots are per run. A paused-and-resumed run reads different moments (marked
interrupted). Snowflake, Databricks and SQLite snapshots are opt-in; Snowflake and Databricks use Time Travel and need retention that covers the run.Bulk loading, Time Travel snapshots and the MySQL, Oracle, Snowflake and Databricks adapters as a whole have not been run against live servers (see the support matrix and the conformance suite).
Identity counters on append are only moved automatically on PostgreSQL.
Security notes
Credentials come from environment variables, are never returned by any tool, and are scrubbed from error messages and evidence.
sample_rows,profile_dataand dry-run examples redact sensitive-looking columns and values by default. Redaction is by column-name and value patterns, so treat it as a safety net, not a guarantee.Dry-run failures identify rows by primary key, which may itself be sensitive in some schemas.
The server never writes to a connection marked
readOnly, and it never writes to the source at all.run_migration,rollbackandfinalize_runare marked as writing (the last two as destructive) in MCP annotations, so clients can require human approval.Bulk loading writes temporary newline-delimited JSON files (mode 0600) to the OS temp directory and removes them after each batch; the same data is staged in your Snowflake user stage or Databricks volume and removed after the load.
Development
npm install
npm test # unit + SQLite/CSV end-to-end + MCP protocol tests (no services needed)
# live tests: PostgreSQL (create two empty databases first) and, optionally, the conformance suite for the others
createdb moveit_src && createdb moveit_dst
MOVEIT_TEST_PG=postgres://postgres@127.0.0.1:5432 npm run test:liveState lives in ./.moveit (MOVEIT_HOME to change): plans, run state and evidence logs. They contain connection names, never credentials.
License
MIT
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Move a database between providers, or convert MySQL to PostgreSQL, with integrity verified.
The all-in-one data stack for agents. Upload files, run SQL, evolve tables, and render charts.
Shared, versioned tables with units, links and SQL. Agents read what your data means.
Review your AI agent's database changes on your schema, with plain-language risk, before they run.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables coding agents to inspect Python applications, research source and target models, and produce reviewable migration plans with evidence gates and evaluation before production code changes.471Apache 2.0
- AlicenseAqualityBmaintenanceEnables safe database migration workflows by detecting frameworks, reviewing migration risk and drift, generating migration files with explicit approval, and providing rollback guidance—without ever applying migrations or executing rollbacks.712 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables developers and coding agents to submit structured database schema changes to a shared coordinator, compare them against accepted migration history and other active work, and receive reports on safe changes, stale bases, dependencies, and conflicts before migrations are finalized. It supports CI-controlled deployment coordination without exposing application database credentials.Apache 2.0
- AlicenseBqualityCmaintenanceEnables AI coding agents to perform deterministic database architecture audits and performance engineering for PostgreSQL and MySQL, covering migration analysis with schema health scoring, ESR-based composite index recommendations, N+1 query detection, EXPLAIN plan diagnosis, transaction and deadlock analysis, partitioning and archival design, and capacity or configuration tuning. It also generates phased Expand & Contract runbooks that turn blocking DDL into zero-downtime production migrations.8MIT