PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.3
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.3
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / docs / api / mcp.md

mcp.md in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.3.3, at docs/api/mcp.md

521 lines 28.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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