Skip to main content
Glama

mcp-wp-cli-terminus

An MCP (Model Context Protocol) server that lets Claude and other AI agents run WP-CLI against WordPress — over local Docker, Pantheon Terminus, or SSH — and byte-faithfully copy posts and post meta between environments with checksum verification.

Built for developers using Claude Code / Claude Desktop (or any MCP client) to operate WordPress sites — including Pantheon multidevs reached through Terminus — without hand-assembling fragile wp eval commands.

wp-cli · wordpress · terminus · pantheon · mcp · model-context-protocol · claude · claude-code · anthropic · wordpress-migration · devops


Why

Moving a WordPress post's body or custom fields between environments (e.g. pushing a block-based front page from local to a Pantheon multidev) is deceptively hard to do correctly:

  • wp post update --post_content truncates at newlines (wp-cli#2712).

  • Piping content over STDIN hangs on terminus remote:wp (terminus#1615).

  • Hand-pasting base64 into an agent prompt is lossy — a single flipped byte silently corrupts production.

  • Large payloads passed as one shell argument hit the Linux MAX_ARG_STRLEN (131072 bytes) limit and fail with E2BIG.

  • Post meta with serialized arrays / multiple values per key is easy to corrupt by re-serializing.

This server solves all of that: content is read, base64-encoded in code, delivered over a transport-safe path, and then re-read and checksum-compared to the source. A mismatch is reported, never silently trusted.

Related MCP server: wp-cli-mcp

Tools

Tool

What it does

wp_init_config

Guided setup: detects your Docker container, WordPress path, and Terminus site, then writes .serena/wp-cli.conf (asking you for anything it can't detect).

wp_cli

Run any WP-CLI command against a configured site — target: local (Docker) or target: production (Terminus or SSH, chosen by config). Destructive commands are guarded on production.

wp_sync_post

Sync a post's post_content onto the same post ID in another environment (updates an existing post; e.g. a multidev cloned from the same DB), with an md5 round-trip verification.

wp_sync_post_meta

Sync a post's complete meta (serialized arrays, multiple values per key, ACF repeaters) onto the same post ID in another environment, with a canonical checksum verification. All keys (full mirror) or an allow-list.

wp_sync_option

Sync one or more wp_options rows (site/plugin settings, and ACF options-page data incl. repeaters/nested groups) between environments — or between two subsites of one multisite — md5-verified. Select by exact option_names or a like prefix; multisite --url is resolved automatically per side from a bare subdomain. The options-table counterpart to wp_sync_post_meta.

wp_clone_post

Clone a post to another environment as a NEW post — the destination assigns its own ID (returned as new_id). Use when the post doesn't exist on the destination yet. Copies fields + all meta, verifies, and reports meta keys that hold ID references for manual remapping.

wp_block

Surgically edit one Gutenberg/ACF block of a post — list / get / insert / replace / update-attrs / remove / move — leaving every other block byte-identical. Uses WP core parse_blocks()/serialize_blocks() (never string surgery), with a re-parse md5 verification and the same production guard.

wp_create_post

Create a NEW post from scratch, with the body carried content-safe (base64 in code) — no hand-quoted wp post create, no throwaway PHP file. Give content (raw markup) or blocks (specs serialized server-side); returns new_id to build up with wp_block.

Sync vs. clone vs. block

  • Block (wp_block) is the single-block primitive: change, read, add, or reorder one block of a post without touching the rest. Reach for it instead of a whole-body wp_sync_post (which overwrites every block) or hand-written str_replace/eval surgery on post_content (which corrupts self-closing ACF blocks and inner blocks). Selectors: name:<blockName> (first of type), name:<blockName>#<N> (Nth, 0-based), anchor:<anchor>, index:<N>.

  • Sync (wp_sync_post, wp_sync_post_meta) updates an existing post that shares the same ID on both sides — the right tool when the environments were cloned from the same database (a Pantheon multidev, a staging copy). It fails if the destination ID doesn't exist.

  • Options (wp_sync_option) copies wp_options rows — data the post tools can't reach. Reach for it for settings / ACF options-page content (theme options, plugin config, an options-page repeater). For an ACF repeater, match both options_<field>% and the underscore-prefixed _options_<field>% field-reference rows; a like sync mirrors the pattern (surplus destination rows are removed) so a repeater shrinks correctly.

  • Clone (wp_clone_post) creates a new post on the destination from a source post; the destination assigns a fresh ID. Use it when the content is new to the destination (e.g. pushing a locally-authored post/alert up to a multidev). Meta values that look like ID references (_thumbnail_id, ACF relationship/image fields) are copied verbatim and reported — never silently remapped across databases.

Correctness guarantees

  • Never routes content through the model's text. Payloads are read into the server and base64-encoded in code.

  • Checksum-verified. Every sync/clone re-reads the destination and compares it to the transferred source; verified: false + an error on any mismatch.

  • Transport-agnostic. The same logic runs over local Docker, Pantheon Terminus, and WP-CLI --ssh.

  • Large payloads. Docker/SSH deliver PHP over STDIN (wp eval-file -, exempt from the argv size limit); Terminus uses a size-guarded argv path and fails loud rather than emitting a raw E2BIG.

  • Meta fidelity. Values are round-tripped so WordPress's own maybe_serialize() reproduces the exact stored meta_value — arrays stay arrays, and strings that merely look serialized stay strings.

  • Production guard. Writes to a production destination require confirm: true when PROD_GUARD is enabled.

  • MCP-client tolerant. Some MCP clients (e.g. Claude Code) serialize object/array/number/boolean tool arguments as JSON strings before sending them (claude-code#5504, #24599). The tools coerce such stringified arguments back to their native types, so a block/blocks/data/fields object delivered as a string still works — while a genuine raw-markup string is never mis-parsed.

Install & run

The server is pure Python (stdlib only, zero dependencies).

With uvx (recommended — no install)

// Claude Desktop / Claude Code MCP config
{
  "mcpServers": {
    "wp-cli": {
      "command": "uvx",
      "args": ["mcp-wp-cli-terminus"]
    }
  }
}

With pip

pip install mcp-wp-cli-terminus
{
  "mcpServers": {
    "wp-cli": { "command": "mcp-wp-cli-terminus" }
  }
}

From source

git clone https://github.com/EarthmanWeb/mcp-wp-cli-terminus
cd mcp-wp-cli-terminus
python -m wp_cli_mcp   # PYTHONPATH=src, or `pip install -e .`

Configure

Guided setup with wp_init_config (recommended)

The easiest way to create the config is to ask your MCP client to run the wp_init_config tool. It works in two phases:

  1. Detect — called with no arguments, it probes the environment (running Docker containers, the WordPress path inside them, and any authenticated Pantheon Terminus site) and reports what it found plus a list of anything it couldn't determine.

  2. Write — the agent asks you for whatever was missing, then calls it again with write=true to save <project-root>/.serena/wp-cli.conf.

Just tell your agent: "set up the wp-cli config for this project" — it will call wp_init_config, fill in what it can, ask you for the rest, and write the file (it won't overwrite an existing config unless you say so).

Manual setup

The server reads <project-root>/.serena/wp-cli.conf at runtime (set CLAUDE_PROJECT_DIR to point at your project). Copy wp-cli.conf.example and edit:

DEFAULT_SITE=example-site
PROD_GUARD=true

[site:example-site]
LOCAL_CONTAINER=my-container       # docker container running WP-CLI
LOCAL_PATH=/var/www/html           # WordPress path inside the container
TERMINUS_SITE=example              # Pantheon site — production routes over Terminus
TERMINUS_ENV=dev                   # default env (override per call)
# — or, for a non-Pantheon remote, omit TERMINUS_* and set:
# REMOTE_SSH=deploy@example.com:22/var/www/html
  • Production transport is chosen by config: TERMINUS_SITE → terminus remote:wp; otherwise REMOTE_SSH → WP-CLI --ssh.

  • Multi-site: add more [site:NAME] sections and pass site per call.

Never commit .serena/wp-cli.conf — it may contain hostnames/SSH strings. The shipped .gitignore excludes it.

Usage examples

// Run a WP-CLI command locally
{ "tool": "wp_cli", "args": "plugin list --status=active --format=json" }

// Run against production (Terminus or SSH per config)
{ "tool": "wp_cli", "args": "option get siteurl", "target": "production" }

// SYNC a front page's block markup onto the SAME post ID on production, verified
{ "tool": "wp_sync_post", "post_id": 42, "from": "local", "to": "production", "confirm": true }

// SYNC ALL meta for a post (full mirror) onto the same ID, verified
{ "tool": "wp_sync_post_meta", "post_id": 42, "from": "local", "to": "production", "confirm": true }

// SYNC only specific meta keys
{ "tool": "wp_sync_post_meta", "post_id": 42, "from": "local", "to": "production",
  "keys": ["_thumbnail_id", "my_field"], "confirm": true }

// CLONE a locally-authored post to production as a NEW post (returns new_id)
{ "tool": "wp_clone_post", "post_id": 268529, "from": "local", "to": "production", "confirm": true }

// Clone but force the new post to draft
{ "tool": "wp_clone_post", "post_id": 268529, "from": "local", "to": "production",
  "overrides": { "post_status": "draft" }, "confirm": true }

// LIST a post's top-level blocks (index, blockName, anchor, ACF data keys)
{ "tool": "wp_block", "op": "list", "post_id": 268483 }

// GET one block's parsed attrs + exact markup
{ "tool": "wp_block", "op": "get", "post_id": 268483, "selector": "name:acf/sps-hero-slideshow-block" }

// INSERT a feature-cards block just before the celebrations block (ACF data form)
{ "tool": "wp_block", "op": "insert", "post_id": 268483,
  "position": "before:name:acf/sps-celebrations-block",
  "block": { "name": "acf/sps-feature-cards-block", "data": { "cards": [268486, 268388, 268364] } } }

// UPDATE-ATTRS: switch the celebrations block to tag mode (merges into attrs.data)
{ "tool": "wp_block", "op": "update-attrs", "post_id": 268483,
  "selector": "name:acf/sps-celebrations-block",
  "data": { "source": "tag", "tag": 436, "posts_per_page": 10 } }

// REPLACE the 2nd paragraph with raw markup; MOVE / REMOVE by selector
{ "tool": "wp_block", "op": "replace", "post_id": 42, "selector": "name:core/paragraph#1",
  "block": "<!-- wp:paragraph --><p>New copy</p><!-- /wp:paragraph -->" }
{ "tool": "wp_block", "op": "move", "post_id": 42, "selector": "anchor:cta", "position": "first" }
{ "tool": "wp_block", "op": "remove", "post_id": 42, "selector": "index:3" }

// PREVIEW a change without writing (returns the intended new_content_b64 + new_md5)
{ "tool": "wp_block", "op": "remove", "post_id": 42, "selector": "index:3", "preview": true }

// SYNC a post's body to production but PRESERVE the destination's hand-built slideshow
{ "tool": "wp_sync_post", "post_id": 268483, "from": "local", "to": "production",
  "except_blocks": ["acf/sps-hero-slideshow-block"], "confirm": true }

// CREATE a new page from block specs in ONE call (no file, no arg-quoting) -> new_id
{ "tool": "wp_create_post", "title": "Landing", "post_type": "page", "status": "draft",
  "blocks": [
    { "name": "acf/sps-feature-cards-block", "data": { "cards": [268486, 268388] } },
    "<!-- wp:paragraph --><p>Intro copy.</p><!-- /wp:paragraph -->"
  ] }

// CREATE from raw markup, then keep building with wp_block on the returned new_id
{ "tool": "wp_create_post", "title": "Draft", "content": "<!-- wp:heading --><h2>Hi</h2><!-- /wp:heading -->" }
{ "tool": "wp_block", "op": "insert", "post_id": /* new_id */ 0, "position": "last",
  "block": { "name": "acf/sps-celebrations-block", "data": { "source": "tag", "tag": 436 } } }

// SYNC named options (site settings) local -> production
{ "tool": "wp_sync_option", "option_names": ["blogname", "blogdescription"],
  "from": "local", "to": "production", "confirm": true }

// SYNC a whole ACF options-page repeater — BOTH prefixes (values + field refs)
{ "tool": "wp_sync_option", "like": "options_page_callouts%",  "from": "local", "to": "production", "confirm": true }
{ "tool": "wp_sync_option", "like": "_options_page_callouts%", "from": "local", "to": "production", "confirm": true }

// COPY options between two SUBSITES of one multisite (same env; bare subdomains)
{ "tool": "wp_sync_option", "like": "options_hero%",
  "from": "local", "to": "local", "from_subdomain": "site-a", "to_subdomain": "site-b" }

wp_sync_* return verified: true/false with src_md5 / dst_md5, the delivery mode, and per-side transport. wp_sync_option adds options_written, the selector used, and (for a subsite copy) the resolved subsite. A bare wp_cli call that a specialized tool would do better (e.g. option get/update, post get --field=post_content, raw eval) also returns a suggestion pointing you at it — the command still runs. wp_clone_post returns new_id, verified, content_verified, meta_verified, and id_reference_keys (meta keys to review). wp_block read ops return the block list / one block's attrs+markup; write ops return wrote, verified (re-parse md5 round-trip), before_count/after_count, and target_index — and, when blocked by the production guard or preview: true, the intended new_content_b64 + new_md5 instead of writing. A block-filtered wp_sync_post (only_blocks/except_blocks) returns blocks_carried, intended_md5/reread_md5, and the filter applied.

Debug logging

Failures (non-zero WP-CLI exits) are appended to a log in your system temp dir — failures only, successes are never logged:

  • Location: ${TMPDIR}/wp-cli-mcp/failures.log (override with WP_CLI_MCP_LOG_DIR).

  • Disable entirely with WP_CLI_MCP_LOG=0.

  • SSH connection strings are redacted in the log.

Requirements

  • Python 3.8+

  • WP-CLI reachable via one of: a local Docker container (docker exec), Pantheon Terminus on the host, or a host WP-CLI with --ssh.

Tests

python -m unittest discover -s tests -v

187 stdlib-only unit tests (split by area under tests/) cover config parsing, transport selection (local/Terminus/SSH), the argv size guard + --url extra-token passthrough, newline handling, PHP-key safety, id-reference detection, option/ACF-repeater copy with pattern mirroring, multisite subdomain→URL resolution, the wp_cli discovery hints, and the full sync/clone/verify orchestration via an injectable command-runner seam (no real WP-CLI invoked).

Releasing

Releases publish to PyPI automatically via GitHub Actions (.github/workflows/publish.yml) using PyPI Trusted Publishing — no API token is stored in the repo. The workflow builds, runs the tests, and uploads on every published GitHub Release.

One-time setup (per project, on PyPI):

  1. On PyPI, open the project → Publishing → Add a new publisher → GitHub, with:

    • Owner: EarthmanWeb · Repository: mcp-wp-cli-terminus

    • Workflow name: publish.yml · Environment: pypi

  2. (Optional) In GitHub repo Settings → Environments, create an environment named pypi to gate/approve publishes.

Cut a release (this triggers the publish):

# 1. Bump the version in pyproject.toml (e.g. 0.1.0 -> 0.1.1), commit, push.
# 2. Tag + create the GitHub Release — the workflow does the rest:
gh release create v0.1.1 --title "v0.1.1" --notes "What changed"

The action then builds, tests, and publishes mcp-wp-cli-terminus to PyPI. Within ~a minute uvx mcp-wp-cli-terminus (and the SWE plugin launcher) pick up the new version. You can also run it manually from the Actions tab (workflow_dispatch).

First release was published manually with uv build && uv publish; subsequent releases use the workflow above.

License

MIT

Available Tools

8 tools
wp_blockA

Surgically operate on ONE Gutenberg/ACF block of a single post without disturbing the rest of the body. This is the block-level primitive that whole-body wp_sync_post lacks: insert, replace, read, update-attrs, remove, or move exactly one block while every other block stays byte-identical. Use it INSTEAD of hand-written str_replace/eval surgery on post_content (which repeatedly corrupted markup: base64 transcription bugs, self-closing /--> terminators, multisite --url errors). All parsing/serialization runs in WordPress core (parse_blocks / serialize_blocks) via one PHP eval — never string surgery — so self-closing ACF blocks, inner blocks, and freeform HTML round-trip faithfully. ops: 'list' (ordered block summary: index, blockName, anchor, attr/ACF-data keys), 'get' (one block's parsed attrs + exact markup), 'insert' (add a block at a position), 'replace' (swap the matched block), 'update-attrs' (merge ACF field values into the matched block's attrs.data, preserving existing _field key pointers), 'remove' (delete the matched block), 'move' (reorder). Selectors: 'name:' (first of type), 'name:#' (Nth, 0-based), 'anchor:', or 'index:' (Nth top-level block). The block arg is either raw block markup OR an object {name, data} where data merges into the ACF attrs.data. Content never passes through the caller's text: post_content is read on the server, payloads are carried base64 in code, and every write RE-PARSES the saved post and reports verified=true only if the round-trip md5 matches. Read ops (list/get) are never guarded; write ops on target='production' require confirm=true when PROD_GUARD is enabled (a preview of the change is returned instead when blocked). Pass preview=true to get the intended change without writing. Transport (local Docker / Terminus / SSH) is resolved from wp-cli.conf exactly like wp_cli.

Inputs: op (required), post_id (required), env, data, +8 more.

wp_cliA

Run a WP-CLI command against a configured WordPress site's local Docker container (default) or its remote production environment. Configuration is read from /.serena/wp-cli.conf, which may define one or MANY sites. Pass the WP-CLI command WITHOUT a leading 'wp' (e.g. args='plugin list --status=active'). When the conf defines multiple sites, pass 'site' = the site's top-level folder name; omit it to use the configured DEFAULT_SITE or the sole site. The production TRANSPORT is chosen by the site's conf: TERMINUS_SITE routes over terminus remote:wp (run on the host, Pantheon/Terminus); otherwise REMOTE_SSH routes over WP-CLI --ssh. For Terminus sites the environment is TERMINUS_ENV (conf default) unless overridden per-call with the 'env' arg. On production, destructive commands (db reset/import, post/user delete, search-replace without --dry-run, plugin/theme delete, etc.) are blocked unless confirm=true and the guard is enabled.

Inputs: args (required), env, site, target, +1 more.

wp_clone_postA

COPY a source post to another environment as a NEW post — the destination assigns its own ID (returned as new_id). Use this (NOT wp_sync_post) when the post does not exist on the destination yet, e.g. pushing a locally-authored alert/notification up to a Pantheon multidev. It reads the source post's fields (title, content, excerpt, status, type, dates, parent, etc.) plus ALL meta, creates the post on the destination via wp_insert_post, writes the meta verbatim, then verifies (content md5 + meta digest). Meta values that look like ID references (e.g. _thumbnail_id, ACF relationship/image fields) are copied RAW and REPORTED in id_reference_keys for manual remapping — the tool never silently remaps IDs across databases. Pass overrides to set post fields on the new post (e.g. {"post_status":"draft"}). Content/meta never pass through the caller's text. Creating on a production destination requires confirm=true when the PROD_GUARD is enabled.

Inputs: to (required), from (required), post_id (required), site, +4 more.

wp_create_postA

Create a NEW post from scratch on a configured site, with the body carried content-safe (base64 in code) — so you never have to hand-quote multi-line block markup into a wp post create command or write a throwaway PHP file into the container just to author a post. Returns the new post's ID (new_id), which you then build up compositionally with wp_block (insert / update-attrs). Supply the body EITHER as content (a raw post_content markup string) OR as blocks (an array of block specs — raw markup strings and/or {name, data} objects — serialized server-side with serialize_blocks() so self-closing ACF blocks and inner blocks stay byte-faithful); omit both for an empty post. fields sets any additional post columns (post_excerpt, post_author, post_parent, menu_order, post_name, ...). The new post is re-read and its content md5 is compared to the intended body (verified). Content never passes through the caller's text. Creating on target='production' requires confirm=true when PROD_GUARD is enabled. To COPY an existing post across environments use wp_clone_post instead; this tool authors a brand-new post from given values.

Inputs: env, site, title, blocks, +6 more.

wp_init_configA

Create the server's configuration file (/.serena/wp-cli.conf) for a WordPress project. Call it FIRST when no config exists yet. Two phases: (1) DETECT — call with no arguments (write=false); it probes the environment (running Docker containers, the WordPress path inside them, and any authenticated Pantheon Terminus site) and returns what it found plus a missing list of values it could NOT determine. Ask the user for those, then (2) WRITE — call again with write=true and a complete site object to save the config. It refuses to overwrite an existing config unless overwrite=true.

Inputs: site, write, overwrite, prod_guard, +1 more.

wp_sync_optionA

SYNC one or more wp_options rows (site/plugin settings, and ACF OPTIONS-PAGE data) from one environment to another, with a mandatory md5 round-trip verification. This is the options-table counterpart to wp_sync_post_meta: use it for settings that live in wp_options, which the post tools cannot reach (they only handle a post's content and post meta). Select rows two ways: option_names=[...] for exact names, OR like='prefix%' for a SQL LIKE pattern. NOTE for ACF repeaters/nested groups: a repeater is stored as a parent count row (options_), numbered sub-rows (options_), AND a parallel set of underscore-prefixed field-key references (options...). To copy one completely, match BOTH prefixes — run once with like='options_%' and once with like='options%', or enumerate the rows in option_names. Matching only 'options_%' omits the field references and ACF will not render the group. Each stored option_value is PHP serialize()'d on the source, base64-encoded IN CODE, and rebuilt verbatim on the destination (delete-then-add, values re-interpreted only via maybe_unserialize so arrays/objects round-trip byte-for-byte). The destination is re-read and md5-compared; a mismatch returns verified=false, never silently trusted. Multisite: pass from_subdomain / to_subdomain as a BARE subdomain to target a subsite per side; URL handling is AUTOMATIC PER-SIDE — each side is checked against its own environment and the correct --url is resolved from that environment's live blog table (a given subdomain → that blog; none → the main 'www' site), and single-site environments get no --url. You never construct a URL, and it stays correct even when local and production use different domains. Content never passes through the caller's text. Transport (local Docker vs Terminus vs SSH) is resolved per side from wp-cli.conf exactly like wp_cli. Writing to a production destination requires confirm=true when the PROD_GUARD is enabled.

Inputs: to (required), from (required), like, site, +6 more.

wp_sync_postA

SYNC a single post's post_content onto the SAME post ID in another environment, with a mandatory md5 round-trip verification. This UPDATES an existing destination post that shares the source's ID (e.g. a multidev cloned from the same DB) — it does NOT create a post; if the destination ID is missing it fails. To copy a post that does NOT yet exist on the destination (creating a new post with a destination-assigned ID), use wp_clone_post instead. Use this instead of hand-assembling post update/eval calls to push post body content between local and production (e.g. update a front-page's block markup on a Pantheon multidev). The content NEVER passes through the caller's text: it is read from the source into the server, base64-encoded IN CODE, applied on the destination through a transport-safe PHP eval (STDIN eval-file for Docker/SSH so arbitrarily large posts work; size-guarded argv for Terminus), then re-read and md5-compared to the source. If the hashes differ, the tool reports verified=false and returns an error rather than leaving the destination silently unverified. Transport for each side (local Docker vs Terminus vs SSH) is resolved from wp-cli.conf exactly like wp_cli. Writing to a production destination requires confirm=true when the PROD_GUARD is enabled.

Inputs: to (required), from (required), post_id (required), site, +5 more.

wp_sync_post_metaA

SYNC a post's COMPLETE meta (all custom fields) onto the SAME post ID in another environment, with a mandatory checksum round-trip verification. This UPDATES an existing destination post that shares the source's ID; it does NOT create a post (use wp_clone_post to copy a post that doesn't exist on the destination yet). Preserves EVERYTHING: multiple values per key, serialized arrays/objects (e.g. ACF repeaters), and exact scalar representations — the raw stored meta map is PHP serialize()'d on the source, base64-encoded IN CODE, and rebuilt on the destination with each stored value written back verbatim (no re-interpretation). It then re-reads the destination and compares a canonical PHP-native digest to the transferred payload; a mismatch is returned as verified=false, never silently trusted. By default ALL meta keys are synced and any destination keys not present on the source are removed (full mirror). Pass keys=[...] to sync only specific keys (destination keys outside that list are left untouched). Content never passes through the caller's text. Transport (local Docker vs Terminus vs SSH) is resolved per side from wp-cli.conf exactly like wp_cli. Writing to a production destination requires confirm=true when the PROD_GUARD is enabled.

Inputs: to (required), from (required), post_id (required), keys, +4 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.6/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct operation: config bootstrap, generic WP-CLI execution, post creation from scratch, environment-to-environment sync of content/meta/options, block-level editing, and full post cloning. The descriptions explicitly cross-reference similar tools (e.g. wp_clone_post vs wp_sync_post) so an agent can reliably distinguish them.

    Naming Consistency4/5

    All tools share a consistent wp_ prefix and snake_case style, and most follow a clear verb_noun pattern like wp_create_post, wp_sync_post, and wp_sync_option. wp_cli and wp_block are noun-style names rather than explicit verb_noun, which is a minor deviation but still predictable.

    Tool Count5/5

    Eight tools is a well-scoped size for a WordPress/Terminus workflow server; each tool covers a distinct high-value operation without redundancy. The count sits comfortably in the ideal range for a focused server.

    Completeness5/5

    The set covers the full workflow of authoring and moving content across environments: config initialization, creation, cloning, content/meta/options sync, and surgical block edits. The included generic wp_cli runner also fills any remaining low-level WP-CLI gaps, so agents are not left with dead ends.

    Maintenance

    ActivityNo data
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers