wp-mcp-gateway
by 118group
README.md
# wp-mcp-gateway (Cloudflare Workers version)
Same idea as the Node version: one MCP endpoint, every tool call takes a
`site_id`, so one Claude connector covers your whole WordPress fleet. This
version runs on Cloudflare Workers instead of a VPS — no server to patch,
free tier covers this comfortably, and it's live at a `workers.dev` URL
within a couple minutes of running `wrangler deploy`.
It's built on Cloudflare's current recommended approach: a **stateless**
`createMcpHandler` from the `agents` SDK (the older stateful `McpAgent` /
Durable Objects approach is deprecated for new servers — this gateway
doesn't need session state anyway, since every tool call is independent).
Verified locally end-to-end before hand-off: Worker boots, `/health`
responds, the MCP `initialize` handshake works, bearer-token auth correctly
rejects missing/wrong tokens and allows the right one, and `list_sites`
returns real data from a test `SITES_JSON`.
## 1. Prerequisites
```bash
npm install -g wrangler # or just use npx wrangler as shown below
wrangler login # opens a browser to authenticate with your Cloudflare account
```
If you don't have a Cloudflare account yet, sign up free at
https://dash.cloudflare.com/sign-up — the free tier (100,000 requests/day)
is more than enough for this.
## 2. Install dependencies
```bash
cd wp-mcp-gateway-cf
npm install
```
## 3. Configure your sites
Unlike the Node version, Workers have no filesystem, so the site registry
lives in a **secret** (`SITES_JSON`) instead of a file. Same shape as before
— one entry per site:
```json
{
"town-green": {
"label": "Town Green",
"baseUrl": "https://town-green.org",
"username": "mcp-agent",
"appPassword": "xxxx xxxx xxxx xxxx xxxx xxxx",
"host": "wpengine"
},
"cra": {
"label": "CRA",
"baseUrl": "https://cra.com",
"username": "mcp-agent",
"appPassword": "xxxx xxxx xxxx xxxx xxxx xxxx",
"host": "gridpane"
}
}
```
Build that JSON for all your sites (a spreadsheet-to-JSON script is easy to
throw together once you've generated Application Passwords for each site —
ask if you want one), then set it as a secret:
```bash
npx wrangler secret put SITES_JSON
# paste the whole JSON blob when prompted, then press Ctrl+D
```
**Application Passwords:** in wp-admin, `Users → your user →
Application Passwords`. Same recommendation as before — use a dedicated
low-privilege WP user per site rather than your personal admin account, so a
leaked token can't do more than that role allows.
## 4. Set your gateway auth token
```bash
npx wrangler secret put GATEWAY_TOKEN
# paste a long random value, e.g. output of: openssl rand -hex 32
```
Without this set, the endpoint runs with no auth — fine for a first local
test, never for the deployed version.
## 5. Test locally
```bash
npx wrangler dev
```
This starts a local dev server (default `http://localhost:8787`). For local
testing, put your secrets in a `.dev.vars` file instead (never commit it):
```
GATEWAY_TOKEN=some-local-test-token
SITES_JSON={"town-green":{"label":"Town Green","baseUrl":"https://town-green.org","username":"mcp-agent","appPassword":"xxxx"}}
```
Check it's alive:
```bash
curl http://localhost:8787/health
```
## 6. Deploy
```bash
npx wrangler deploy
```
Wrangler prints your live URL, something like:
```
https://wp-mcp-gateway.<your-subdomain>.workers.dev
```
Your MCP endpoint is `https://wp-mcp-gateway.<your-subdomain>.workers.dev/mcp`.
## 7. Add it as a connector in Claude
Add a custom connector pointing at that `/mcp` URL, with header
`Authorization: Bearer <GATEWAY_TOKEN>` (the same value you set in step 4).
From then on, "list draft posts on cra.com" only needs Claude to call
`list_sites` (or already know the id) and then `list_posts` with
`site_id: "cra"` — same behavior as the Node version, just edge-hosted.
## Updating sites later
Add a new site, or change credentials, by re-running:
```bash
npx wrangler secret put SITES_JSON
```
No redeploy needed — secrets update independently of the Worker code.
## Notes specific to this deployment
- **No filesystem, no long-running process** — the whole registry lives in
the `SITES_JSON` secret, parsed fresh on each request. Trivial cost even
at ~80 sites.
- **Stateless by design** — each MCP request creates a fresh server
instance (per Cloudflare's current guidance); there's no cross-request
session to worry about losing.
- **WAF flags on managed hosts** — same caveat as the Node version: WP
Engine in particular may flag traffic from an unfamiliar IP range calling
the REST API. If a site 403s only when hit via this gateway, that's the
first thing to check with WP Engine support — worth allowlisting
Cloudflare's egress ranges if needed.
- **Scope of tools** — this only talks to the core WP REST API
(`/wp/v2/...`). No WP-CLI, SSH, or database-level operations — Workers
can't SSH out anyway, so that would need a different piece entirely if
you need it later.
## Security checklist before connecting Claude to the deployed URL
- [ ] `GATEWAY_TOKEN` set via `wrangler secret put`, long and random
- [ ] `SITES_JSON` uses a dedicated low-privilege WP user per site
- [ ] `.dev.vars` (if you used one locally) is in `.gitignore` and never committed
- [ ] You've tested against 2–3 sites before scaling the secret up to all ~80
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing