| 1 |
# MCP Module — Model Context Protocol Foundation |
| 2 |
|
| 3 |
## Overview |
| 4 |
|
| 5 |
The `Imagify\MCP` module integrates Imagify with the WordPress MCP (Model Context Protocol) adapter (`wordpress/mcp-adapter`). It exposes an MCP server endpoint that AI agents can use to discover and invoke Imagify abilities. |
| 6 |
|
| 7 |
The adapter's three built-in tools (`discover-abilities`, `get-ability-info`, `execute-ability`) are always present. Concrete Imagify abilities are defined under `classes/Abilities/`. |
| 8 |
|
| 9 |
## Requirements |
| 10 |
|
| 11 |
- PHP >= 7.4 |
| 12 |
- WordPress >= 6.9 (Abilities API). On WP < 6.9 the module boots but all callbacks no-op silently. |
| 13 |
- The `wordpress/mcp-adapter` package (`^0.5.0`), installed via Composer. |
| 14 |
|
| 15 |
## Boot flow |
| 16 |
|
| 17 |
The adapter is booted inside `imagify_init()` in `inc/main.php`, **after** `$plugin->init($providers)` completes, guarded by: |
| 18 |
|
| 19 |
```php |
| 20 |
class_exists( \WP\MCP\Core\McpAdapter::class ) |
| 21 |
&& function_exists( 'wp_register_ability' ) |
| 22 |
&& function_exists( 'wp_get_ability' ) |
| 23 |
&& function_exists( 'wp_get_abilities' ) |
| 24 |
&& function_exists( 'wp_register_ability_category' ) |
| 25 |
``` |
| 26 |
|
| 27 |
`McpAdapter::instance()` defers its real work to `rest_api_init` (priority 15), so Imagify's subscribers (attached on `plugins_loaded` via `EventManager`) are always listening before the adapter fires `mcp_adapter_init` / `wp_abilities_api_*` actions. |
| 28 |
|
| 29 |
## REST endpoint |
| 30 |
|
| 31 |
| Key | Value | |
| 32 |
|-----|-------| |
| 33 |
| Path | `/wp-json/mcp/mcp-adapter-default-server` | |
| 34 |
| Method | GET / POST (JSON-RPC) | |
| 35 |
| Registered by | `wordpress/mcp-adapter` `DefaultServerFactory::create()` on `mcp_adapter_init` | |
| 36 |
|
| 37 |
The endpoint returns HTTP 200 with the adapter's default three-tool set plus all registered Imagify abilities. |
| 38 |
|
| 39 |
## OAuth (Claude Desktop / MCP clients) |
| 40 |
|
| 41 |
Imagify bundles the shared `wp-media/mcp-oauth` library (`^1.0.2`) to expose an OAuth 2.1 + PKCE authenticated MCP server for clients such as Claude Desktop. Imagify contains **no custom OAuth code** — the library owns the entire flow (authorize / consent / token / revoke endpoints, `.well-known` discovery documents, and the isolated server registration). |
| 42 |
|
| 43 |
The library is booted in `imagify_init()` in `inc/main.php`, guarded by `class_exists( \WPMedia\MCP\OAuth\Bootstrap::class )`, inside the same `$can_boot_mcp_adapter` guard as the `wordpress/mcp-adapter` boot it depends on: |
| 44 |
|
| 45 |
```php |
| 46 |
if ( $can_boot_mcp_adapter ) { |
| 47 |
\WP\MCP\Core\McpAdapter::instance(); |
| 48 |
|
| 49 |
if ( class_exists( \WPMedia\MCP\OAuth\Bootstrap::class ) ) { |
| 50 |
\WPMedia\MCP\OAuth\Bootstrap::instance(); |
| 51 |
} |
| 52 |
} |
| 53 |
``` |
| 54 |
|
| 55 |
The server is enabled by default from `wp-media/mcp-oauth` v1.0.2 onward, so no `wpmedia_mcp_oauth_server_enabled` filter is needed. It can still be disabled by filtering that value to `false`. |
| 56 |
|
| 57 |
| Key | Value | |
| 58 |
|-----|-------| |
| 59 |
| OAuth server path | `/wp-json/mcp/mcp-oauth-server` | |
| 60 |
| Discovery | `/.well-known/oauth-authorization-server`, `/.well-known/oauth-protected-resource` | |
| 61 |
| Tools exposed | `mcp-adapter/discover-abilities`, `mcp-adapter/get-ability-info`, `mcp-adapter/execute-ability` | |
| 62 |
| Auth | OAuth 2.1 + PKCE, JWT bearer tokens | |
| 63 |
|
| 64 |
The OAuth server's fixed tool list is only the three generic mcp-adapter tools, but `discover-abilities` and `execute-ability` read from the **global** WordPress Abilities registry (`wp_get_abilities()` / `wp_get_ability()`), not a fixed category — so all `imagify/*` abilities registered by `AbilitiesSubscriber` are fully discoverable and callable through the OAuth server, with no extra wiring needed. `ConfigSubscriber`'s `mcp_adapter_default_server_config` filter is unaffected — it only customizes the unauthenticated default server, not the OAuth server created by the library. |
| 65 |
|
| 66 |
## Classes |
| 67 |
|
| 68 |
| Class | Responsibility | |
| 69 |
|-------|----------------| |
| 70 |
| `Imagify\Abilities\AbilitiesInterface` | Contract every Imagify MCP ability must implement. | |
| 71 |
| `Imagify\Abilities\AbstractAbility` | Base class for abilities; provides `check_permissions()`, `fire_executed()`, and `guard_credit_confirmation()` (see [](#credit-confirmation-flowCredit confirmation flow](#credit-confirmation-flow](#credit-confirmation-flow)). | |
| 72 |
| `Imagify\Abilities\CreditConsumingAbilityInterface` | Contract for abilities that spend Imagify quota (`get_impact_estimate()`); opts an ability into `guard_credit_confirmation()`. Implemented by `OptimizeMedia`, `BulkOptimize`, `GenerateMissingNextgen`. | |
| 73 |
| `Imagify\Abilities\BulkOptimize` | MCP ability: schedule a bulk image optimization run (`imagify/bulk-optimize`). | |
| 74 |
| `Imagify\Abilities\GenerateMissingNextgen` | MCP ability: queue generation of missing next-gen (WebP/AVIF) versions (`imagify/generate-missing-nextgen`). | |
| 75 |
| `Imagify\Abilities\GetAccount` | Ability `imagify/get-account` — returns account quota, plan details, and API key validity. | |
| 76 |
| `Imagify\Abilities\GetMediaStatus` | Ability `imagify/get-media-status` — returns optimization status and metrics for a media attachment. | |
| 77 |
| `Imagify\Abilities\GetNextgenCoverage` | Ability `imagify/get-nextgen-coverage` — returns the count of optimized images missing a next-gen version and the configured format. | |
| 78 |
| `Imagify\Abilities\GetSettings` | Ability `imagify/get-settings` — returns all Imagify configuration options (redacting `api_key` and `version`). | |
| 79 |
| `Imagify\Abilities\GetStats` | Ability `imagify/get-stats` — returns optimization statistics for WP media and custom folders. | |
| 80 |
| `Imagify\Abilities\OptimizeMedia` | Ability `imagify/optimize-media` — optimizes a WP media attachment on demand. | |
| 81 |
| `Imagify\Abilities\RestoreMedia` | MCP ability: restore an optimized media to its original state via backup (`imagify/restore-media`). | |
| 82 |
| `Imagify\Abilities\UpdateSettings` | MCP ability: updates one or more Imagify configuration settings. | |
| 83 |
| `Imagify\MCP\ConfigSubscriber` | Customizes the MCP server name and description via `mcp_adapter_default_server_config`. | |
| 84 |
| `Imagify\MCP\AbilitiesSubscriber` | Registers the `imagify` ability category and all injected abilities. | |
| 85 |
| `Imagify\MCP\ServiceProvider` | DI wiring — registered in `config/providers.php`. | |
| 86 |
|
| 87 |
## AbilitiesInterface contract |
| 88 |
|
| 89 |
```php |
| 90 |
namespace Imagify\Abilities; |
| 91 |
|
| 92 |
interface AbilitiesInterface { |
| 93 |
public function register(): void; |
| 94 |
public function check_permissions(): bool; |
| 95 |
public function execute(); |
| 96 |
} |
| 97 |
``` |
| 98 |
|
| 99 |
- `register()` — calls `wp_register_ability()` (guarded by `function_exists`) wiring `execute_callback` and `permission_callback`. |
| 100 |
- `check_permissions()` — returns `current_user_can( 'manage_options' )` (per epic #1097 spec). |
| 101 |
- `execute()` — returns the tool-result value (array, string, or any MCP-compatible type). |
| 102 |
|
| 103 |
## Registered abilities |
| 104 |
|
| 105 |
### Identifying a media |
| 106 |
|
| 107 |
`imagify/optimize-media`, `imagify/restore-media` and `imagify/get-media-status` accept three |
| 108 |
interchangeable identifiers, so a caller that only knows the file name or the URL of an image does |
| 109 |
not have to look its attachment ID up first. None of the three is required on its own — exactly one |
| 110 |
must be supplied (hence `no*` in the input tables): |
| 111 |
|
| 112 |
| Input | Resolution | |
| 113 |
|-------|------------| |
| 114 |
| `media_id` | Used as-is. | |
| 115 |
| `media_url` | `attachment_url_to_postid()`. Next-gen and thumbnail URLs (`hero-300x200.jpg.webp`) fall back to a file-name lookup on the original. | |
| 116 |
| `media_filename` | Exact, case-insensitive match on the base name of the `_wp_attached_file` meta. A full relative path (`2026/08/hero.jpg`) is accepted too. | |
| 117 |
|
| 118 |
`media_id` wins over `media_url`, which wins over `media_filename`. A `media_id` of `0` or less is |
| 119 |
treated as absent, so the next identifier is tried. |
| 120 |
|
| 121 |
Errors are explicit and actionable rather than best-effort: |
| 122 |
|
| 123 |
| Error code | When | |
| 124 |
|------------|------| |
| 125 |
| `imagify_missing_media_identifier` | None of the three inputs was usable. | |
| 126 |
| `imagify_media_not_found` | The URL or file name matches nothing in the library. | |
| 127 |
| `imagify_ambiguous_media_filename` | Several attachments share the file name. The message lists the matching IDs so the caller can retry with `media_id`. | |
| 128 |
|
| 129 |
Resolution lives in `Imagify\Abilities\MediaResolver::resolve_id()`, which returns an attachment ID |
| 130 |
or a `WP_Error`. Each ability turns that `WP_Error` into its own `status: "error"` output shape. |
| 131 |
|
| 132 |
### Credit confirmation flow |
| 133 |
|
| 134 |
`imagify/optimize-media`, `imagify/bulk-optimize`, and `imagify/generate-missing-nextgen` consume |
| 135 |
Imagify quota, so each one is gated by `Imagify\Abilities\AbstractAbility::guard_credit_confirmation()` |
| 136 |
before its real side effect runs. Because MCP tool calls are made by an AI agent rather than a human |
| 137 |
clicking a confirm dialog, the "explicit confirmation" requirement is implemented as a two-call |
| 138 |
protocol instead of a client-side dialog: |
| 139 |
|
| 140 |
1. **Preview call** — call the ability without `confirm` (or with `confirm: false`). The guard never |
| 141 |
invokes the ability's real logic; it returns a `confirmation_required` response describing the |
| 142 |
impact (unit, count, and — for `bulk-optimize`/`generate-missing-nextgen` — the total) and the |
| 143 |
current quota remaining. |
| 144 |
2. **Confirmed call** — resend the same call with `confirm: true`. The guard re-checks the API key and |
| 145 |
quota (they may have changed since the preview) and, if both are still fine, invokes the ability's |
| 146 |
real logic and returns its normal result. |
| 147 |
|
| 148 |
The guard runs this exact 4-step sequence on every call, confirmed or not: |
| 149 |
|
| 150 |
1. `Imagify_Requirements::is_api_key_valid()` — if false, returns `status: invalid_api_key` and stops. |
| 151 |
2. `Imagify_Requirements::is_over_quota()` — if true, returns `status: insufficient_quota` and stops. |
| 152 |
3. `$args['confirm'] === true` (strict boolean check; `"true"` as a string does not count) — if not |
| 153 |
true, returns `status: confirmation_required` and stops. |
| 154 |
4. Otherwise, runs the ability's real logic and returns its result unchanged. |
| 155 |
|
| 156 |
A confirmed call is **never** allowed to skip the quota re-check — only the confirmation step is |
| 157 |
skipped. This closes the race where quota is exhausted between the preview and the confirmed call. |
| 158 |
|
| 159 |
**`confirmation_required` response shape:** |
| 160 |
|
| 161 |
| Field | Type | Description | |
| 162 |
|-------|------|-------------| |
| 163 |
| `status` | `"confirmation_required"` | | |
| 164 |
| `message` | string | Human-readable summary of what will happen and how much quota remains. | |
| 165 |
| `impact` | object | `{ unit, count }` for `optimize-media`; `{ unit, count, total }` for `bulk-optimize` / `generate-missing-nextgen`. | |
| 166 |
| `quota_remaining` | number | Percentage of quota still available. | |
| 167 |
| `confirm_with` | object | `{ "confirm": true }` — tells the caller exactly which argument to resend. | |
| 168 |
|
| 169 |
**`insufficient_quota` response shape:** |
| 170 |
|
| 171 |
| Field | Type | Description | |
| 172 |
|-------|------|-------------| |
| 173 |
| `status` | `"insufficient_quota"` | | |
| 174 |
| `message` | string | Human-readable explanation that quota is exhausted. | |
| 175 |
| `next_date_update` | string | ISO date of the next quota reset, or `""` if unknown. | |
| 176 |
| `upgrade_url` | string | Link to the Imagify subscription/upgrade page. | |
| 177 |
|
| 178 |
**`invalid_api_key` response shape:** |
| 179 |
|
| 180 |
| Field | Type | Description | |
| 181 |
|-------|------|-------------| |
| 182 |
| `status` | `"invalid_api_key"` | | |
| 183 |
| `message` | string | Human-readable explanation that the API key is invalid or missing. | |
| 184 |
|
| 185 |
Every credit-consuming ability's `input_schema` includes a `confirm` boolean property (default |
| 186 |
`false`, not in `required`) and every credit-consuming ability's `output_schema` `status` enum |
| 187 |
includes `confirmation_required`, `insufficient_quota`, and `invalid_api_key` alongside its normal |
| 188 |
success/error values. |
| 189 |
|
| 190 |
### `imagify/bulk-optimize` |
| 191 |
|
| 192 |
**Class:** `Imagify\Abilities\BulkOptimize` |
| 193 |
**Capability required:** `manage_options` |
| 194 |
**Exposed via REST:** yes (`show_in_rest: true`) |
| 195 |
**MCP discoverable:** yes (`mcp.public: true`) |
| 196 |
|
| 197 |
Schedules a bulk image optimization run for the WordPress media library or custom folders. The operation is asynchronous — the ability returns immediately after dispatching via Action Scheduler / WP-Cron. |
| 198 |
|
| 199 |
**Input schema:** |
| 200 |
|
| 201 |
| Field | Type | Required | Description | |
| 202 |
|-------|------|----------|-------------| |
| 203 |
| `context` | string | yes | `"wp"` for the WordPress media library or `"custom-folders"` for custom folder sources. Enum: `["wp", "custom-folders"]`. | |
| 204 |
| `optimization_level` | integer (0–2) | no | Overrides the global setting. 0 = normal, 1 = aggressive, 2 = ultra. | |
| 205 |
| `confirm` | boolean | no | Set to `true` to execute after reviewing the credit-consumption preview returned by a prior call without this flag. Defaults to `false`. See [](#credit-confirmation-flowCredit confirmation flow](#credit-confirmation-flow](#credit-confirmation-flow). | |
| 206 |
|
| 207 |
**Output schema:** |
| 208 |
|
| 209 |
| Field | Type | Description | |
| 210 |
|-------|------|-------------| |
| 211 |
| `status` | `"scheduled"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. | |
| 212 |
| `context` | string | The requested optimization context, echoed back. Present only on `scheduled`/`error`. | |
| 213 |
| `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `scheduled`/`error`. | |
| 214 |
|
| 215 |
`get_impact_estimate()`'s `count`/`total` figures come from cheap COUNT-only queries per context: |
| 216 |
`imagify_count_unoptimized_attachments()` / `imagify_count_attachments()` for `"wp"`, |
| 217 |
`Imagify_Files_Stats::count_unoptimized_files()` / `count_files()` for `"custom-folders"`. Any |
| 218 |
context other than `"custom-folders"` (including an unrecognized value or a missing `context` key) |
| 219 |
is normalized to `"wp"` before the counts and translatable `impact.label` are computed, so the |
| 220 |
label always matches the branch the counts came from. |
| 221 |
|
| 222 |
### `imagify/generate-missing-nextgen` |
| 223 |
|
| 224 |
**Class:** `Imagify\Abilities\GenerateMissingNextgen` |
| 225 |
**Capability required:** `manage_options` |
| 226 |
**Exposed via REST:** yes (`show_in_rest: true`) |
| 227 |
**MCP discoverable:** yes (`mcp.public: true`) |
| 228 |
|
| 229 |
Queues generation of missing next-gen (WebP/AVIF) versions for all optimized media by delegating to `Bulk::run_generate_nextgen()`. Runs asynchronously via Action Scheduler. |
| 230 |
|
| 231 |
`status=scheduled` is returned both when jobs were enqueued (`queued_count > 0`) and when there is nothing to generate (`queued_count=0`). The latter is a successful no-op, not an error. |
| 232 |
|
| 233 |
**Input schema:** |
| 234 |
|
| 235 |
| Field | Type | Required | Description | |
| 236 |
|-------|------|----------|-------------| |
| 237 |
| `confirm` | boolean | no | Set to `true` to execute after reviewing the credit-consumption preview returned by a prior call without this flag. Defaults to `false`. See [](#credit-confirmation-flowCredit confirmation flow](#credit-confirmation-flow](#credit-confirmation-flow). | |
| 238 |
|
| 239 |
**Output schema:** |
| 240 |
|
| 241 |
| Field | Type | Description | |
| 242 |
|-------|------|-------------| |
| 243 |
| `status` | `"scheduled"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. | |
| 244 |
| `queued_count` | integer | Number of images queued for next-gen generation. Present only on `scheduled`/`error`. | |
| 245 |
| `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `scheduled`/`error`. | |
| 246 |
|
| 247 |
`get_impact_estimate()`'s `count` comes from the live `Imagify\Stats\OptimizedMediaWithoutNextGen` |
| 248 |
stat service (`get_stat()`, injected via DI — not the 2-day cached `get_cached_stat()`, since a stale |
| 249 |
count could exceed `total` and mislead the credit-spend decision); `total` sums |
| 250 |
`imagify_count_optimized_attachments() + Imagify_Files_Stats::count_optimized_files()`. `count` is |
| 251 |
clamped to `total` as a defensive safeguard. |
| 252 |
|
| 253 |
### `imagify/optimize-media` |
| 254 |
|
| 255 |
Registered by `Imagify\Abilities\OptimizeMedia`. Optimizes a specific WordPress media library attachment on demand. |
| 256 |
|
| 257 |
| Key | Value | |
| 258 |
|-----|-------| |
| 259 |
| Slug | `imagify/optimize-media` | |
| 260 |
| Class | `Imagify\Abilities\OptimizeMedia` | |
| 261 |
| Permission | `manage_options` capability | |
| 262 |
| Annotations | `readonly: false`, `destructive: true`, `idempotent: false` | |
| 263 |
| MCP public | `true` | |
| 264 |
|
| 265 |
Delegates to `Imagify\Optimization\Process\WP::optimize()` for first-time optimization or `::reoptimize()` when the media has already been processed. Because both methods queue asynchronous background jobs, the `optimized_size` and `savings_percent` fields reflect data already stored in post meta at the time of the call. Clients should poll `imagify/get-media-status` to track the final result. |
| 266 |
|
| 267 |
**Input schema:** |
| 268 |
|
| 269 |
| Field | Type | Required | Description | |
| 270 |
|-------|------|----------|-------------| |
| 271 |
| `media_id` | integer | no* | WordPress attachment ID. | |
| 272 |
| `media_filename` | string | no* | File name of the media, for example `hero-banner.jpg`. | |
| 273 |
| `media_url` | string | no* | Absolute URL of the media. | |
| 274 |
| `optimization_level` | integer (0–2) | no | Overrides the global setting. 0 = normal, 1 = aggressive, 2 = ultra. | |
| 275 |
| `confirm` | boolean | no | Set to `true` to execute after reviewing the credit-consumption preview returned by a prior call without this flag. Defaults to `false`. See [](#credit-confirmation-flowCredit confirmation flow](#credit-confirmation-flow](#credit-confirmation-flow). | |
| 276 |
|
| 277 |
**Output schema:** |
| 278 |
|
| 279 |
| Field | Type | Description | |
| 280 |
|-------|------|-------------| |
| 281 |
| `status` | `"success"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. | |
| 282 |
| `original_size` | integer \| null | File size in bytes before optimization, or null on error. Present only on `success`/`error`. | |
| 283 |
| `optimized_size` | integer \| null | File size in bytes after optimization (may be null if the background job is not yet complete). Present only on `success`/`error`. | |
| 284 |
| `savings_percent` | float \| null | Percentage savings, or null on error or when sizes are unavailable. Present only on `success`/`error`. | |
| 285 |
| `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `success`/`error`. | |
| 286 |
|
| 287 |
`get_impact_estimate()` always returns `{ unit: "image", count: 1, label: "this media" }` — optimizing |
| 288 |
a single media always costs exactly one unit, so `impact.total` is omitted from the |
| 289 |
`confirmation_required` response for this ability. |
| 290 |
|
| 291 |
### `imagify/restore-media` |
| 292 |
|
| 293 |
**Class:** `Imagify\Abilities\RestoreMedia` |
| 294 |
**Capability required:** `manage_options` |
| 295 |
**Exposed via REST:** yes (`show_in_rest: true`) |
| 296 |
**MCP discoverable:** yes (`mcp.public: true`) |
| 297 |
|
| 298 |
Restores an optimized media to its original state using the stored backup file. Requires that backup was enabled (`backup: 1` in settings) at the time of optimization. |
| 299 |
|
| 300 |
**Input schema:** |
| 301 |
|
| 302 |
| Field | Type | Required | Description | |
| 303 |
|-------|------|----------|-------------| |
| 304 |
| `media_id` | integer | no* | WordPress attachment ID to restore. | |
| 305 |
| `media_filename` | string | no* | File name of the media, for example `hero-banner.jpg`. | |
| 306 |
| `media_url` | string | no* | Absolute URL of the media. | |
| 307 |
|
| 308 |
**Output schema:** |
| 309 |
|
| 310 |
| Field | Type | Description | |
| 311 |
|-------|------|-------------| |
| 312 |
| `status` | `"success"` \| `"error"` | Result status. | |
| 313 |
| `restored_size` | integer \| null | Restored original file size in bytes, or null on error. | |
| 314 |
| `error_message` | string \| null | Human-readable error on failure, null on success. | |
| 315 |
|
| 316 |
## Hooks |
| 317 |
|
| 318 |
### Filter: `mcp_adapter_default_server_config` |
| 319 |
|
| 320 |
Subscribed by `ConfigSubscriber::customize_mcp_server()`. Sets `server_name` and `server_description`. All other keys (`server_id`, `server_route`, `tools`) are preserved. |
| 321 |
|
| 322 |
| Param | Type | Description | |
| 323 |
|-------|------|-------------| |
| 324 |
| `$config` | `array` | Default server configuration from the adapter. | |
| 325 |
|
| 326 |
Returns the modified `$config` array. |
| 327 |
|
| 328 |
### Action: `wp_abilities_api_categories_init` |
| 329 |
|
| 330 |
Subscribed by `AbilitiesSubscriber::register_categories()`. Registers the `imagify` ability category with label `Imagify` and a description string. No-ops on WP < 6.9. |
| 331 |
|
| 332 |
### Action: `wp_abilities_api_init` |
| 333 |
|
| 334 |
Subscribed by `AbilitiesSubscriber::register_abilities()`. Loops over injected `AbilitiesInterface` instances calling `->register()`. No-ops on WP < 6.9. |
| 335 |
|
| 336 |
## Abilities |
| 337 |
|
| 338 |
### `imagify/update-settings` |
| 339 |
|
| 340 |
Registered by `Imagify\Abilities\UpdateSettings`. Accepts a partial settings object and updates only the supplied keys. |
| 341 |
|
| 342 |
| Key | Value | |
| 343 |
|-----|-------| |
| 344 |
| Slug | `imagify/update-settings` | |
| 345 |
| Class | `Imagify\Abilities\UpdateSettings` | |
| 346 |
| Permission | `manage_options` capability | |
| 347 |
| Annotations | `readonly: false`, `destructive: false`, `idempotent: true` | |
| 348 |
| MCP public | `true` | |
| 349 |
|
| 350 |
**Input:** a partial associative array of Imagify setting key-value pairs. Only supplied keys are changed; others remain unchanged. |
| 351 |
|
| 352 |
**Output on success:** |
| 353 |
```json |
| 354 |
{ |
| 355 |
"updated": ["<key>", ...], |
| 356 |
"settings": { "<key>": "<value>", ... } |
| 357 |
} |
| 358 |
``` |
| 359 |
`updated` lists only the keys whose value actually changed. `settings` contains the full post-update settings (excluding `api_key` and `version`). |
| 360 |
|
| 361 |
**Error codes:** |
| 362 |
- `imagify_unknown_setting` — a supplied key is not a recognized Imagify setting. |
| 363 |
- `imagify_invalid_value` — a supplied value fails the constrained-field validation (`optimization_level`, `optimization_format`, `display_nextgen_method`, `display_webp_method`). |
| 364 |
- `imagify_api_key_immutable` — the `api_key` key was supplied while `IMAGIFY_API_KEY` constant is defined. |
| 365 |
|
| 366 |
**Constrained fields:** |
| 367 |
- `optimization_level`: integer `0`, `1`, or `2` |
| 368 |
- `optimization_format`: `"off"`, `"webp"`, or `"avif"` |
| 369 |
- `display_nextgen_method` / `display_webp_method`: `"picture"` or `"rewrite"` |
| 370 |
|
| 371 |
All other keys pass through to `Imagify_Options::set()`, which fires the `sanitize_option_<name>` WP filter for a final sanitization pass. |
| 372 |
|
| 373 |
### `imagify/get-account` |
| 374 |
|
| 375 |
Registered by `Imagify\Abilities\GetAccount`. Returns the current Imagify account status, quota consumption, plan details, and whether the configured API key is valid. |
| 376 |
|
| 377 |
| Key | Value | |
| 378 |
|-----|-------| |
| 379 |
| Slug | `imagify/get-account` | |
| 380 |
| Class | `Imagify\Abilities\GetAccount` | |
| 381 |
| Permission | `manage_options` capability | |
| 382 |
| Annotations | `readonly: true`, `destructive: false`, `idempotent: true` | |
| 383 |
| MCP public | `true` | |
| 384 |
|
| 385 |
No input parameters. |
| 386 |
|
| 387 |
**Output schema:** |
| 388 |
|
| 389 |
| Field | Type | Description | |
| 390 |
|-------|------|-------------| |
| 391 |
| `plan_label` | string | Human-readable plan name (e.g. "Free", "Basic"). Empty string when the API key is invalid. | |
| 392 |
| `quota` | string \| number | Total monthly quota for the plan. | |
| 393 |
| `consumed_current_month_quota` | string \| number | Quota consumed in the current billing month. | |
| 394 |
| `extra_quota` | string \| number | Extra (paid) quota purchased above the plan limit. | |
| 395 |
| `extra_quota_consumed` | string \| number | Extra quota already consumed this month. | |
| 396 |
| `next_date_update` | string | ISO date of the next quota reset. Empty string when the API key is invalid. | |
| 397 |
| `is_api_key_valid` | boolean | `true` when the configured API key is valid; `false` otherwise. | |
| 398 |
|
| 399 |
When `is_api_key_valid` is `false`, all numeric quota fields return `0` and string fields return `""`. |
| 400 |
|
| 401 |
### `imagify/get-media-status` |
| 402 |
|
| 403 |
Registered by `Imagify\Abilities\GetMediaStatus`. Returns the optimization status and key metrics for a given WordPress media library attachment. |
| 404 |
|
| 405 |
| Key | Value | |
| 406 |
|-----|-------| |
| 407 |
| Slug | `imagify/get-media-status` | |
| 408 |
| Class | `Imagify\Abilities\GetMediaStatus` | |
| 409 |
| Permission | `manage_options` capability | |
| 410 |
| Annotations | `readonly: true`, `destructive: false`, `idempotent: true` | |
| 411 |
| MCP public | `true` | |
| 412 |
|
| 413 |
**Input schema:** |
| 414 |
|
| 415 |
| Field | Type | Required | Description | |
| 416 |
|-------|------|----------|-------------| |
| 417 |
| `media_id` | integer | no* | WordPress attachment ID. | |
| 418 |
| `media_filename` | string | no* | File name of the media, for example `hero-banner.jpg`. | |
| 419 |
| `media_url` | string | no* | Absolute URL of the media. | |
| 420 |
|
| 421 |
**Output schema:** |
| 422 |
|
| 423 |
| Field | Type | Description | |
| 424 |
|-------|------|-------------| |
| 425 |
| `status` | `"success"` \| `"error"` \| `"unoptimized"` | Optimization status. `"success"` covers both optimized and already-optimized states; `"unoptimized"` means no optimization data exists yet. | |
| 426 |
| `optimization_level` | integer \| null | 0 = lossless, 1 = aggressive, 2 = ultra. `null` when not yet optimized. | |
| 427 |
| `original_size` | integer | File size in bytes before optimization (reads from filesystem when no optimization data exists). | |
| 428 |
| `optimized_size` | integer | File size in bytes after optimization. `0` when not yet optimized. | |
| 429 |
| `webp_available` | boolean | `true` when a WebP version of the full-size image has been generated. | |
| 430 |
| `avif_available` | boolean | `true` when an AVIF version of the full-size image has been generated. | |
| 431 |
| `error_message` | string \| null | Human-readable error message when `status` is `"error"`. `null` otherwise. | |
| 432 |
|
| 433 |
**Error behaviour:** If no identifier resolves to an attachment, returns `status: "error"` with a descriptive `error_message`. See [](#identifying-a-mediaIdentifying a media](#identifying-a-media](#identifying-a-media). |
| 434 |
|
| 435 |
### `imagify/get-nextgen-coverage` |
| 436 |
|
| 437 |
Registered by `Imagify\Abilities\GetNextgenCoverage`. Returns the number of optimized images that are missing a next-gen (WebP/AVIF) version, and the next-gen format currently configured in Imagify settings. |
| 438 |
|
| 439 |
| Key | Value | |
| 440 |
|-----|-------| |
| 441 |
| Slug | `imagify/get-nextgen-coverage` | |
| 442 |
| Class | `Imagify\Abilities\GetNextgenCoverage` | |
| 443 |
| Permission | `manage_options` capability | |
| 444 |
| Annotations | `readonly: true`, `destructive: false`, `idempotent: true` | |
| 445 |
| MCP public | `true` | |
| 446 |
|
| 447 |
No input parameters. The count is served from a cached stat (not a live query). |
| 448 |
|
| 449 |
**Output schema:** |
| 450 |
|
| 451 |
| Field | Type | Description | |
| 452 |
|-------|------|-------------| |
| 453 |
| `missing_nextgen_count` | integer | Number of optimized media items that do not have a next-gen version. | |
| 454 |
| `nextgen_format` | string | The currently configured next-gen format (`"off"`, `"webp"`, or `"avif"`). | |
| 455 |
|
| 456 |
### `imagify/get-settings` |
| 457 |
|
| 458 |
Registered by `Imagify\Abilities\GetSettings`. Returns all Imagify configuration options and their current values. |
| 459 |
|
| 460 |
| Key | Value | |
| 461 |
|-----|-------| |
| 462 |
| Slug | `imagify/get-settings` | |
| 463 |
| Class | `Imagify\Abilities\GetSettings` | |
| 464 |
| Permission | `manage_options` capability | |
| 465 |
| Annotations | `readonly: true`, `destructive: false`, `idempotent: true` | |
| 466 |
| MCP public | `true` | |
| 467 |
|
| 468 |
No input parameters. |
| 469 |
|
| 470 |
**Output:** A flat associative object containing all Imagify options. The `api_key` and `version` keys are always omitted from the response to avoid leaking credentials and internal metadata. |
| 471 |
|
| 472 |
The output shape mirrors the input schema of `imagify/update-settings` — callers can pass the returned object back directly as input to `update-settings`. |
| 473 |
|
| 474 |
### `imagify/get-stats` |
| 475 |
|
| 476 |
Registered by `Imagify\Abilities\GetStats`. Returns aggregated optimization statistics for both WP media library and custom folders. |
| 477 |
|
| 478 |
| Key | Value | |
| 479 |
|-----|-------| |
| 480 |
| Slug | `imagify/get-stats` | |
| 481 |
| Class | `Imagify\Abilities\GetStats` | |
| 482 |
| Permission | `manage_options` capability | |
| 483 |
| Annotations | `readonly: true`, `destructive: false`, `idempotent: true` | |
| 484 |
| MCP public | `true` | |
| 485 |
|
| 486 |
No input parameters. |
| 487 |
|
| 488 |
**Output schema:** |
| 489 |
|
| 490 |
| Field | Type | Description | |
| 491 |
|-------|------|-------------| |
| 492 |
| `wp.count_optimized` | integer | Number of successfully optimized WP media attachments. | |
| 493 |
| `wp.count_errors` | integer | Number of WP media attachments in error state. | |
| 494 |
| `wp.original_size` | integer | Total original size in bytes across all WP media (before optimization). | |
| 495 |
| `wp.optimized_size` | integer | Total optimized size in bytes across all WP media (after optimization). | |
| 496 |
| `wp.savings_percent` | number | Overall savings percentage for WP media. | |
| 497 |
| `custom-folders.count_optimized` | integer | Number of successfully optimized files in custom folders. | |
| 498 |
| `custom-folders.count_errors` | integer | Number of files in custom folders in error state. | |
| 499 |
| `custom-folders.original_size` | integer | Total original size in bytes across all custom folder files. | |
| 500 |
| `custom-folders.optimized_size` | integer | Total optimized size in bytes across all custom folder files. | |
| 501 |
| `custom-folders.savings_percent` | number | Overall savings percentage for custom folder files. | |
| 502 |
|
| 503 |
The `wp` and `custom-folders` objects are always present. Fields default to `0` when no data is available. |
| 504 |
|
| 505 |
## Adding a new ability |
| 506 |
|
| 507 |
1. Create `classes/Abilities/<AbilityName>.php` implementing `AbilitiesInterface`. See `OptimizeMedia` as a reference implementation. |
| 508 |
2. Add the ability as a shared service and append it to the `AbilitiesSubscriber` arguments in `classes/MCP/ServiceProvider.php`: |
| 509 |
```php |
| 510 |
$this->getContainer()->addShared( MyAbility::class ); |
| 511 |
$this->getContainer()->addShared( AbilitiesSubscriber::class ) |
| 512 |
->addArguments( [ OptimizeMedia::class, MyAbility::class ] ); |
| 513 |
``` |
| 514 |
3. The loop in `AbilitiesSubscriber::register_abilities()` calls `->register()` on every injected ability automatically — no manual call is needed. |
| 515 |
4. Add the class to the `$provides` array in `ServiceProvider`. |
| 516 |
5. Document the new ability in this file under "Registered abilities". |
| 517 |
|
| 518 |
## Patch |
| 519 |
|
| 520 |
`wordpress/mcp-adapter` contains a PHP 8.1+ deprecated static-trait-method call in `RequestRouter.php`. The patch at `patches/wordpress/mcp-adapter/fix-static-trait-call.patch` is applied automatically during `composer install` via `cweagans/composer-patches`. |
| 521 |
|