Metadata-Version: 2.4
Name: mcp-wp-cli-terminus
Version: 0.1.0
Summary: MCP server for WP-CLI over local Docker, Pantheon Terminus, or SSH — run WP-CLI and byte-faithfully copy posts/meta between WordPress environments, with checksum verification.
Project-URL: Homepage, https://github.com/EarthmanWeb/mcp-wp-cli-terminus
Project-URL: Repository, https://github.com/EarthmanWeb/mcp-wp-cli-terminus
Project-URL: Issues, https://github.com/EarthmanWeb/mcp-wp-cli-terminus/issues
Author: EarthmanWeb
License: MIT
License-File: LICENSE
Keywords: ai,anthropic,claude,claude-code,cli,devops,llm,mcp,migration,model-context-protocol,pantheon,terminus,wordpress,wp,wp-cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# mcp-wp-cli-terminus

**An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server that lets Claude and other AI agents run [WP-CLI](https://wp-cli.org) against WordPress — over local Docker, [Pantheon Terminus](https://docs.pantheon.io/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.

<sub>`wp-cli` · `wordpress` · `terminus` · `pantheon` · `mcp` · `model-context-protocol` · `claude` · `claude-code` · `anthropic` · `wordpress-migration` · `devops`</sub>

---

## 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](https://github.com/wp-cli/wp-cli/issues/2712)).
- Piping content over STDIN **hangs on `terminus remote:wp`** ([terminus#1615](https://github.com/pantheon-systems/terminus/issues/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.

## Tools

| Tool | What it does |
|------|--------------|
| `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_copy_post` | Byte-faithfully copy a post's `post_content` from one environment to another, with an **md5 round-trip verification**. |
| `wp_copy_post_meta` | Byte-faithfully copy a post's **complete** meta (all custom fields — serialized arrays, multiple values per key, ACF repeaters) between environments, with a canonical **checksum verification**. Copy all keys (full mirror) or an allow-list. |

### Correctness guarantees

- **Never routes content through the model's text.** Payloads are read into the server and base64-encoded in code.
- **Checksum-verified.** Every copy 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.

## Install & run

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

### With `uvx` (recommended — no install)

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

### With `pip`

```bash
pip install mcp-wp-cli-terminus
```

```jsonc
{
  "mcpServers": {
    "wp-cli": { "command": "mcp-wp-cli-terminus" }
  }
}
```

### From source

```bash
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

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`](wp-cli.conf.example) and edit:

```ini
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`](.gitignore) excludes it.

## Usage examples

```jsonc
// 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" }

// Copy a front page's block markup from local to production, verified
{ "tool": "wp_copy_post", "post_id": 42, "from": "local", "to": "production", "confirm": true }

// Copy ALL meta for a post (full mirror), verified
{ "tool": "wp_copy_post_meta", "post_id": 42, "from": "local", "to": "production", "confirm": true }

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

Each copy returns `verified: true/false` with `src_md5` / `dst_md5`, the delivery mode, and per-side transport.

## 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](https://wp-cli.org) reachable via one of: a local Docker container (`docker exec`), Pantheon [Terminus](https://docs.pantheon.io/terminus) on the host, or a host WP-CLI with `--ssh`.

## Tests

```bash
python -m unittest discover -s tests -v
```

54 stdlib-only unit tests cover config parsing, transport selection (local/Terminus/SSH), the argv size guard, newline handling, PHP-key safety, and the full copy/verify orchestration via an injectable command-runner seam (no real WP-CLI invoked).

## License

[MIT](LICENSE)
