Skip to main content
Glama
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