WP MCP Connector
Self-contained Model Context Protocol server for WordPress. Streamable HTTP and HTTP+SSE, Application Passwords / bearer tokens / built-in OAuth 2.1, 32 tools, one-call article publishing with images and SEO.
by Remy Mazmanian · github.com/remymazmanian/wp-mcp-connector-plugin · website
Install
The author publishes release zips, so WP-CLI can install straight from GitHub:
wp plugin install https://github.com/remymazmanian/wp-mcp-connector-plugin/releases/download/v1.0.1/wp-mcp-connector-1.0.1.zipReadme
WP MCP Connector
Serve a self-hosted WordPress site to AI clients over the Model Context Protocol.
Claude Desktop, Claude Code, Cursor, Grok and anything else that speaks MCP can read and manage the site through 32 permission-gated tools: posts, pages, media, taxonomies, comments, SEO metadata, plugin and theme inventory, site health, and a set of emulated WP-CLI commands.
- No Composer. No npm. No build step. The plugin is plain PHP and runs on a stock WordPress install.
- Two transports. Streamable HTTP (current spec) and HTTP+SSE (legacy clients).
- Three auth routes. Application Passwords (primary), optional Bearer tokens, and a built-in OAuth 2.1 authorization server with PKCE for hosted clients.
- One call publishes.
wp_publish_articlebuilds a finished post — body, featured image, in-article images at named paragraphs, categories, tags and SEO — in a single tool call. - Modern where it counts. On WordPress 6.9+ every tool is also registered with the core Abilities API, so the same definitions are reachable from
/wp-json/wp-abilities/v1/and from the official MCP Adapter if you install it. On 7.1+ each ability also carries the unifiedpublicexposure flag that core, the MCP Adapter and AI agents read.
Contents
- Architecture
- Requirements
- Install and activate
- Configure
- Generate an Application Password
- Credential storage
- Connect a client
- The stdio bridge
- Tool reference
- Security model
- Example prompts
- Troubleshooting
- Extending
Architecture
Claude Desktop / Claude Code / Cursor / Grok
│
├─ (A) native remote MCP ──► POST /wp-json/mcp/v1/mcp Streamable HTTP
├─ (B) legacy clients ─────► GET /wp-json/mcp/v1/sse HTTP+SSE
└─ (C) stdio-only clients ─► npx wp-mcp-bridge ──► (A) zero-dep TS proxy
│
┌───────────────────┴──────────────────┐
│ WPMCP_Server (JSON-RPC 2.0 engine) │
│ initialize / tools.list / tools.call │
└───────────────────┬──────────────────┘
┌──────────────┬────────────────┼──────────────┬─────────────┐
Auth Rate limiter Registry Abilities Logger
(App Password (sliding window (32 tools, mirror →
+ Bearer per user) cap-gated) WP core Abilities API
+ OAuth 2.1)
The protocol engine is transport agnostic. Streamable HTTP and legacy SSE both hand messages to the same WPMCP_Server, and differ only in how they frame the reply, so behaviour cannot drift between them.
Why a self-contained server rather than only the MCP Adapter
The official wordpress/mcp-adapter package is the direction of travel, but it needs Composer and a feature plugin, and its API has moved between releases. This plugin therefore does both:
| Self-contained endpoints | Abilities API mirror | |
|---|---|---|
| Requires | nothing | WordPress 6.9+ |
| Endpoint | /wp-json/mcp/v1/mcp |
/wp-json/wp-abilities/v1/abilities/…/run |
| Always available | yes | when core supports it |
| Used by MCP Adapter | no | yes, if installed |
One tool definition feeds all three paths. Adding a tool once exposes it everywhere.
File layout
wp-mcp-connector/
├── wp-mcp-connector.php bootstrap, constants, autoloader
├── includes/
│ ├── class-wpmcp-plugin.php orchestrator, tool registry owner
│ ├── class-wpmcp-server.php JSON-RPC 2.0 / MCP protocol engine
│ ├── class-wpmcp-rest.php Streamable HTTP + legacy SSE transports
│ ├── class-wpmcp-auth.php Application Passwords + Bearer tokens
│ ├── class-wpmcp-settings.php settings, permission profiles
│ ├── class-wpmcp-registry.php tool registry and exposure rules
│ ├── class-wpmcp-schema.php JSON Schema builders and validator
│ ├── class-wpmcp-session.php MCP sessions + legacy SSE message queue
│ ├── class-wpmcp-rate-limiter.php per-user sliding window
│ ├── class-wpmcp-logger.php rolling activity log
│ ├── class-wpmcp-seo.php SEO adapter (Yoast / Rank Math / SEOPress / custom)
│ ├── class-wpmcp-abilities.php core Abilities API mirror + MCP Adapter handoff
│ ├── class-wpmcp-admin.php settings screen
│ └── tools/
│ ├── class-wpmcp-tools-content.php
│ ├── class-wpmcp-tools-taxonomy.php
│ ├── class-wpmcp-tools-media.php
│ ├── class-wpmcp-tools-site.php
│ ├── class-wpmcp-tools-comments.php
│ └── class-wpmcp-tools-maintenance.php
└── bridge/ optional TypeScript stdio bridge
├── package.json
├── tsconfig.json
└── src/index.ts
Requirements
| Minimum | Notes | |
|---|---|---|
| WordPress | 6.4 | 6.9+ additionally registers abilities in core; tested up to 7.1 |
| PHP | 7.4 | tested on 8.2 and 8.4 |
| HTTPS | required in production | local environments are exempt |
| Composer | not needed | — |
| npm | only for the optional bridge | Node 18+ |
Install and activate
A short connection walkthrough for Grok, ChatGPT and Claude lives at remymazmanian.com/connect-grok-chatgpt-claude-to-wordpress.
Upload the folder:
rsync -av wp-mcp-connector/ user@host:/path/to/wp-content/plugins/wp-mcp-connector/
Or zip it and use Plugins → Add New → Upload Plugin.
Activate, then open Settings → MCP Connector.
The server ships switched off and on the least-privileged profile. Nothing is reachable until you enable it.
Verify it is running
curl -u 'USERNAME:APP_PASSWORD' https://example.com/wp-json/mcp/v1/health
A healthy response reports the WordPress and PHP versions, which auth method was used, the active profile, and the exact list of tools that user can currently call.
Configure
Settings → MCP Connector.
Permission profiles
A profile decides which tools are offered. The connected WordPress user still needs the matching capability, so a profile can never grant more than the account already has.
| Profile | Tools | Use for |
|---|---|---|
| Read only | 12 | research, audits, reporting |
| Author | 25 | drafting and editing content, uploading media |
| Editor | 29 | the above plus trashing content and moderating comments |
| Administrator | 32 | everything, including permanent deletion and option writes |
| Custom | your choice | tick exactly the tools you want |
The default is Author: it can create and update content but cannot delete anything, cannot touch options, and cannot run the CLI emulator.
Other settings worth setting deliberately
- Required capability — the floor for reaching the endpoints at all. Default
edit_posts. - Rate limit — default 120 tool calls per 60 seconds per user. An agent stuck in a retry loop hits this instead of your database.
- Readable and writable options — an allowlist.
wp_get_optionandwp_update_optionrefuse everything not on it, and the refusal names what is allowed. - Media download hosts — leave empty to allow any public host. Private and loopback addresses are always blocked regardless.
- Legacy SSE transport — each open stream holds a PHP worker for the configured duration. Turn it off unless a client needs it.
Generate an Application Password
Application Passwords are built into WordPress and are the recommended credential. They are per-application, revocable individually, and never expose the account's real password.
- Users → Profile (or Users → All Users → edit the user).
- Scroll to Application Passwords.
- Enter a name that identifies the client, for example
Claude Desktop, work laptop. - Click Add New Application Password.
- Copy the value shown. It looks like
abcd EFGH ijkl MNOP qrst UVWXand is displayed once only.
The spaces are cosmetic and WordPress ignores them. Use it as the HTTP Basic password with the account's username (not email):
curl -u 'remy:abcdEFGHijklMNOPqrstUVWX' https://example.com/wp-json/mcp/v1/health
Create a dedicated account for this. An account with the Author or Editor role, used only by the AI client, means a leaked credential cannot install plugins or read user emails, and revoking it does not disturb your own login.
If the Application Passwords section is missing: WordPress hides it when the request is not over HTTPS and the environment type is not
local. Fix the HTTPS, or setWP_ENVIRONMENT_TYPEtolocalinwp-config.phpfor local development.
Bearer tokens (optional)
Required for hosted clients such as Grok, and useful for any client you want to hold on a shorter leash. Enable Bearer tokens in settings, then issue one at the bottom of the settings page: pick a user, a label, a scope and an expiry. The token is shown once and only a SHA-256 hash is stored.
Bearer tokens work on /wp-json/mcp/v1/* and nowhere else. That is deliberate, and it is the main reason to prefer them for anything you do not control: a token that also unlocked the whole REST API, including plugin installation and every ability registered by every other plugin, would be a far larger credential than the job needs. See Connecting an administrator account safely.
Credential storage
What an Application Password is and is not
It is not your WordPress password. It is a separate 24-character credential that cannot be used at wp-login.php, does not reveal your real password, and can be revoked on its own without disturbing your login or any other client.
But be clear about what it can do. An Application Password authenticates against the whole REST API, not just this plugin's endpoints. If it belongs to an administrator, it can reach /wp/v2/plugins to install and activate plugins, /wp/v2/users to read every account's email address, and /wp/v2/users/me/application-passwords to mint more credentials — none of which this plugin's permission profiles constrain, because those routes are WordPress core's, not ours.
So the single most effective thing you can do is not use an administrator account. An Editor-role Application Password cannot touch /wp/v2/plugins at all: the capability check fails before your configuration is even consulted. Every tool in the Editor profile still works.
| Account role | Worst case if the credential leaks |
|---|---|
| Administrator | Full site compromise: plugin installation, user data, new credentials |
| Editor | Content and comments can be altered; no code execution, no user emails |
| Author | Its own drafts can be altered |
Where the secret ends up
Ordinary MCP client setup writes the credential to disk in plaintext:
| Client | File | Format |
|---|---|---|
Claude Code (--scope user) |
~/.claude.json |
plaintext JSON |
Claude Code (--scope project) |
.mcp.json |
plaintext JSON, easy to commit by accident |
| Claude Desktop | claude_desktop_config.json |
plaintext JSON |
| Cursor | ~/.cursor/mcp.json |
plaintext JSON |
A claude mcp add … --header "Authorization: Basic $(…)" command also lands in your shell history.
Keychain-backed credentials (macOS)
The bridge can keep the secret in the login keychain instead, so no config file and no shell history ever contains it. Store it once:
node bridge/dist/index.js --save-credential \
--url https://example.com/wp-json/mcp/v1/mcp \
--user YOUR_USERNAME
security is spawned with the terminal attached and does its own no-echo, type-it-twice prompt. The secret therefore never passes through argv (where ps could see it), the environment, a Node string, or your history — it goes straight from your keyboard into the keychain.
From then on the client config holds only non-secret values:
{
"mcpServers": {
"wordpress": {
"command": "node",
"args": ["/absolute/path/to/bridge/dist/index.js"],
"env": {
"WP_MCP_URL": "https://example.com/wp-json/mcp/v1/mcp",
"WP_MCP_USERNAME": "YOUR_USERNAME"
}
}
}
}
Verify, and remove, with:
node bridge/dist/index.js --probe --url https://example.com --user YOUR_USERNAME
node bridge/dist/index.js --delete-credential --url https://example.com --user YOUR_USERNAME
Credentials are stored under the service name wp-mcp-bridge, keyed by username@host, so one machine can hold several sites and several users without collision. They are visible and revocable in Keychain Access. An explicit WP_MCP_APP_PASSWORD or WP_MCP_TOKEN still wins when set, so CI and existing setups are unaffected.
On Linux and Windows the keychain flags are refused with an explanation; use environment variables from your OS credential store there.
Keeping the secret out of shell history
If you do use the header approach, avoid pasting the credential on a command line:
read -rs -p 'App password: ' APW
claude mcp add --scope user --transport http wordpress \
https://example.com/wp-json/mcp/v1/mcp \
--header "Authorization: Basic $(printf '%s' "USERNAME:$APW" | base64)"
unset APW
read -rs does not echo and does not record. The credential still lands in ~/.claude.json, so this is a partial measure — the keychain route above is the one that removes it entirely.
Connect a client
Endpoint: https://example.com/wp-json/mcp/v1/mcp
Read the full README on GitHub →
Releases
| Tag | Published | Asset | Downloads |
|---|---|---|---|
| v1.0.1 | Aug 19, 2026 | wp-mcp-connector-1.0.1.zip | 8 |