wp-mcp
# wp-mcp — WordPress connector for Claude
Lets you chat with Claude and have it read and change a WordPress site directly.
> **Just want to use it, not develop it?**
> Non-technical setup guide for **Claude Desktop**: **[INSTALL-DESKTOP.md](INSTALL-DESKTOP.md)**.
> Download `dist/wp-mcp.mjs` — it is a single self-contained file that needs only
> Node installed, no `npm install`, no cloning.
Works with **any** self-hosted WordPress 5.6+. Nothing is installed on the site —
it uses the REST API that ships with core, authenticated with an Application
Password. Multiple sites can be configured; every tool takes an optional `site`.
## Install
Requires **Node 20+**. Pick one of the two.
### Option 1 — Single file (fastest, nothing to clone)
```bash
curl -fsSL -o ~/wp-mcp.mjs https://raw.githubusercontent.com/amolb1986/wp-mcp/main/dist/wp-mcp.mjs
claude mcp add wordpress -s user -- node ~/wp-mcp.mjs
```
`dist/wp-mcp.mjs` is a self-contained bundle — no `npm install`, no dependencies.
### Option 2 — Clone (for development, or to run the tests)
```bash
git clone https://github.com/amolb1986/wp-mcp.git
cd wp-mcp
npm install
claude mcp add wordpress -s user -- node "$(pwd)/src/index.js"
```
### Then connect a WordPress site
```bash
# Option 1 (single file) — pass credentials as environment variables:
claude mcp remove wordpress -s user
claude mcp add wordpress -s user \
-e WP_URL=https://example.com \
-e WP_USER=your-username \
-e WP_APP_PASSWORD="abcd efgh ijkl mnop qrst uvwx" \
-- node ~/wp-mcp.mjs
# Option 2 (cloned) — use the setup CLI, which supports multiple sites:
node bin/setup.js add mysite https://example.com your-username "abcd efgh ijkl mnop qrst uvwx"
```
Restart Claude Code, then ask: *"list the WordPress sites you can access"*.
Using **Claude Desktop** instead? See **[INSTALL-DESKTOP.md](INSTALL-DESKTOP.md)** —
no terminal needed beyond installing Node.
## Setup details
**1. Create an Application Password on the site**
In wp-admin: **Users → Profile → Application Passwords**. Name it `Claude`,
click Add, and copy the generated password (shown once, looks like
`abcd efgh ijkl mnop qrst uvwx`).
This is *not* your login password. It can be revoked from the same screen at any
time without changing your account, and it inherits your user's capabilities —
so an Editor account can edit posts but not touch plugins.
**2. Register the site**
```bash
cd ~/wp-mcp
node bin/setup.js add mysite https://example.com your-username "abcd efgh ijkl mnop qrst uvwx"
```
That verifies the connection immediately and tells you which role you connected as.
Add `--read-only` to make a site read-only. Useful if you ever point this at production:
```bash
node bin/setup.js add prod https://www.example.com me "…" --read-only
```
**3. Use it**
The connector is registered with Claude Code as `wordpress`. Restart Claude Code
and ask things like:
> list the draft pages on mysite
> read the "Pricing" page and rewrite the intro to be shorter
> create a draft post titled "Q3 update" with these bullets …
> which plugins are active on dev?
### Managing sites
```bash
node bin/setup.js list # show configured sites
node bin/setup.js test # check every site connects
node bin/setup.js default mysite
node bin/setup.js remove prod
```
Credentials live in `~/.wp-mcp/sites.json`, written `chmod 600`.
## Tools
| Tool | What it does |
|---|---|
| `wp_sites` | List configured sites and test each connection |
| `wp_whoami` | Show the authenticated user, roles, capabilities |
| `wp_list_post_types` / `wp_list_taxonomies` | Discover post types and taxonomies, including custom ones |
| `wp_list_content` | List posts/pages/custom types, filtered and searched |
| `wp_get_content` | Fetch one item with its raw block/HTML body |
| `wp_search` | Search all content at once |
| `wp_create_content` | Create content (**defaults to draft**) |
| `wp_update_content` | Update fields on an existing item |
| `wp_delete_content` | Trash, or permanently delete with `force` |
| `wp_list_terms` / `wp_create_term` | Categories, tags, custom terms |
| `wp_list_media` / `wp_upload_media` | Media library; upload from a local path or URL |
| `wp_list_users` | Users and roles |
| `wp_list_plugins` / `wp_manage_plugin` | List plugins, activate/deactivate |
| `wp_list_themes` | Installed themes |
| `wp_settings` | Read or change site settings |
| `wp_rest` | Call **any** REST route — WooCommerce, ACF, menus, plugin namespaces |
`wp_rest` is the escape hatch: anything the site exposes over REST is reachable
even if there is no dedicated tool for it. `wp_rest` with `path: ""` lists every
available route on the site.
## Safety
- Creating content defaults to **draft**, never publish.
- Deleting **trashes** by default; permanent deletion needs `force: true`.
- `--read-only` (or `WP_MCP_READONLY=1`) rejects every non-GET request.
- You are limited to whatever the WordPress user can do. For a dev site where
Claude should manage plugins, use an Administrator account; to be conservative,
use an Editor.
## Environment variables
| Variable | Purpose |
|---|---|
| `WP_URL`, `WP_USER`, `WP_APP_PASSWORD` | Configure a single site without a config file |
| `WP_SITE_NAME` | Name for that env-configured site (default `default`) |
| `WP_MCP_CONFIG` | Alternate path to `sites.json` |
| `WP_MCP_READONLY=1` | Force read-only across all sites |
| `WP_MCP_INSECURE_TLS=1` | Accept self-signed certificates (dev servers only) |
| `WP_MCP_TIMEOUT_MS` | Request timeout, default 30000 |
## Troubleshooting
**"Could not reach …"** — the site is not reachable from this machine. If it is an
internal dev host, connect to the VPN first. Check with
`curl -I https://example.com/`.
**Self-signed certificate** — set `WP_MCP_INSECURE_TLS=1`.
**401 incorrect_password** — some hosts strip the `Authorization` header. Add to
`.htaccess` on the site:
```apache
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]
```
**Site behind HTTP Basic auth** — that uses the same `Authorization` header as
Application Passwords and will conflict. Allowlist your IP, or exempt
`/wp-json` from basic auth.
**`rest_no_route`** — that route does not exist on the site. Run `wp_rest` with
`path: ""` to see what is actually available.
## Tests
```bash
npm test
```
`test/smoke.js` spawns the real MCP server over stdio against a stub WordPress
REST API and exercises every tool, plus auth failure, unreachable host,
read-only mode, and sites without pretty permalinks. `test/cli.js` covers the
setup CLI. 71 checks total, no network access needed.
TDQS
Scored across 20 tools
Most tools have clearly distinct purposes (e.g., wp_list_content vs wp_get_content, wp_list_taxonomies vs wp_list_terms), and descriptions clarify boundaries. However, wp_search and wp_list_content both serve content discovery, and wp_rest is a catch-all that can overlap with any dedicated tool, creating minor selection ambiguity.
The wp_ prefix is consistent, and the majority follow a verb_noun pattern (list_*, create_*, get_*, update_*, delete_*). A few exceptions like wp_whoami, wp_sites, wp_search, wp_settings, and wp_rest break the pattern, but the overall style remains readable and predictable.
With 20 tools, the server is on the heavier side but still within reason for a WordPress management tool. Each tool covers a distinct domain (content, terms, media, plugins, themes, settings), and the count is not bloated; the generic wp_rest tool could potentially have reduced the number, but the dedicated tools provide clear value.
Content CRUD is fully covered, and basic operations exist for terms, media, plugins, and settings. However, obvious gaps include term update/delete, media delete, user creation/update/delete, and theme activation. While wp_rest can fill these gaps, the dedicated tool surface is incomplete for a fully-featured WordPress management server.