Documentation
Connect your AI client
Portcullis is the MCP server. Nothing to install, nothing running on your machine — one URL and one approval.
-
Copy the server URL
https://broker.portculliswp.com/mcp— also under Settings → MCP in the dashboard, with a copy button. -
Add it to your client
Claude Code:
claude mcp add --transport http portcullis https://broker.portculliswp.com/mcp
Claude Desktop or claude.ai: Settings → Connectors → Add custom connector, paste the URL. - Approve once Your client opens the Portcullis sign-in. Sign in and approve — the tools are available from then on.
Custom connectors are a paid-plan feature on Claude (Pro, Max, Team or Enterprise) — Anthropic's requirement, not ours. Any other client that does dynamic registration and a PKCE authorization-code flow works too; Portcullis speaks standard MCP over streamable HTTP with standard OAuth 2.1.
The tools you get
| Tool | Needs | What it does |
|---|---|---|
list_sites |
read | Every connected site you can act on — id, address, tags, owning client. The ids the other tools take. |
resolve_sites |
read | Target by attribute instead of by id: tags, WordPress version below a threshold, health state, owning client, whether an action is installed, whether it can actually run, or last-reached-before a date. |
list_abilities |
read | What one site exposes — each action's name, description, input schema, and read-only / destructive flags. |
run_ability |
write | Run one action on one site. |
run_ability_fleet |
write | Run one action across many sites at once. Up to 25 run inline and return per-site outcomes; beyond that the work is queued and you get a job id back immediately. 1,000 sites per call is the ceiling. |
get_fleet_job |
read | Progress and failures for a queued run: total, done, succeeded, failed, status, and which sites are failing. |
get_fleet_job_results |
read | The per-site success bodies from a queued run, one page at a time — what makes "audit 300 sites and tell me the worst" answerable. |
The usual fleet shape: resolve_sites to target,
run_ability_fleet over the ids it returns, then get_fleet_job
and get_fleet_job_results to read back what happened.
resolve_sites filters down from the set you can already reach, so
it can never surface a site you have no access to.
Reading the consent screen
Anyone can register an MCP client and call it whatever they like. So the screen makes the one fact an attacker can't fake the loudest thing on it.
- "Sends access to" is the identity. To use that address, whoever registered the client has to actually control it. It's the only claim on the page that costs an attacker anything. Approve only if you recognise it.
- "Calls itself" is a claim, not a fact — and it's labelled as one. There's no verification process, so there's no verified badge to show.
- No logo is ever shown. A real Claude logo above a hostile address would defeat everything else on the page, so there's nowhere to put one.
Approving is a form submission, never a link — a page in another tab can't navigate you into silently granting access. It also writes an entry to your audit log recording the client, the scope and who approved it.
Scopes
| Scope | Authorizes |
|---|---|
abilities:read |
Listing sites and listing what a site can do. Nothing can be run. |
abilities:write |
Running actions, single-site and fleet-wide. Includes read. |
abilities:destructive |
Required on top of write for anything Portcullis classifies as destructive. Includes the two below it. |
A client that requests no scope — the common case, Claude included — is granted
read and write, never destructive.
abilities:destructive has to be asked for by name, and when a client asks,
the consent screen warns you in as many words before you approve.
What counts as destructive
Portcullis decides, and only ever upward: a site can declare an action destructive and be believed, but a site declaring an action safe can't pull it out of the gate. Self-declaration is unreliable in practice — of the actions our own plugin registers, exactly one declares itself destructive, while resetting a password, deactivating a plugin and taking a site offline all declare themselves safe.
So a floor holds regardless: anything that deletes, removes, purges, resets, uninstalls or deactivates, anything in the user or database namespaces, plus named high-blast-radius actions — creating a user, updating core, writing a site setting, switching theme, maintenance mode, publishing a post, firing a cron event. The floor is a pure function of the action's name, so it still applies when a site is unreachable, which is exactly when you'd least want a gate to quietly disappear.
A denied call is recorded in the audit log, and the client is told to re-authorize with the scope rather than left guessing.
What one approval covers
You, across your organization's fleet. Not one site — there's no "switch site" step and no re-authorization per site.
Breadth isn't where the control lives. The site id is an argument your client passes, and every call revalidates it before anything reaches WordPress: the site must belong to your organization and be one you personally may reach, with your membership re-checked at that moment. A site id you aren't entitled to fails closed. If your membership is restricted to part of the fleet, that's all the client sees.
Your client's token never reaches WordPress. It stays at Portcullis. When a tool call needs to touch a site, Portcullis signs a separate short-lived pass for that leg — named to that one site, carrying that one action, valid about a minute, and bound to a single-use handshake so a copy captured in flight is inert.
Turning it off
There's no screen today that revokes one MCP client's authorization. Saying so is worth more than implying a button exists.
What you can do right now cuts what any client can reach:
- Disconnect a site. Portcullis stops signing for it and it fails closed on the very next call, for every client at once.
- Narrow a site's approved actions. Takes effect for calls in flight as well as future ones — see What AI can do.
Removing the connector in your own client stops it being used too, of course. It just isn't the same thing as a server-side revocation, and we'd rather say so.
If something looks wrong
The tools return an empty fleet
No sites connected yet — see Getting started. A site stored but pending verification also won't appear and can't be acted on.
An action was refused even though the model could see it
The site hasn't approved that action. The approval list is a separate gate from the scope you granted here, and it's the one that always holds — see What AI can do.
The client is told to re-authorize
An authorization predating a feature it would need fails closed rather than quietly carrying on with privileges it was never granted. Approving again is the whole fix.
An error message told me nothing
Deliberate. When an action fails, your client gets a generic message — the underlying WordPress error is logged on our side and never handed to the model, because error bodies echo request detail. Successful results are passed through; that's the work you asked for. Inside a fan-out, a site your client can't reach becomes one per-site error, not a failed batch.
Calls are being throttled
Requests are rate limited per user, so no client can drive tool calls arbitrarily fast.
Next: What AI can do — the approval list, the two gates, and why a WordPress admin always wins.
Trouble connecting? Email hello@portculliswp.com.