Skip to main content
Glama
README.md
# 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

A3.6/5.0

Scored across 20 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count4/5

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.

Completeness3/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues