WP Rocket: MCP server and AI Abilities
New in WP Rocket 3.23. WP Rocket ships an AI-integration surface that lets a Model Context Protocol (MCP) client — the bundled/trusted publisher is Claude — read and write WP Rocket settings on a live site in natural language. For a companion plugin / theme developer this skill covers how to scope, extend, or lock down that surface, not how to build an MCP client.
The MCP OAuth server is described in its own source as a proof of concept (inc/Engine/MCP/Auth/ClaudeClientVerifier.php:8). Treat it as experimental: the abilities are stable-ish public hooks, the OAuth transport is newer and off by default.
The two layers (the mental model that prevents 90% of mistakes)
WP Rocket's AI surface is two independent layers with opposite defaults:
| Layer |
What it is |
Default |
Enable/kill filter |
| Abilities |
WP Abilities API entries (wp-rocket/get-options, wp-rocket/set-option) in category wp-rocket-options |
ON (true) |
rocket_enable_abilities |
| MCP OAuth server |
OAuth 2.1 + JWT transport at /wp-json/mcp/mcp-oauth-server, /oauth/*, /.well-known/* that lets a remote client authenticate and reach the abilities |
OFF (false) |
rocket_mcp_oauth_server_enabled |
Key consequences:
- Abilities register on their own, gated only by
rocket_enable_abilities (default true) — verified inc/Engine/Abilities/Context.php:19. They are reachable by any local abilities/MCP consumer and via REST (show_in_rest => true), still behind the rocket_manage_options capability. Turning the OAuth server on is NOT required for the abilities to exist.
- The OAuth server is what a remote client (Claude) needs to log in. It is off until a site owner opts in with
rocket_mcp_oauth_server_enabled — verified inc/Engine/MCP/Context.php:23.
- To fully remove AI read/write of WP Rocket settings, set
rocket_enable_abilities to false. Turning off only the OAuth server still leaves the abilities registered for local consumers.
Feature-detection — three things must ALL be present
Nothing here loads on a stock WordPress. Before relying on it:
- WP Rocket 3.23+ —
defined('WP_ROCKET_VERSION') and version_compare(WP_ROCKET_VERSION, '3.23', '>=').
- The WP Abilities API —
function_exists('wp_register_ability'). WP Rocket's register() methods early-return without it (GetOptions.php:44, SetOption.php:154).
- The MCP Adapter —
class_exists(\WP\MCP\Core\McpAdapter::class) (bundled as wordpress/mcp-adapter ^0.4.1). The transport Server::register_server() early-returns without it (Server.php:19). Only needed for the OAuth/MCP server layer, not for the abilities themselves.
if (
! defined( 'WP_ROCKET_VERSION' )
|| version_compare( WP_ROCKET_VERSION, '3.23', '<' )
|| ! function_exists( 'wp_register_ability' )
) {
return; // No WP Rocket AI surface to customize.
}
Misconception this skill corrects
"Enabling the MCP OAuth server is what exposes my settings to AI, so leaving it off is safe."
Half-true. The OAuth server (off by default) only gates remote authentication. The abilities (wp-rocket/get-options, wp-rocket/set-option) register regardless, defaulting ON, and are callable by any local abilities consumer and over REST — always subject to the rocket_manage_options capability. If you need to guarantee no AI path can touch settings, use add_filter( 'rocket_enable_abilities', '__return_false' ), don't just leave the OAuth server off.
Other AI-prone misconceptions:
- "
rocket_mcp_options_allowlist is a blocklist — I add keys to hide them." Wrong, it's an allowlist. Whatever it returns is exactly what AI can read (get-options) and write (set-option). Adding a key EXPOSES it; removing a key restricts it. Verified AllowedOptions.php:14 and enforced in SetOption::validate_option_name() (SetOption.php:318).
- "These are
apply_filters, so I return whatever." They run through wpm_apply_filters_typed( 'boolean' | 'array', ... ) — the wrapper calls core apply_filters (so add_filter works normally) then coerces/validates the return type. Return a real bool for boolean filters and a real array for array filters; a wrong type is coerced or dropped. Prefer __return_true / __return_false for the booleans.
- "
rocket_mcp_trusted_publishers lets me bless any client_id." No — it can only ADD publishers, and each must still pass an exact client_id URL match plus a host-based SSRF gate; it cannot bypass the verified hard-reject. Verified ClaudeClientVerifier.php:83-113.
- "
set-option just writes what I send." It validates the key against the allowlist, sanitizes per option type, and for array/textarea options update (default) MERGES into the existing list; only replace overwrites. Verified SetOption.php:331.
When to use this skill
Trigger when ANY of the following is true:
- Exposing WP Rocket settings to Claude / an MCP client, or scoping WHICH settings AI may touch.
- The diff adds
add_filter( 'rocket_enable_abilities' | 'rocket_mcp_oauth_server_enabled' | 'rocket_mcp_options_allowlist' | 'rocket_mcp_trusted_publishers', ... ).
- Code references
wp-rocket/get-options, wp-rocket/set-option, the wp-rocket-options ability category, or /wp-json/mcp/mcp-oauth-server / /oauth/* / /.well-known/oauth-*.
- Reviewing a companion plugin that registers its own abilities and wants to appear alongside WP Rocket's, or that adds custom option keys to the allowlist.
- Security review: is a destructive
set-option reachable, is the allowlist minimal, is the OAuth server intentionally on?
The abilities (verified surface)
Both live in category wp-rocket-options (registered on wp_abilities_api_categories_init), and both check current_user_can( 'rocket_manage_options' ) (rocket_manage_options is a WP Rocket capability that exists @since 3.4, granted to administrator on activation).
| Ability |
Kind |
meta.annotations |
Permission |
wp-rocket/get-options |
Read all allowlisted options as a flat key→value object |
readonly: true, destructive: false, idempotent: true |
rocket_manage_options |
wp-rocket/set-option |
Write ONE option (option_name, option_value, optional update_mode) |
readonly: false, destructive: true, idempotent: true |
rocket_manage_options |
Both carry meta.mcp.public => true and show_in_rest => true.
set-option input schema (verified SetOption.php:171):
option_name — enum of the allowlist keys.
option_value — anyOf string / boolean / integer / array-of-string.
update_mode — update (default, appends to array/textarea options) or replace (overwrites the whole list).
set-option returns { success, error?, previous_value, new_value }. The ability's own description text instructs the AI to show current→new value and require explicit per-change confirmation before writing, and to call get-options first when editing an array-type option so it doesn't clobber existing entries.
Controlling exposure — the allowlist and the kill switch
The single control point for "what can AI see and change" is rocket_mcp_options_allowlist. The built-in list (~70 keys) spans cache, CSS/JS optimization, media/lazyload, fonts, preload, database cleanup, CDN, Cloudflare, Heartbeat, performance monitoring, and add-ons — verified AllowedOptions.php:14.
// Expose a custom / third-party option key to the get-options & set-option abilities.
add_filter( 'rocket_mcp_options_allowlist', function ( array $allowlist ): array {
$allowlist[] = 'my_plugin_cache_toggle';
return $allowlist;
} );
// RESTRICT — remove sensitive settings you never want AI to change (e.g. CDN + Cloudflare).
add_filter( 'rocket_mcp_options_allowlist', function ( array $allowlist ): array {
return array_values( array_diff( $allowlist, [
'cdn', 'cdn_cnames', 'cdn_zone',
'do_cloudflare', 'cloudflare_auto_settings',
] ) );
} );
// Kill switch — no AI read/write of WP Rocket settings at all.
add_filter( 'rocket_enable_abilities', '__return_false' );
A key added to the allowlist is readable via get-options only if it also has a schema entry in GetOptions (the readable set = allowlist ∩ known schema, array_intersect_key at GetOptions.php:416); it is writable via set-option purely on allowlist membership, and unknown keys fall through sanitize_value() unmodified. So when you expose a custom key, prefer wiring your own sanitization on the WP Rocket option save path rather than trusting the pass-through.
The MCP OAuth server (opt-in)
Off until a site owner opts in:
// Turn the MCP OAuth server ON (rewrite rules, /oauth/* endpoints, discovery, transport).
add_filter( 'rocket_mcp_oauth_server_enabled', '__return_true' );
// After toggling, flush rewrite rules once (Settings > Permalinks, or wp rewrite flush).
What it registers when enabled (verified):
- MCP server at
/wp-json/mcp/mcp-oauth-server via WP\MCP\Core\McpAdapter, exposing mcp-adapter/discover-abilities, get-ability-info, execute-ability over a custom JWT-Bearer OAuthHttpTransport — Server.php:18-41.
- OAuth endpoints via rewrite rules (query var
mcp_oauth_endpoint, handled on template_redirect) — Rewrite.php:43-48:
/oauth/authorize, /oauth/authorize-callback, /oauth/token, /oauth/consent, /oauth/revoke.
- Discovery documents (rewrite on
init) — Discovery/Endpoints.php:44-52:
/.well-known/oauth-protected-resource (RFC 9728), /.well-known/oauth-authorization-server (RFC 8414).
Trusted publishers (who may connect)
Client identification uses Client ID Metadata Documents (CIMD). Only client_ids whose host is allowlisted are ever network-fetched (SSRF gate), and the fetched doc must exactly match a pinned client_id. The bundled publisher is Claude — verified ClaudeClientVerifier.php:100-112:
claude → client_ids: https://claude.ai/oauth/claude-code-client-metadata
https://claude.ai/oauth/mcp-oauth-client-metadata
host: claude.ai
Add another trusted MCP client (can only ADD; the exact client_id match + host gate still apply):
add_filter( 'rocket_mcp_trusted_publishers', function ( array $publishers ): array {
$publishers['my-agent'] = [
'client_ids' => [ 'https://agent.example.com/.well-known/mcp-client' ],
'host' => 'agent.example.com',
];
return $publishers;
} );
Security considerations
set-option is destructive by declaration. Anyone (or any AI) that can authenticate as a user holding rocket_manage_options can flip caching, CDN, Cloudflare, and database-cleanup settings. Keep the allowlist minimal for the risk profile you accept.
- Minimize the allowlist, don't expand it casually. Database-cleanup toggles (
database_*), cdn*, and cloudflare_* have real side effects if flipped by a confused agent.
- The OAuth server is a public network surface when enabled — leave it off unless a specific integration needs it, and confirm the trusted-publisher list is exactly what you intend.
- Telemetry: every ability execution fires
track_event( 'MCP Ability Executed', … ) (WP Media Mixpanel, via TrackingTrait) — GetOptions.php:467, SetOption.php:269. If your compliance posture forbids that, gate it at WP Rocket's analytics/consent setting.
- Capability, not nonce, is the gate. Access hinges entirely on
rocket_manage_options; do not grant that cap to roles you wouldn't trust to reconfigure caching.
Critical rules
- Two layers, opposite defaults: abilities default ON (
rocket_enable_abilities), OAuth server default OFF (rocket_mcp_oauth_server_enabled). Reason about them separately.
- To disable AI settings access entirely, use
rocket_enable_abilities => false; the OAuth-server toggle alone is not enough.
rocket_mcp_options_allowlist is an ALLOWLIST — adding exposes, removing restricts. Minimize it.
- Typed filters (
wpm_apply_filters_typed): return the exact declared type — bool for the two enable filters, array for allowlist / trusted-publishers. Use __return_true/__return_false for booleans.
set-option update_mode defaults to update (merge) for array/textarea options; pass replace to overwrite. Read get-options before editing an array option.
rocket_mcp_trusted_publishers can only ADD publishers; exact client_id match + host SSRF gate still apply. Never widen a host to a shared domain.
- Flush rewrite rules after toggling the OAuth server on/off (the
/oauth/* and /.well-known/* paths are rewrite-based).
- Feature-detect all three: WP Rocket 3.23+,
wp_register_ability, and (for the server) McpAdapter.
- The abilities are behind
rocket_manage_options — treat that capability as the real security boundary.
Common mistakes
// WRONG — assuming the allowlist hides keys
add_filter( 'rocket_mcp_options_allowlist', function ( $a ) {
$a[] = 'stripe_secret_key'; // WRONG: this EXPOSES it to AI read/write
return $a;
} );
// RIGHT — allowlist is additive exposure; to hide, remove
add_filter( 'rocket_mcp_options_allowlist', function ( $a ) {
return array_values( array_diff( $a, [ 'do_cloudflare' ] ) );
} );
// WRONG — returning the wrong type into a typed filter
add_filter( 'rocket_mcp_oauth_server_enabled', function () {
return 1; // WRONG: boolean filter; return a real bool
} );
// RIGHT
add_filter( 'rocket_mcp_oauth_server_enabled', '__return_true' );
// WRONG — thinking "OAuth server off" means settings are AI-proof
add_filter( 'rocket_mcp_oauth_server_enabled', '__return_false' );
// abilities still registered & REST-exposed behind rocket_manage_options
// RIGHT — to guarantee no AI settings surface
add_filter( 'rocket_enable_abilities', '__return_false' );
// WRONG — trusting an arbitrary client via a shared host
add_filter( 'rocket_mcp_trusted_publishers', function ( $p ) {
$p['x'] = [ 'client_ids' => [ 'https://github.com/foo' ], 'host' => 'github.com' ];
return $p; // WRONG: shared host = anyone on that host is a candidate client
} );
// RIGHT — a host you control, with exact client_id URLs
add_filter( 'rocket_mcp_trusted_publishers', function ( $p ) {
$p['my-agent'] = [
'client_ids' => [ 'https://agent.example.com/.well-known/mcp-client' ],
'host' => 'agent.example.com',
];
return $p;
} );
// WRONG — expecting update_mode 'update' to overwrite an array option
// set-option { option_name: 'cache_reject_uri', option_value: ['/x'] }
// → MERGES '/x' into the existing list, does not replace it
// RIGHT — pass replace when you mean overwrite
// set-option { option_name: 'cache_reject_uri', option_value: ['/x'], update_mode: 'replace' }
Cross-references
- Run
wp-abilities-api for the generic WP Abilities API mechanics (registering abilities, categories, wp_register_ability, permission/execute callbacks, input_schema/output_schema) — WP Rocket's abilities are a concrete consumer of that API.
- Run
claude-api / MCP references when building the client side that connects to this server (Claude as the trusted publisher).
- Run
wp-rocket-cache-rejection-and-filters to understand what the exposed option keys actually do (cache_reject_uri, cdn_*, lazyload*, etc.) before allowlisting them.
- Run
wp-rocket-cache-invalidation when the task is clearing cache after a change rather than exposing settings.
- Run
wp-security-audit / wp-security-secrets when reviewing whether the allowlist or trusted-publisher list widens the attack surface.
What this skill does NOT cover
- Building an MCP client (the Claude side). This is the server/integrator surface only.
- The WP Abilities API internals — see
wp-abilities-api.
- The
wordpress/mcp-adapter package internals (WP\MCP\*) — bundled dependency; treat its classes as private and version-volatile. Integrate via WP Rocket's filters and the public abilities.
- OAuth/JWT protocol internals (token issuance, JTI/refresh handling, application-password linkage) — private
inc/Engine/MCP/Auth/* implementation; do not call directly.
- The WP Rocket settings UI / license / activation — out of scope for integrators.
References
- Abilities enable gate: inc/Engine/Abilities/Context.php:19 —
wpm_apply_filters_typed( 'boolean', 'rocket_enable_abilities', true ).
- Allowlist: inc/Engine/Abilities/Options/AllowedOptions.php:14-102 — built-in ~70 keys +
rocket_mcp_options_allowlist.
wp-rocket/get-options: inc/Engine/Abilities/Options/GetOptions.php:418 — readonly, mcp.public, cap rocket_manage_options.
wp-rocket/set-option: inc/Engine/Abilities/Options/SetOption.php:158 — destructive; input_schema, update_mode, per-type sanitization at SetOption.php:331.
- MCP OAuth enable gate: inc/Engine/MCP/Context.php:23 —
wpm_apply_filters_typed( 'boolean', 'rocket_mcp_oauth_server_enabled', false ).
- MCP server: inc/Engine/MCP/Transport/Server.php:18 —
/wp-json/mcp/mcp-oauth-server, McpAdapter, ability-adapter tools.
- OAuth endpoints: inc/Engine/MCP/Auth/Rewrite.php:43 —
/oauth/{authorize,authorize-callback,token,consent,revoke}.
- Discovery: inc/Engine/MCP/Auth/Discovery/Endpoints.php:44 —
/.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server.
- Trusted publishers (Claude): inc/Engine/MCP/Auth/ClaudeClientVerifier.php:100 —
rocket_mcp_trusted_publishers.
- Bundled deps (composer.json):
wordpress/mcp-adapter ^0.4.1, wp-media/apply-filters-typed ^1.0 (the wpm_apply_filters_typed helper), wp-media/wp-mixpanel (telemetry).
- Official documentation: https://docs.wp-rocket.me/article/92-plugin-compatibility-with-wp-rocket
- Official documentation: https://docs.wp-rocket.me/article/2-getting-started
1---2name: wp-rocket-mcp-and-abilities3description: Expose, scope, or secure WP Rocket settings for AI / MCP clients — new in 3.23. Two layers — WP Abilities API abilities (wp-rocket/get-options readonly, wp-rocket/set-option destructive, category wp-rocket-options, cap rocket_manage_options) and a built-in MCP OAuth 2.1 server at /wp-json/mcp/mcp-oauth-server (/oauth/*, /.well-known) with trusted publisher Claude (claude.ai). Critical — abilities default ON (rocket_enable_abilities), the OAuth server defaults OFF (rocket_mcp_oauth_server_enabled), and both need the WP Abilities API + wordpress/mcp-adapter or nothing registers. Scope what AI may read / write via rocket_mcp_options_allowlist (an additive allowlist); add clients via rocket_mcp_trusted_publishers. Filters are typed (wpm_apply_filters_typed) — return exact bool / array. Use when exposing WP Rocket settings to an MCP client, scoping the allowlist, or reviewing AI-facing code. Triggers on rocket_mcp_, rocket_enable_abilities, wp-rocket/get-options, wp-rocket/set-option, "/oauth/" MCP server.4---56# WP Rocket: MCP server and AI Abilities78New in **WP Rocket 3.23**. WP Rocket ships an AI-integration surface that lets a Model Context Protocol (MCP) client — the bundled/trusted publisher is **Claude** — read and write WP Rocket settings on a live site in natural language. For a companion plugin / theme developer this skill covers **how to scope, extend, or lock down that surface**, not how to build an MCP client.910> The MCP OAuth server is described in its own source as a **proof of concept** ([inc/Engine/MCP/Auth/ClaudeClientVerifier.php:8](ClaudeClientVerifier.php)). Treat it as experimental: the abilities are stable-ish public hooks, the OAuth transport is newer and off by default.1112## The two layers (the mental model that prevents 90% of mistakes)1314WP Rocket's AI surface is **two independent layers** with **opposite defaults**:1516| Layer | What it is | Default | Enable/kill filter |17|---|---|---|---|18| **Abilities** | WP Abilities API entries (`wp-rocket/get-options`, `wp-rocket/set-option`) in category `wp-rocket-options` | **ON** (`true`) | `rocket_enable_abilities` |19| **MCP OAuth server** | OAuth 2.1 + JWT transport at `/wp-json/mcp/mcp-oauth-server`, `/oauth/*`, `/.well-known/*` that lets a **remote** client authenticate and reach the abilities | **OFF** (`false`) | `rocket_mcp_oauth_server_enabled` |2021Key consequences:2223- Abilities register **on their own**, gated only by `rocket_enable_abilities` (default `true`) — verified [inc/Engine/Abilities/Context.php:19](Context.php). They are reachable by **any local abilities/MCP consumer** and via REST (`show_in_rest => true`), still behind the `rocket_manage_options` capability. Turning the OAuth server on is NOT required for the abilities to exist.24- The **OAuth server is what a remote client (Claude) needs** to log in. It is off until a site owner opts in with `rocket_mcp_oauth_server_enabled` — verified [inc/Engine/MCP/Context.php:23](Context.php).25- To **fully remove AI read/write of WP Rocket settings**, set `rocket_enable_abilities` to `false`. Turning off only the OAuth server still leaves the abilities registered for local consumers.2627## Feature-detection — three things must ALL be present2829Nothing here loads on a stock WordPress. Before relying on it:30311. **WP Rocket 3.23+** — `defined('WP_ROCKET_VERSION')` and `version_compare(WP_ROCKET_VERSION, '3.23', '>=')`.322. **The WP Abilities API** — `function_exists('wp_register_ability')`. WP Rocket's `register()` methods early-return without it ([GetOptions.php:44](GetOptions.php), [SetOption.php:154](SetOption.php)).333. **The MCP Adapter** — `class_exists(\WP\MCP\Core\McpAdapter::class)` (bundled as `wordpress/mcp-adapter` ^0.4.1). The transport `Server::register_server()` early-returns without it ([Server.php:19](Server.php)). Only needed for the OAuth/MCP server layer, not for the abilities themselves.3435```php36if (37 ! defined( 'WP_ROCKET_VERSION' )38 || version_compare( WP_ROCKET_VERSION, '3.23', '<' )39 || ! function_exists( 'wp_register_ability' )40) {41 return; // No WP Rocket AI surface to customize.42}43```4445## Misconception this skill corrects4647> "Enabling the MCP OAuth server is what exposes my settings to AI, so leaving it off is safe."4849Half-true. The **OAuth server** (off by default) only gates *remote* authentication. The **abilities** (`wp-rocket/get-options`, `wp-rocket/set-option`) register regardless, defaulting ON, and are callable by any local abilities consumer and over REST — always subject to the `rocket_manage_options` capability. If you need to guarantee no AI path can touch settings, use `add_filter( 'rocket_enable_abilities', '__return_false' )`, don't just leave the OAuth server off.5051Other AI-prone misconceptions:5253- "`rocket_mcp_options_allowlist` is a blocklist — I add keys to hide them." Wrong, it's an **allowlist**. Whatever it returns is exactly what AI can read (`get-options`) and write (`set-option`). Adding a key EXPOSES it; removing a key restricts it. Verified [AllowedOptions.php:14](AllowedOptions.php) and enforced in `SetOption::validate_option_name()` ([SetOption.php:318](SetOption.php)).54- "These are `apply_filters`, so I return whatever." They run through `wpm_apply_filters_typed( 'boolean' | 'array', ... )` — the wrapper calls core `apply_filters` (so `add_filter` works normally) then **coerces/validates the return type**. Return a real `bool` for boolean filters and a real `array` for array filters; a wrong type is coerced or dropped. Prefer `__return_true` / `__return_false` for the booleans.55- "`rocket_mcp_trusted_publishers` lets me bless any client_id." No — it can only ADD publishers, and each must still pass an **exact client_id URL match** plus a host-based SSRF gate; it cannot bypass the `verified` hard-reject. Verified [ClaudeClientVerifier.php:83-113](ClaudeClientVerifier.php).56- "`set-option` just writes what I send." It validates the key against the allowlist, sanitizes per option type, and for array/textarea options **`update` (default) MERGES into the existing list**; only `replace` overwrites. Verified [SetOption.php:331](SetOption.php).5758## When to use this skill5960Trigger when ANY of the following is true:6162- Exposing WP Rocket settings to Claude / an MCP client, or scoping WHICH settings AI may touch.63- The diff adds `add_filter( 'rocket_enable_abilities' | 'rocket_mcp_oauth_server_enabled' | 'rocket_mcp_options_allowlist' | 'rocket_mcp_trusted_publishers', ... )`.64- Code references `wp-rocket/get-options`, `wp-rocket/set-option`, the `wp-rocket-options` ability category, or `/wp-json/mcp/mcp-oauth-server` / `/oauth/*` / `/.well-known/oauth-*`.65- Reviewing a companion plugin that registers its own abilities and wants to appear alongside WP Rocket's, or that adds custom option keys to the allowlist.66- Security review: is a destructive `set-option` reachable, is the allowlist minimal, is the OAuth server intentionally on?6768## The abilities (verified surface)6970Both live in category `wp-rocket-options` (registered on `wp_abilities_api_categories_init`), and both check `current_user_can( 'rocket_manage_options' )` (`rocket_manage_options` is a WP Rocket capability that exists `@since 3.4`, granted to `administrator` on activation).7172| Ability | Kind | `meta.annotations` | Permission |73|---|---|---|---|74| `wp-rocket/get-options` | Read all allowlisted options as a flat key→value object | `readonly: true`, `destructive: false`, `idempotent: true` | `rocket_manage_options` |75| `wp-rocket/set-option` | Write ONE option (`option_name`, `option_value`, optional `update_mode`) | `readonly: false`, **`destructive: true`**, `idempotent: true` | `rocket_manage_options` |7677Both carry `meta.mcp.public => true` and `show_in_rest => true`.7879`set-option` input schema (verified [SetOption.php:171](SetOption.php)):8081- `option_name` — enum of the allowlist keys.82- `option_value` — `anyOf` string / boolean / integer / array-of-string.83- `update_mode` — `update` (default, appends to array/textarea options) or `replace` (overwrites the whole list).8485`set-option` returns `{ success, error?, previous_value, new_value }`. The ability's own description text instructs the AI to **show current→new value and require explicit per-change confirmation** before writing, and to call `get-options` first when editing an array-type option so it doesn't clobber existing entries.8687## Controlling exposure — the allowlist and the kill switch8889The single control point for "what can AI see and change" is `rocket_mcp_options_allowlist`. The built-in list (~70 keys) spans cache, CSS/JS optimization, media/lazyload, fonts, preload, database cleanup, CDN, Cloudflare, Heartbeat, performance monitoring, and add-ons — verified [AllowedOptions.php:14](AllowedOptions.php).9091```php92// Expose a custom / third-party option key to the get-options & set-option abilities.93add_filter( 'rocket_mcp_options_allowlist', function ( array $allowlist ): array {94 $allowlist[] = 'my_plugin_cache_toggle';95 return $allowlist;96} );9798// RESTRICT — remove sensitive settings you never want AI to change (e.g. CDN + Cloudflare).99add_filter( 'rocket_mcp_options_allowlist', function ( array $allowlist ): array {100 return array_values( array_diff( $allowlist, [101 'cdn', 'cdn_cnames', 'cdn_zone',102 'do_cloudflare', 'cloudflare_auto_settings',103 ] ) );104} );105106// Kill switch — no AI read/write of WP Rocket settings at all.107add_filter( 'rocket_enable_abilities', '__return_false' );108```109110> A key added to the allowlist is readable via `get-options` only if it also has a schema entry in `GetOptions` (the readable set = allowlist ∩ known schema, `array_intersect_key` at [GetOptions.php:416](GetOptions.php)); it is **writable** via `set-option` purely on allowlist membership, and unknown keys fall through `sanitize_value()` unmodified. So when you expose a custom key, prefer wiring your own sanitization on the WP Rocket option save path rather than trusting the pass-through.111112## The MCP OAuth server (opt-in)113114Off until a site owner opts in:115116```php117// Turn the MCP OAuth server ON (rewrite rules, /oauth/* endpoints, discovery, transport).118add_filter( 'rocket_mcp_oauth_server_enabled', '__return_true' );119// After toggling, flush rewrite rules once (Settings > Permalinks, or wp rewrite flush).120```121122What it registers when enabled (verified):123124- **MCP server** at `/wp-json/mcp/mcp-oauth-server` via `WP\MCP\Core\McpAdapter`, exposing `mcp-adapter/discover-abilities`, `get-ability-info`, `execute-ability` over a custom JWT-Bearer `OAuthHttpTransport` — [Server.php:18-41](Server.php).125- **OAuth endpoints** via rewrite rules (query var `mcp_oauth_endpoint`, handled on `template_redirect`) — [Rewrite.php:43-48](Rewrite.php):126 `/oauth/authorize`, `/oauth/authorize-callback`, `/oauth/token`, `/oauth/consent`, `/oauth/revoke`.127- **Discovery documents** (rewrite on `init`) — [Discovery/Endpoints.php:44-52](Endpoints.php):128 `/.well-known/oauth-protected-resource` (RFC 9728), `/.well-known/oauth-authorization-server` (RFC 8414).129130### Trusted publishers (who may connect)131132Client identification uses Client ID Metadata Documents (CIMD). Only client_ids whose host is allowlisted are ever network-fetched (SSRF gate), and the fetched doc must exactly match a pinned client_id. The bundled publisher is **Claude** — verified [ClaudeClientVerifier.php:100-112](ClaudeClientVerifier.php):133134```135claude → client_ids: https://claude.ai/oauth/claude-code-client-metadata136 https://claude.ai/oauth/mcp-oauth-client-metadata137 host: claude.ai138```139140Add another trusted MCP client (can only ADD; the exact client_id match + host gate still apply):141142```php143add_filter( 'rocket_mcp_trusted_publishers', function ( array $publishers ): array {144 $publishers['my-agent'] = [145 'client_ids' => [ 'https://agent.example.com/.well-known/mcp-client' ],146 'host' => 'agent.example.com',147 ];148 return $publishers;149} );150```151152## Security considerations153154- **`set-option` is destructive by declaration.** Anyone (or any AI) that can authenticate as a user holding `rocket_manage_options` can flip caching, CDN, Cloudflare, and database-cleanup settings. Keep the allowlist minimal for the risk profile you accept.155- **Minimize the allowlist**, don't expand it casually. Database-cleanup toggles (`database_*`), `cdn*`, and `cloudflare_*` have real side effects if flipped by a confused agent.156- **The OAuth server is a public network surface when enabled** — leave it off unless a specific integration needs it, and confirm the trusted-publisher list is exactly what you intend.157- **Telemetry**: every ability execution fires `track_event( 'MCP Ability Executed', … )` (WP Media Mixpanel, via `TrackingTrait`) — [GetOptions.php:467](GetOptions.php), [SetOption.php:269](SetOption.php). If your compliance posture forbids that, gate it at WP Rocket's analytics/consent setting.158- **Capability, not nonce, is the gate.** Access hinges entirely on `rocket_manage_options`; do not grant that cap to roles you wouldn't trust to reconfigure caching.159160## Critical rules161162- **Two layers, opposite defaults**: abilities default ON (`rocket_enable_abilities`), OAuth server default OFF (`rocket_mcp_oauth_server_enabled`). Reason about them separately.163- **To disable AI settings access entirely**, use `rocket_enable_abilities => false`; the OAuth-server toggle alone is not enough.164- **`rocket_mcp_options_allowlist` is an ALLOWLIST** — adding exposes, removing restricts. Minimize it.165- **Typed filters** (`wpm_apply_filters_typed`): return the exact declared type — `bool` for the two enable filters, `array` for allowlist / trusted-publishers. Use `__return_true`/`__return_false` for booleans.166- **`set-option` `update_mode`** defaults to `update` (merge) for array/textarea options; pass `replace` to overwrite. Read `get-options` before editing an array option.167- **`rocket_mcp_trusted_publishers` can only ADD** publishers; exact client_id match + host SSRF gate still apply. Never widen a host to a shared domain.168- **Flush rewrite rules** after toggling the OAuth server on/off (the `/oauth/*` and `/.well-known/*` paths are rewrite-based).169- **Feature-detect all three**: WP Rocket 3.23+, `wp_register_ability`, and (for the server) `McpAdapter`.170- **The abilities are behind `rocket_manage_options`** — treat that capability as the real security boundary.171172## Common mistakes173174```php175// WRONG — assuming the allowlist hides keys176add_filter( 'rocket_mcp_options_allowlist', function ( $a ) {177 $a[] = 'stripe_secret_key'; // WRONG: this EXPOSES it to AI read/write178 return $a;179} );180181// RIGHT — allowlist is additive exposure; to hide, remove182add_filter( 'rocket_mcp_options_allowlist', function ( $a ) {183 return array_values( array_diff( $a, [ 'do_cloudflare' ] ) );184} );185186// WRONG — returning the wrong type into a typed filter187add_filter( 'rocket_mcp_oauth_server_enabled', function () {188 return 1; // WRONG: boolean filter; return a real bool189} );190191// RIGHT192add_filter( 'rocket_mcp_oauth_server_enabled', '__return_true' );193194// WRONG — thinking "OAuth server off" means settings are AI-proof195add_filter( 'rocket_mcp_oauth_server_enabled', '__return_false' );196// abilities still registered & REST-exposed behind rocket_manage_options197198// RIGHT — to guarantee no AI settings surface199add_filter( 'rocket_enable_abilities', '__return_false' );200201// WRONG — trusting an arbitrary client via a shared host202add_filter( 'rocket_mcp_trusted_publishers', function ( $p ) {203 $p['x'] = [ 'client_ids' => [ 'https://github.com/foo' ], 'host' => 'github.com' ];204 return $p; // WRONG: shared host = anyone on that host is a candidate client205} );206207// RIGHT — a host you control, with exact client_id URLs208add_filter( 'rocket_mcp_trusted_publishers', function ( $p ) {209 $p['my-agent'] = [210 'client_ids' => [ 'https://agent.example.com/.well-known/mcp-client' ],211 'host' => 'agent.example.com',212 ];213 return $p;214} );215216// WRONG — expecting update_mode 'update' to overwrite an array option217// set-option { option_name: 'cache_reject_uri', option_value: ['/x'] }218// → MERGES '/x' into the existing list, does not replace it219220// RIGHT — pass replace when you mean overwrite221// set-option { option_name: 'cache_reject_uri', option_value: ['/x'], update_mode: 'replace' }222```223224## Cross-references225226- Run **`wp-abilities-api`** for the generic WP Abilities API mechanics (registering abilities, categories, `wp_register_ability`, permission/execute callbacks, `input_schema`/`output_schema`) — WP Rocket's abilities are a concrete consumer of that API.227- Run **`claude-api`** / MCP references when building the client side that connects to this server (Claude as the trusted publisher).228- Run **`wp-rocket-cache-rejection-and-filters`** to understand what the exposed option keys actually do (`cache_reject_uri`, `cdn_*`, `lazyload*`, etc.) before allowlisting them.229- Run **`wp-rocket-cache-invalidation`** when the task is clearing cache after a change rather than exposing settings.230- Run **`wp-security-audit`** / **`wp-security-secrets`** when reviewing whether the allowlist or trusted-publisher list widens the attack surface.231232## What this skill does NOT cover233234- **Building an MCP client** (the Claude side). This is the server/integrator surface only.235- **The WP Abilities API internals** — see `wp-abilities-api`.236- **The `wordpress/mcp-adapter` package internals** (`WP\MCP\*`) — bundled dependency; treat its classes as private and version-volatile. Integrate via WP Rocket's filters and the public abilities.237- **OAuth/JWT protocol internals** (token issuance, JTI/refresh handling, application-password linkage) — private `inc/Engine/MCP/Auth/*` implementation; do not call directly.238- **The WP Rocket settings UI / license / activation** — out of scope for integrators.239240## References241242- Abilities enable gate: [inc/Engine/Abilities/Context.php:19](Context.php) — `wpm_apply_filters_typed( 'boolean', 'rocket_enable_abilities', true )`.243- Allowlist: [inc/Engine/Abilities/Options/AllowedOptions.php:14-102](AllowedOptions.php) — built-in ~70 keys + `rocket_mcp_options_allowlist`.244- `wp-rocket/get-options`: [inc/Engine/Abilities/Options/GetOptions.php:418](GetOptions.php) — readonly, `mcp.public`, cap `rocket_manage_options`.245- `wp-rocket/set-option`: [inc/Engine/Abilities/Options/SetOption.php:158](SetOption.php) — destructive; `input_schema`, `update_mode`, per-type sanitization at [SetOption.php:331](SetOption.php).246- MCP OAuth enable gate: [inc/Engine/MCP/Context.php:23](Context.php) — `wpm_apply_filters_typed( 'boolean', 'rocket_mcp_oauth_server_enabled', false )`.247- MCP server: [inc/Engine/MCP/Transport/Server.php:18](Server.php) — `/wp-json/mcp/mcp-oauth-server`, McpAdapter, ability-adapter tools.248- OAuth endpoints: [inc/Engine/MCP/Auth/Rewrite.php:43](Rewrite.php) — `/oauth/{authorize,authorize-callback,token,consent,revoke}`.249- Discovery: [inc/Engine/MCP/Auth/Discovery/Endpoints.php:44](Endpoints.php) — `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`.250- Trusted publishers (Claude): [inc/Engine/MCP/Auth/ClaudeClientVerifier.php:100](ClaudeClientVerifier.php) — `rocket_mcp_trusted_publishers`.251- Bundled deps (composer.json): `wordpress/mcp-adapter ^0.4.1`, `wp-media/apply-filters-typed ^1.0` (the `wpm_apply_filters_typed` helper), `wp-media/wp-mixpanel` (telemetry).252- Official documentation: <https://docs.wp-rocket.me/article/92-plugin-compatibility-with-wp-rocket>253- Official documentation: <https://docs.wp-rocket.me/article/2-getting-started>