A read-only MCP server for analyzing, comparing, and planning WordPress migrations.
Migrate MCP gives AI coding agents structured migration-analysis tools through the Model Context Protocol (MCP). It helps you inspect installations, identify risks, compare source and target sites, and prepare a migration plan without modifying either WordPress installation.
Status: Early-stage / pre-release. The current release focuses on read-only migration analysis and planning; tool interfaces and planning models may change.
Traditional migration tools are generally good at moving data. For complex WordPress migrations, the difficult work often comes before and after that move:
- Understanding what exists on each site.
- Identifying differences between source and target.
- Finding conflicts and migration risks.
- Deciding what needs manual review.
- Defining how to validate the migration.
Migrate MCP focuses on these analysis and planning problems so developers and agents can make informed decisions before choosing how to execute a migration.
- Site/environment inspection: WordPress, PHP, and WP-CLI versions, URLs, and multisite status.
- Database inspection: Table inventory, prefixes, charset/collation, and estimated size.
- Content type discovery: Registered post types, configuration, and stored post counts.
- Taxonomy discovery: Registered taxonomies, associated types, and stored term counts.
- Custom table analysis: Non-core tables, size estimates, scope, and ownership/reference hints.
- User migration-risk analysis: User totals, ID ranges, roles, memberships, and duplicate indicators.
- Media analysis: Attachment summaries, metadata/parent anomalies, and upload configuration.
- URL/environment-reference analysis: Source-host dependencies, external hosts, and development/path indicators.
- Source/target comparison: Configuration differences and categorized migration findings.
- Migration planning: Ordered phases, blockers, unresolved decisions, and validation requirements.
- Node.js 22.13+ within 22.x, or Node.js 24+, and npm.
- Git to clone the repository.
- An MCP client that supports local stdio servers.
- To analyze sites: PHP, WP-CLI (
wp) on the server process'sPATH, and access to trusted WordPress installations and their databases. Media and URL analysis require 64-bit PHP.
WordPress and WP-CLI are not needed just to start the server, discover tools, or run the mocked tests. Client-specific integrations have not been validated.
Migrate MCP currently runs from a local checkout. The package is marked
"private": true and is not published for npm installation.
git clone https://github.com/abhishekfdd/migrate-mcp.git
cd migrate-mcp
npm ci
npm run build
npm start --silentThe server waits for MCP messages on stdin and writes protocol responses to stdout. It does not print a startup banner or open an HTTP port. Diagnostics go to stderr. Stop a manual session with Ctrl+C.
For clients using an mcpServers configuration, use:
{
"mcpServers": {
"migrate-mcp": {
"command": "node",
"args": ["/absolute/path/to/migrate-mcp/dist/index.js"]
}
}
}Replace the example path with your checkout path. The client launches the server and must be able to find a supported Node.js version. Launching Node directly keeps npm's script banners out of the protocol stream. Rebuild after changing TypeScript source files.
The server identifies itself as migrate-mcp, using the version in package.json.
WordPress installation paths are tool arguments, not the server's working directory.
After connecting, try migration_site_info with:
{
"path": "/absolute/path/to/wordpress"
}With both installation paths supplied to your MCP client, a conversation might look like this. Findings depend on the sites being analyzed.
User:
Compare these two WordPress installations and identify migration risks.
Migrate MCP, through the agent:
- reports content-type and taxonomy differences
- reports custom tables requiring review
- identifies overlapping user ID ranges
- summarizes media metadata and parent anomalies
- reports environment-specific URL references
User:
Generate a migration plan.
Migrate MCP, through the agent:
- produces ordered phases and preparation steps
- reports blockers
- identifies unresolved decisions
- provides post-migration validation requirements
The plan is a reviewable artifact; it does not execute the migration or validation.
| Tool | Purpose |
|---|---|
migration_site_info |
Inspect WordPress/runtime versions, URLs, table prefix, and multisite status. |
migration_database_info |
Summarize database metadata, table counts, and estimated size. |
migration_list_content_types |
List registered post types, configuration, and post counts. |
migration_list_taxonomies |
List registered taxonomies, configuration, and term counts. |
migration_list_custom_tables |
List non-core tables with metadata estimates and scope/reference hints. |
migration_analyze_users |
Summarize users, roles, memberships, ID ranges, and usermeta risks. |
migration_analyze_media |
Summarize attachments, metadata/parent anomalies, and upload configuration. |
migration_analyze_urls |
Summarize URL dependencies, external hosts, and environment references. |
migration_compare_sites |
Compare source and target installations and categorize migration risks. |
migration_generate_plan |
Analyze both sites and produce phases, decisions, and validation requirements. |
The first eight tools accept only path, an absolute WordPress installation path
on the server host. For example, this is a complete MCP tools/call request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "migration_analyze_urls",
"arguments": { "path": "/absolute/path/to/wordpress" }
}
}Comparison and planning accept sourcePath and targetPath. Use this params
object in a tools/call request:
{
"name": "migration_compare_sites",
"arguments": {
"sourcePath": "/absolute/path/to/source-wordpress",
"targetPath": "/absolute/path/to/target-wordpress"
}
}Use migration_generate_plan with the same arguments to run comparison and then
generate a plan. Blank/relative paths, NUL bytes, and additional properties are
rejected. Two paths that normalize to the same absolute path return
SAME_INSTALLATION; this lexical check does not detect symlink aliases or
separate directories sharing a database.
Successful calls return structuredContent and matching JSON in a text content
block. Content types, taxonomies, and custom tables use contentTypes,
taxonomies, and customTables array wrappers respectively; other tools return
their result object directly. Optional fields remain absent when unavailable.
Known execution failures return isError: true with a code and sanitized guidance
(such as EXECUTABLE_NOT_FOUND or TIMEOUT), without raw subprocess output or
exception details. Comparison/planning failures return no partial result; inspect
both sites individually if the failing side is unclear. Invalid results are
reported as errors, including INVALID_ANALYZER_RESPONSE,
INVALID_COMPARISON_RESPONSE, or INVALID_PLAN_RESPONSE; planner failures use
PLANNING_FAILED.
The current implementation does not:
- Modify database records or files.
- Perform search-replace.
- Migrate content or remap IDs.
- Copy media or create database tables.
- Execute migration plans or validation steps.
WordPress runtime inspection uses fixed internal PHP through WP-CLI where needed. MCP tools expose neither caller-provided arbitrary PHP nor arbitrary SQL execution. WP-CLI runs through an adapter using direct process execution without shell parsing; there are no mutation commands or direct MySQL connections.
Use trusted installations. WordPress bootstrap can execute installation code, including must-use plugins and drop-ins even when plugins/themes are skipped. Loading that code can have its own side effects: read-only inspection is not an isolation boundary for untrusted WordPress code.
User analysis returns summaries, not user records, emails, logins, passwords, or auth-related meta values. URL analysis excludes secret-bearing option/meta keys, transients, and sensitive JSON fields, and returns no raw content or discovered full URLs. This filtering is conservative, not exhaustive secret classification. Configured URL paths, hostnames, database metadata, and upload configuration are intentional inventory data; configured URLs in URL analysis omit userinfo, query strings, and fragments.
src/
├── mcp/ MCP transport, tool registration, validation, and response handling
├── wordpress/ Safe WordPress/WP-CLI integration
├── migration/ Migration analyzers, comparison engine, and planning engine
└── utils/ Shared low-level utilities
MCP handlers are thin: they validate inputs and responses and delegate migration logic to domain services. The server uses the official TypeScript MCP SDK v2 server package over stdio. The WP-CLI adapter centralizes fixed inspection commands and process limits; services can be tested with injected adapters and mock runners.
- WP-CLI and runtime inspection: Fixed internal PHP reads effective WordPress
configuration and registrations. Site/database metadata and user analysis skip
plugins/themes; content types, taxonomies, media, and URLs load them for runtime
registrations or upload filters. CLI packages are skipped throughout. Upload
configuration uses
wp_upload_dir(null, false)to avoid directory creation. - Read-only database queries: Database/custom-table inspection uses core table
mappings and
information_schemametadata. Content, taxonomy, user, and media summaries use aggregates; URL analysis scans selected core fields. MCP analysis returns summaries without loading raw user, attachment, or URL-content rows into the Node process. - Bounded analysis: Active-site user roles are decoded in batches of 500 with object deserialization disabled and 64 KiB/16-level limits. URL scans use 100-value primary-key pages bounded by initial maximum IDs. Internal opt-in media file checks use pages of at most 100 attachments.
- Deterministic output: Inventories, host summaries, and findings use stable
ordering. Comparison reuses all eight inspection services once per side, one
source/target service pair at a time. Unavailable comparison metadata is marked
unknown, rather than treated as zero or a mismatch. - Planning: The MCP planning tool runs comparison, then passes its complete
result to the pure, synchronous
MigrationPlanner.generatePlan({ comparison }). The planner performs no I/O and produces deterministic phases, dependencies, issues, unresolved decisions, manual reviews, and validation requirements.
Plans report kind: "migration-plan" and executionPerformed: false. They contain
no executable commands, SQL, calculated ID mappings, or completed steps;
automated describes potential future automation only. Preflight and validation
are always present; other phases depend on relevant findings/data, and each phase
conservatively depends on all generated earlier phases.
Readiness is blocked for blocking findings, ready_with_warnings for significant
findings, unresolved decisions, or medium/high-risk manual reviews, and ready
otherwise. Informational issues count toward warningCount but affect readiness
only when they imply unresolved work. Readiness describes the available evidence,
not execution authorization or a guarantee of safety.
Analysis reflects the WP-CLI runtime. PHP may differ from the web server, and registrations conditional on web/admin/REST requests or WP-CLI configuration may not appear. Runtime labels follow the locale; implicit REST bases are not inferred. User analysis uses persisted roles, so dynamic plugin roles/authorization filters may differ.
Most analyzers cover only the active site selected by WP-CLI; media and URL scans do not automatically traverse a multisite network. User totals and ID bounds cover the shared users table, while roles cover the active site. Memberships are stored capability-key evidence, not effective access or super-admin checks. Database inventories cover visible base tables across the database, potentially including other installations.
Exact aggregates and URL scans can be expensive even with bounded memory. Metadata work scales with table/site counts. Each subprocess has a default 30-second timeout and a 1 MiB output limit per stream, not a whole-analysis budget. Internal file checks add one WP-CLI bootstrap per page and filesystem operations per path component, which can be costly on slow storage.
Reads and source/target collection are not transactional snapshots. Concurrent edits can cause inconsistent observations, changed-count warnings, or validation failures; same-count edits may be undetectable. Use stable installations and refresh evidence before acting on a plan.
- Post counts include all statuses (including drafts, trash, and auto-drafts) for registered types. Taxonomy counts include unused stored terms, not post assignments or referential-integrity checks. Unregistered types/taxonomies with leftover rows are omitted. Runtime capability normalization can lose the original plural capability type; custom archive slugs are preserved.
- User role counts can overlap. Duplicate email/login counts measure extra
accounts per equal-value group, including blanks, using database collation.
maxUserIdis the highest assigned ID, not an auto-increment forecast. Role parsing uses the lowest capability meta ID; malformed values and unexpected membership keys produce warnings. Unavailable optional risk counts remain unknown. Registered role keys count even when their stored value is false, matching WordPress. Usermeta summaries include orphaned rows and users with over 1,000 meta rows, without returning their values. - Custom tables are visible base tables absent from the installed core mapping;
views are excluded. Core discovery accounts for registered blogs, network
tables, and custom user-table overrides. Legacy/deleted-blog tables and other
installations can appear as custom. Owner names and
referenceHintsare naming hints, not verified plugin ownership or foreign keys. - Custom-table contents are not scanned, schemas are not compared, and row/size
statistics are metadata estimates. Prefix-based scope can be ambiguous:
base-prefixed multisite tables may be
network-or-main-site, and unknown numeric prefixes remainunknown. Logical names remove recognized prefixes; active-site logical names support matching across source/target prefixes, but ambiguous matches are not paired and other blogs are not equated by blog ID.
The MCP media tool accepts only path and uses summary mode. It does not accept
checkLocalFiles; checkedLocalFiles and missingLocalFiles are omitted with
LOCAL_FILES_NOT_CHECKED. Comparison and planning inherit this limit.
Attachment summaries cover all stored statuses. unattachedAttachments means
post_parent = 0, not broken media; orphanedAttachments means a nonzero parent
has no existing post. Missing metadata means absent/empty data, not invalid
serialization, and some formats legitimately lack it. The lowest meta ID supplies
the primary attached-file value when duplicates exist. Generated sizes,
thumbnails, original-image metadata, and dangling postmeta are not inspected.
External-reference indicators do not verify remote availability or identify every
offload plugin/CDN; missing local files alone do not prove offloading. No remote
requests are made.
The internal domain API supports analyze({ checkLocalFiles: true }) for filesystem
metadata checks only. Paths must remain inside the configured upload base;
traversal, stream wrappers, symlinks below that base, and references over 1,024
bytes are rejected. The base itself may be a configured symlink. No file contents,
hashes, or recursive directory scans are used. Permission/storage/path problems
produce warnings instead of false missing-file counts; wholly inconclusive checks
omit missingLocalFiles. These checks assume a stable, trusted filesystem and
are not a sandbox against concurrent symlink changes.
The scan covers the active site's posts.post_content, posts.post_excerpt,
postmeta.meta_value, options.option_value, termmeta.meta_value,
comments.comment_content, and commentmeta.meta_value. It excludes usermeta,
GUIDs, comment author identities, network options, custom tables, and other sites.
totalReferences counts HTTP(S) and protocol-relative occurrences, including
repeats, not unique URLs or matching rows. Host maps lowercase names, strip
trailing dots, and group independently of ports/protocols. Source-host counts
match site/home hostnames exactly; site/home/upload URL counts also consider
normalized ports and path boundaries, ignoring scheme, and may overlap. External
hosts are not inherently problems; observed www variants and CDN/media/path
indicators are hints, not verified ownership or dependencies.
Serialized-looking values are scanned as text, never unserialized or rewritten.
JSON string values are scanned with a depth limit of 32; malformed JSON falls back
to text. Values over 64 KiB, containing NUL bytes, or invalid UTF-8 are skipped with
counters/warnings. Host maps retain at most 1,000 encountered hosts; omitted
occurrences remain in totals and unreportedHostReferences.
Only ASCII/punycode DNS names and IP literals are recognized. Bare hostnames,
encoded/compressed content, deeply nested JSON, and uncommon filesystem paths may
be missed. Filtering can exclude useful data. A target host marked not-observed
is absent from the bounded scan, not proven absent from the database. Results are
observations within this scope, not a complete reference audit or replacement plan.
Comparison reports differences and risk indicators, not individual account
matches, file comparisons, or migration mappings. Overlapping user ID ranges do
not prove individual collisions; equal versions do not certify compatibility.
Count differences describe volume. Single-site/multisite mismatch is blocking
for direct migration; missing source registrations, missing active-site custom
tables, and user ID-range overlap are high risks. info/warning findings appear
in warnings, and high/blocking findings in conflicts; warningCount includes
informational findings. Any service failure stops the whole comparison.
Plans inherit all analyzer scope and scan limits. They cannot infer schemas, verified ownership, account matches, or storage permissions, and do not verify backups. Source-data scope, retained target data, external hosting, and reference policies remain human decisions. Validation requirements must account for those decisions and estimated counts; every step requires review and confirmation before any separately implemented execution workflow.
npm ci
npm test
npm run build
npm run lint
npm run typecheck
npm run format:check| Command | Purpose |
|---|---|
npm test |
Run tests once. |
npm run test:watch |
Run tests in watch mode. |
npm run build |
Compile src/ to dist/ with source maps. |
npm run lint |
Lint JavaScript and TypeScript; fail on warnings. |
npm run typecheck |
Check source, tests, and Vitest configuration without emitting files. |
npm run format |
Format project files with Prettier. |
npm run format:check |
Check formatting without modifying files. |
The project uses native ES modules and strict TypeScript. Tests and tooling
configuration are excluded from the build. Adapter/domain tests use mocks and
need no WordPress installation, database, or internet access. URL inspection also
has PHP runtime tests with mocked WordPress functions and database responses;
these run when php is on PATH and are skipped otherwise. Install PHP to exercise
the extraction code as well as its TypeScript boundaries.
Keep changes focused and run the development checks above before submitting a pull request. Update documentation when tool inputs, outputs, or limits change.
MIT.
WordPress is a registered trademark of the WordPress Foundation.
Migrate MCP is an independent open-source project and is not affiliated with, endorsed by, or sponsored by the WordPress Foundation or the WordPress project.