mcp-wp-cli-terminus 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_wp_cli_terminus-0.1.0/.gitignore +24 -0
- mcp_wp_cli_terminus-0.1.0/LICENSE +21 -0
- mcp_wp_cli_terminus-0.1.0/PKG-INFO +169 -0
- mcp_wp_cli_terminus-0.1.0/README.md +147 -0
- mcp_wp_cli_terminus-0.1.0/pyproject.toml +56 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/__init__.py +59 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/__main__.py +7 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/command.py +278 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/config.py +178 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/tools.py +596 -0
- mcp_wp_cli_terminus-0.1.0/src/wp_cli_mcp/transport.py +131 -0
- mcp_wp_cli_terminus-0.1.0/tests/test_wp_cli_mcp.py +622 -0
- mcp_wp_cli_terminus-0.1.0/wp-cli.conf.example +27 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
|
|
14
|
+
# Project config that may contain host/site/credential details — NEVER commit.
|
|
15
|
+
# Ship wp-cli.conf.example instead.
|
|
16
|
+
.serena/wp-cli.conf
|
|
17
|
+
wp-cli.conf
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
|
|
22
|
+
# Editors
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EarthmanWeb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-wp-cli-terminus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
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.
|
|
5
|
+
Project-URL: Homepage, https://github.com/EarthmanWeb/mcp-wp-cli-terminus
|
|
6
|
+
Project-URL: Repository, https://github.com/EarthmanWeb/mcp-wp-cli-terminus
|
|
7
|
+
Project-URL: Issues, https://github.com/EarthmanWeb/mcp-wp-cli-terminus/issues
|
|
8
|
+
Author: EarthmanWeb
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,anthropic,claude,claude-code,cli,devops,llm,mcp,migration,model-context-protocol,pantheon,terminus,wordpress,wp,wp-cli
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
18
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# mcp-wp-cli-terminus
|
|
24
|
+
|
|
25
|
+
**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.**
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
<sub>`wp-cli` · `wordpress` · `terminus` · `pantheon` · `mcp` · `model-context-protocol` · `claude` · `claude-code` · `anthropic` · `wordpress-migration` · `devops`</sub>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Why
|
|
34
|
+
|
|
35
|
+
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:
|
|
36
|
+
|
|
37
|
+
- `wp post update --post_content` **truncates at newlines** ([wp-cli#2712](https://github.com/wp-cli/wp-cli/issues/2712)).
|
|
38
|
+
- Piping content over STDIN **hangs on `terminus remote:wp`** ([terminus#1615](https://github.com/pantheon-systems/terminus/issues/1615)).
|
|
39
|
+
- Hand-pasting base64 into an agent prompt is **lossy** — a single flipped byte silently corrupts production.
|
|
40
|
+
- Large payloads passed as one shell argument hit the Linux **`MAX_ARG_STRLEN` (131072 bytes)** limit and fail with `E2BIG`.
|
|
41
|
+
- Post **meta** with serialized arrays / multiple values per key is easy to corrupt by re-serializing.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
| Tool | What it does |
|
|
48
|
+
|------|--------------|
|
|
49
|
+
| `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. |
|
|
50
|
+
| `wp_copy_post` | Byte-faithfully copy a post's `post_content` from one environment to another, with an **md5 round-trip verification**. |
|
|
51
|
+
| `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. |
|
|
52
|
+
|
|
53
|
+
### Correctness guarantees
|
|
54
|
+
|
|
55
|
+
- **Never routes content through the model's text.** Payloads are read into the server and base64-encoded in code.
|
|
56
|
+
- **Checksum-verified.** Every copy re-reads the destination and compares it to the transferred source; `verified: false` + an error on any mismatch.
|
|
57
|
+
- **Transport-agnostic.** The same logic runs over local Docker, Pantheon Terminus, and WP-CLI `--ssh`.
|
|
58
|
+
- **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`.
|
|
59
|
+
- **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.
|
|
60
|
+
- **Production guard.** Writes to a production destination require `confirm: true` when `PROD_GUARD` is enabled.
|
|
61
|
+
|
|
62
|
+
## Install & run
|
|
63
|
+
|
|
64
|
+
The server is **pure Python (stdlib only, zero dependencies)**.
|
|
65
|
+
|
|
66
|
+
### With `uvx` (recommended — no install)
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
// Claude Desktop / Claude Code MCP config
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"wp-cli": {
|
|
73
|
+
"command": "uvx",
|
|
74
|
+
"args": ["mcp-wp-cli-terminus"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### With `pip`
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install mcp-wp-cli-terminus
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"wp-cli": { "command": "mcp-wp-cli-terminus" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### From source
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
git clone https://github.com/EarthmanWeb/mcp-wp-cli-terminus
|
|
98
|
+
cd mcp-wp-cli-terminus
|
|
99
|
+
python -m wp_cli_mcp # PYTHONPATH=src, or `pip install -e .`
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Configure
|
|
103
|
+
|
|
104
|
+
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:
|
|
105
|
+
|
|
106
|
+
```ini
|
|
107
|
+
DEFAULT_SITE=example-site
|
|
108
|
+
PROD_GUARD=true
|
|
109
|
+
|
|
110
|
+
[site:example-site]
|
|
111
|
+
LOCAL_CONTAINER=my-container # docker container running WP-CLI
|
|
112
|
+
LOCAL_PATH=/var/www/html # WordPress path inside the container
|
|
113
|
+
TERMINUS_SITE=example # Pantheon site — production routes over Terminus
|
|
114
|
+
TERMINUS_ENV=dev # default env (override per call)
|
|
115
|
+
# — or, for a non-Pantheon remote, omit TERMINUS_* and set:
|
|
116
|
+
# REMOTE_SSH=deploy@example.com:22/var/www/html
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **Production transport is chosen by config:** `TERMINUS_SITE` → `terminus remote:wp`; otherwise `REMOTE_SSH` → WP-CLI `--ssh`.
|
|
120
|
+
- **Multi-site:** add more `[site:NAME]` sections and pass `site` per call.
|
|
121
|
+
|
|
122
|
+
**Never commit `.serena/wp-cli.conf`** — it may contain hostnames/SSH strings. The shipped [`.gitignore`](.gitignore) excludes it.
|
|
123
|
+
|
|
124
|
+
## Usage examples
|
|
125
|
+
|
|
126
|
+
```jsonc
|
|
127
|
+
// Run a WP-CLI command locally
|
|
128
|
+
{ "tool": "wp_cli", "args": "plugin list --status=active --format=json" }
|
|
129
|
+
|
|
130
|
+
// Run against production (Terminus or SSH per config)
|
|
131
|
+
{ "tool": "wp_cli", "args": "option get siteurl", "target": "production" }
|
|
132
|
+
|
|
133
|
+
// Copy a front page's block markup from local to production, verified
|
|
134
|
+
{ "tool": "wp_copy_post", "post_id": 42, "from": "local", "to": "production", "confirm": true }
|
|
135
|
+
|
|
136
|
+
// Copy ALL meta for a post (full mirror), verified
|
|
137
|
+
{ "tool": "wp_copy_post_meta", "post_id": 42, "from": "local", "to": "production", "confirm": true }
|
|
138
|
+
|
|
139
|
+
// Copy only specific meta keys
|
|
140
|
+
{ "tool": "wp_copy_post_meta", "post_id": 42, "from": "local", "to": "production",
|
|
141
|
+
"keys": ["_thumbnail_id", "my_field"], "confirm": true }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Each copy returns `verified: true/false` with `src_md5` / `dst_md5`, the delivery mode, and per-side transport.
|
|
145
|
+
|
|
146
|
+
## Debug logging
|
|
147
|
+
|
|
148
|
+
Failures (non-zero WP-CLI exits) are appended to a log in your system temp dir — **failures only**, successes are never logged:
|
|
149
|
+
|
|
150
|
+
- Location: `${TMPDIR}/wp-cli-mcp/failures.log` (override with `WP_CLI_MCP_LOG_DIR`).
|
|
151
|
+
- Disable entirely with `WP_CLI_MCP_LOG=0`.
|
|
152
|
+
- SSH connection strings are redacted in the log.
|
|
153
|
+
|
|
154
|
+
## Requirements
|
|
155
|
+
|
|
156
|
+
- Python 3.8+
|
|
157
|
+
- [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`.
|
|
158
|
+
|
|
159
|
+
## Tests
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python -m unittest discover -s tests -v
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
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).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# mcp-wp-cli-terminus
|
|
2
|
+
|
|
3
|
+
**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.**
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
<sub>`wp-cli` · `wordpress` · `terminus` · `pantheon` · `mcp` · `model-context-protocol` · `claude` · `claude-code` · `anthropic` · `wordpress-migration` · `devops`</sub>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
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:
|
|
14
|
+
|
|
15
|
+
- `wp post update --post_content` **truncates at newlines** ([wp-cli#2712](https://github.com/wp-cli/wp-cli/issues/2712)).
|
|
16
|
+
- Piping content over STDIN **hangs on `terminus remote:wp`** ([terminus#1615](https://github.com/pantheon-systems/terminus/issues/1615)).
|
|
17
|
+
- Hand-pasting base64 into an agent prompt is **lossy** — a single flipped byte silently corrupts production.
|
|
18
|
+
- Large payloads passed as one shell argument hit the Linux **`MAX_ARG_STRLEN` (131072 bytes)** limit and fail with `E2BIG`.
|
|
19
|
+
- Post **meta** with serialized arrays / multiple values per key is easy to corrupt by re-serializing.
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|------|--------------|
|
|
27
|
+
| `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. |
|
|
28
|
+
| `wp_copy_post` | Byte-faithfully copy a post's `post_content` from one environment to another, with an **md5 round-trip verification**. |
|
|
29
|
+
| `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. |
|
|
30
|
+
|
|
31
|
+
### Correctness guarantees
|
|
32
|
+
|
|
33
|
+
- **Never routes content through the model's text.** Payloads are read into the server and base64-encoded in code.
|
|
34
|
+
- **Checksum-verified.** Every copy re-reads the destination and compares it to the transferred source; `verified: false` + an error on any mismatch.
|
|
35
|
+
- **Transport-agnostic.** The same logic runs over local Docker, Pantheon Terminus, and WP-CLI `--ssh`.
|
|
36
|
+
- **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`.
|
|
37
|
+
- **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.
|
|
38
|
+
- **Production guard.** Writes to a production destination require `confirm: true` when `PROD_GUARD` is enabled.
|
|
39
|
+
|
|
40
|
+
## Install & run
|
|
41
|
+
|
|
42
|
+
The server is **pure Python (stdlib only, zero dependencies)**.
|
|
43
|
+
|
|
44
|
+
### With `uvx` (recommended — no install)
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
// Claude Desktop / Claude Code MCP config
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"wp-cli": {
|
|
51
|
+
"command": "uvx",
|
|
52
|
+
"args": ["mcp-wp-cli-terminus"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### With `pip`
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install mcp-wp-cli-terminus
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```jsonc
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"wp-cli": { "command": "mcp-wp-cli-terminus" }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### From source
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/EarthmanWeb/mcp-wp-cli-terminus
|
|
76
|
+
cd mcp-wp-cli-terminus
|
|
77
|
+
python -m wp_cli_mcp # PYTHONPATH=src, or `pip install -e .`
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Configure
|
|
81
|
+
|
|
82
|
+
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:
|
|
83
|
+
|
|
84
|
+
```ini
|
|
85
|
+
DEFAULT_SITE=example-site
|
|
86
|
+
PROD_GUARD=true
|
|
87
|
+
|
|
88
|
+
[site:example-site]
|
|
89
|
+
LOCAL_CONTAINER=my-container # docker container running WP-CLI
|
|
90
|
+
LOCAL_PATH=/var/www/html # WordPress path inside the container
|
|
91
|
+
TERMINUS_SITE=example # Pantheon site — production routes over Terminus
|
|
92
|
+
TERMINUS_ENV=dev # default env (override per call)
|
|
93
|
+
# — or, for a non-Pantheon remote, omit TERMINUS_* and set:
|
|
94
|
+
# REMOTE_SSH=deploy@example.com:22/var/www/html
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **Production transport is chosen by config:** `TERMINUS_SITE` → `terminus remote:wp`; otherwise `REMOTE_SSH` → WP-CLI `--ssh`.
|
|
98
|
+
- **Multi-site:** add more `[site:NAME]` sections and pass `site` per call.
|
|
99
|
+
|
|
100
|
+
**Never commit `.serena/wp-cli.conf`** — it may contain hostnames/SSH strings. The shipped [`.gitignore`](.gitignore) excludes it.
|
|
101
|
+
|
|
102
|
+
## Usage examples
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
// Run a WP-CLI command locally
|
|
106
|
+
{ "tool": "wp_cli", "args": "plugin list --status=active --format=json" }
|
|
107
|
+
|
|
108
|
+
// Run against production (Terminus or SSH per config)
|
|
109
|
+
{ "tool": "wp_cli", "args": "option get siteurl", "target": "production" }
|
|
110
|
+
|
|
111
|
+
// Copy a front page's block markup from local to production, verified
|
|
112
|
+
{ "tool": "wp_copy_post", "post_id": 42, "from": "local", "to": "production", "confirm": true }
|
|
113
|
+
|
|
114
|
+
// Copy ALL meta for a post (full mirror), verified
|
|
115
|
+
{ "tool": "wp_copy_post_meta", "post_id": 42, "from": "local", "to": "production", "confirm": true }
|
|
116
|
+
|
|
117
|
+
// Copy only specific meta keys
|
|
118
|
+
{ "tool": "wp_copy_post_meta", "post_id": 42, "from": "local", "to": "production",
|
|
119
|
+
"keys": ["_thumbnail_id", "my_field"], "confirm": true }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Each copy returns `verified: true/false` with `src_md5` / `dst_md5`, the delivery mode, and per-side transport.
|
|
123
|
+
|
|
124
|
+
## Debug logging
|
|
125
|
+
|
|
126
|
+
Failures (non-zero WP-CLI exits) are appended to a log in your system temp dir — **failures only**, successes are never logged:
|
|
127
|
+
|
|
128
|
+
- Location: `${TMPDIR}/wp-cli-mcp/failures.log` (override with `WP_CLI_MCP_LOG_DIR`).
|
|
129
|
+
- Disable entirely with `WP_CLI_MCP_LOG=0`.
|
|
130
|
+
- SSH connection strings are redacted in the log.
|
|
131
|
+
|
|
132
|
+
## Requirements
|
|
133
|
+
|
|
134
|
+
- Python 3.8+
|
|
135
|
+
- [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`.
|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python -m unittest discover -s tests -v
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
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).
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-wp-cli-terminus"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "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."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "EarthmanWeb" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"wp-cli",
|
|
17
|
+
"wordpress",
|
|
18
|
+
"terminus",
|
|
19
|
+
"pantheon",
|
|
20
|
+
"claude",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"anthropic",
|
|
23
|
+
"ai",
|
|
24
|
+
"llm",
|
|
25
|
+
"devops",
|
|
26
|
+
"migration",
|
|
27
|
+
"wp",
|
|
28
|
+
"cli",
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Development Status :: 4 - Beta",
|
|
32
|
+
"Environment :: Console",
|
|
33
|
+
"Intended Audience :: Developers",
|
|
34
|
+
"License :: OSI Approved :: MIT License",
|
|
35
|
+
"Programming Language :: Python :: 3",
|
|
36
|
+
"Topic :: Software Development :: Build Tools",
|
|
37
|
+
"Topic :: Internet :: WWW/HTTP :: Site Management",
|
|
38
|
+
"Topic :: Utilities",
|
|
39
|
+
]
|
|
40
|
+
dependencies = [] # stdlib only — zero runtime dependencies
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/EarthmanWeb/mcp-wp-cli-terminus"
|
|
44
|
+
Repository = "https://github.com/EarthmanWeb/mcp-wp-cli-terminus"
|
|
45
|
+
Issues = "https://github.com/EarthmanWeb/mcp-wp-cli-terminus/issues"
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
# Console entry point — run with `uvx mcp-wp-cli-terminus` (no install) or after
|
|
49
|
+
# `pip install mcp-wp-cli-terminus`.
|
|
50
|
+
mcp-wp-cli-terminus = "wp_cli_mcp:main"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/wp_cli_mcp"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src/wp_cli_mcp", "tests", "README.md", "LICENSE", "wp-cli.conf.example"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""wp_cli — a transport-agnostic WP-CLI MCP server package.
|
|
3
|
+
|
|
4
|
+
Exposes a stdio JSON-RPC MCP server with three tools:
|
|
5
|
+
wp_cli, wp_copy_post, wp_copy_post_meta.
|
|
6
|
+
|
|
7
|
+
Public API is re-exported here so callers (and tests) can `from wp_cli import ...`
|
|
8
|
+
without reaching into submodules.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .config import (
|
|
12
|
+
CONF_RELATIVE_PATH,
|
|
13
|
+
DESTRUCTIVE_PREFIXES,
|
|
14
|
+
SEARCH_REPLACE,
|
|
15
|
+
ConfigError,
|
|
16
|
+
get_project_root,
|
|
17
|
+
guard_enabled,
|
|
18
|
+
load_config,
|
|
19
|
+
require,
|
|
20
|
+
resolve_site,
|
|
21
|
+
)
|
|
22
|
+
from .command import (
|
|
23
|
+
MAX_ARG_STRLEN,
|
|
24
|
+
build_command,
|
|
25
|
+
is_destructive,
|
|
26
|
+
log_failure,
|
|
27
|
+
run_php_eval,
|
|
28
|
+
run_wp,
|
|
29
|
+
set_runner,
|
|
30
|
+
transport_label,
|
|
31
|
+
uses_terminus,
|
|
32
|
+
)
|
|
33
|
+
from .tools import (
|
|
34
|
+
TOOL_DEFINITIONS,
|
|
35
|
+
TOOL_REGISTRY,
|
|
36
|
+
tool_wp_cli,
|
|
37
|
+
tool_wp_copy_post,
|
|
38
|
+
tool_wp_copy_post_meta,
|
|
39
|
+
)
|
|
40
|
+
from .transport import (
|
|
41
|
+
SERVER_NAME,
|
|
42
|
+
SERVER_VERSION,
|
|
43
|
+
PROTOCOL_VERSION,
|
|
44
|
+
handle_initialize,
|
|
45
|
+
handle_tools_call,
|
|
46
|
+
handle_tools_list,
|
|
47
|
+
main,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"CONF_RELATIVE_PATH", "DESTRUCTIVE_PREFIXES", "SEARCH_REPLACE", "ConfigError",
|
|
52
|
+
"get_project_root", "guard_enabled", "load_config", "require", "resolve_site",
|
|
53
|
+
"MAX_ARG_STRLEN", "build_command", "is_destructive", "log_failure",
|
|
54
|
+
"run_php_eval", "run_wp", "set_runner", "transport_label", "uses_terminus",
|
|
55
|
+
"TOOL_DEFINITIONS", "TOOL_REGISTRY",
|
|
56
|
+
"tool_wp_cli", "tool_wp_copy_post", "tool_wp_copy_post_meta",
|
|
57
|
+
"SERVER_NAME", "SERVER_VERSION", "PROTOCOL_VERSION",
|
|
58
|
+
"handle_initialize", "handle_tools_call", "handle_tools_list", "main",
|
|
59
|
+
]
|