PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.1
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.1, at docs/api/mcp.md

488 lines 26.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 ### Credit confirmation flow
106
107 `imagify/optimize-media`, `imagify/bulk-optimize`, and `imagify/generate-missing-nextgen` consume
108 Imagify quota, so each one is gated by `Imagify\Abilities\AbstractAbility::guard_credit_confirmation()`
109 before its real side effect runs. Because MCP tool calls are made by an AI agent rather than a human
110 clicking a confirm dialog, the "explicit confirmation" requirement is implemented as a two-call
111 protocol instead of a client-side dialog:
112
113 1. **Preview call** — call the ability without `confirm` (or with `confirm: false`). The guard never
114 invokes the ability's real logic; it returns a `confirmation_required` response describing the
115 impact (unit, count, and — for `bulk-optimize`/`generate-missing-nextgen` — the total) and the
116 current quota remaining.
117 2. **Confirmed call** — resend the same call with `confirm: true`. The guard re-checks the API key and
118 quota (they may have changed since the preview) and, if both are still fine, invokes the ability's
119 real logic and returns its normal result.
120
121 The guard runs this exact 4-step sequence on every call, confirmed or not:
122
123 1. `Imagify_Requirements::is_api_key_valid()` — if false, returns `status: invalid_api_key` and stops.
124 2. `Imagify_Requirements::is_over_quota()` — if true, returns `status: insufficient_quota` and stops.
125 3. `$args['confirm'] === true` (strict boolean check; `"true"` as a string does not count) — if not
126 true, returns `status: confirmation_required` and stops.
127 4. Otherwise, runs the ability's real logic and returns its result unchanged.
128
129 A confirmed call is **never** allowed to skip the quota re-check — only the confirmation step is
130 skipped. This closes the race where quota is exhausted between the preview and the confirmed call.
131
132 **`confirmation_required` response shape:**
133
134 | Field | Type | Description |
135 |-------|------|-------------|
136 | `status` | `"confirmation_required"` | |
137 | `message` | string | Human-readable summary of what will happen and how much quota remains. |
138 | `impact` | object | `{ unit, count }` for `optimize-media`; `{ unit, count, total }` for `bulk-optimize` / `generate-missing-nextgen`. |
139 | `quota_remaining` | number | Percentage of quota still available. |
140 | `confirm_with` | object | `{ "confirm": true }` — tells the caller exactly which argument to resend. |
141
142 **`insufficient_quota` response shape:**
143
144 | Field | Type | Description |
145 |-------|------|-------------|
146 | `status` | `"insufficient_quota"` | |
147 | `message` | string | Human-readable explanation that quota is exhausted. |
148 | `next_date_update` | string | ISO date of the next quota reset, or `""` if unknown. |
149 | `upgrade_url` | string | Link to the Imagify subscription/upgrade page. |
150
151 **`invalid_api_key` response shape:**
152
153 | Field | Type | Description |
154 |-------|------|-------------|
155 | `status` | `"invalid_api_key"` | |
156 | `message` | string | Human-readable explanation that the API key is invalid or missing. |
157
158 Every credit-consuming ability's `input_schema` includes a `confirm` boolean property (default
159 `false`, not in `required`) and every credit-consuming ability's `output_schema` `status` enum
160 includes `confirmation_required`, `insufficient_quota`, and `invalid_api_key` alongside its normal
161 success/error values.
162
163 ### `imagify/bulk-optimize`
164
165 **Class:** `Imagify\Abilities\BulkOptimize`
166 **Capability required:** `manage_options`
167 **Exposed via REST:** yes (`show_in_rest: true`)
168 **MCP discoverable:** yes (`mcp.public: true`)
169
170 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.
171
172 **Input schema:**
173
174 | Field | Type | Required | Description |
175 |-------|------|----------|-------------|
176 | `context` | string | yes | `"wp"` for the WordPress media library or `"custom-folders"` for custom folder sources. Enum: `["wp", "custom-folders"]`. |
177 | `optimization_level` | integer (0–2) | no | Overrides the global setting. 0 = normal, 1 = aggressive, 2 = ultra. |
178 | `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). |
179
180 **Output schema:**
181
182 | Field | Type | Description |
183 |-------|------|-------------|
184 | `status` | `"scheduled"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. |
185 | `context` | string | The requested optimization context, echoed back. Present only on `scheduled`/`error`. |
186 | `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `scheduled`/`error`. |
187
188 `get_impact_estimate()`'s `count`/`total` figures come from cheap COUNT-only queries per context:
189 `imagify_count_unoptimized_attachments()` / `imagify_count_attachments()` for `"wp"`,
190 `Imagify_Files_Stats::count_unoptimized_files()` / `count_files()` for `"custom-folders"`. Any
191 context other than `"custom-folders"` (including an unrecognized value or a missing `context` key)
192 is normalized to `"wp"` before the counts and translatable `impact.label` are computed, so the
193 label always matches the branch the counts came from.
194
195 ### `imagify/generate-missing-nextgen`
196
197 **Class:** `Imagify\Abilities\GenerateMissingNextgen`
198 **Capability required:** `manage_options`
199 **Exposed via REST:** yes (`show_in_rest: true`)
200 **MCP discoverable:** yes (`mcp.public: true`)
201
202 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.
203
204 `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.
205
206 **Input schema:**
207
208 | Field | Type | Required | Description |
209 |-------|------|----------|-------------|
210 | `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). |
211
212 **Output schema:**
213
214 | Field | Type | Description |
215 |-------|------|-------------|
216 | `status` | `"scheduled"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. |
217 | `queued_count` | integer | Number of images queued for next-gen generation. Present only on `scheduled`/`error`. |
218 | `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `scheduled`/`error`. |
219
220 `get_impact_estimate()`'s `count` comes from the live `Imagify\Stats\OptimizedMediaWithoutNextGen`
221 stat service (`get_stat()`, injected via DI — not the 2-day cached `get_cached_stat()`, since a stale
222 count could exceed `total` and mislead the credit-spend decision); `total` sums
223 `imagify_count_optimized_attachments() + Imagify_Files_Stats::count_optimized_files()`. `count` is
224 clamped to `total` as a defensive safeguard.
225
226 ### `imagify/optimize-media`
227
228 Registered by `Imagify\Abilities\OptimizeMedia`. Optimizes a specific WordPress media library attachment on demand.
229
230 | Key | Value |
231 |-----|-------|
232 | Slug | `imagify/optimize-media` |
233 | Class | `Imagify\Abilities\OptimizeMedia` |
234 | Permission | `manage_options` capability |
235 | Annotations | `readonly: false`, `destructive: true`, `idempotent: false` |
236 | MCP public | `true` |
237
238 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.
239
240 **Input schema:**
241
242 | Field | Type | Required | Description |
243 |-------|------|----------|-------------|
244 | `media_id` | integer | yes | WordPress attachment ID. |
245 | `optimization_level` | integer (0–2) | no | Overrides the global setting. 0 = normal, 1 = aggressive, 2 = ultra. |
246 | `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). |
247
248 **Output schema:**
249
250 | Field | Type | Description |
251 |-------|------|-------------|
252 | `status` | `"success"` \| `"error"` \| `"confirmation_required"` \| `"insufficient_quota"` \| `"invalid_api_key"` | Result status. |
253 | `original_size` | integer \| null | File size in bytes before optimization, or null on error. Present only on `success`/`error`. |
254 | `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`. |
255 | `savings_percent` | float \| null | Percentage savings, or null on error or when sizes are unavailable. Present only on `success`/`error`. |
256 | `error_message` | string \| null | Human-readable error on failure, null on success. Present only on `success`/`error`. |
257
258 `get_impact_estimate()` always returns `{ unit: "image", count: 1, label: "this media" }` — optimizing
259 a single media always costs exactly one unit, so `impact.total` is omitted from the
260 `confirmation_required` response for this ability.
261
262 ### `imagify/restore-media`
263
264 **Class:** `Imagify\Abilities\RestoreMedia`
265 **Capability required:** `manage_options`
266 **Exposed via REST:** yes (`show_in_rest: true`)
267 **MCP discoverable:** yes (`mcp.public: true`)
268
269 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.
270
271 **Input schema:**
272
273 | Field | Type | Required | Description |
274 |-------|------|----------|-------------|
275 | `media_id` | integer | yes | WordPress attachment ID to restore. |
276
277 **Output schema:**
278
279 | Field | Type | Description |
280 |-------|------|-------------|
281 | `status` | `"success"` \| `"error"` | Result status. |
282 | `restored_size` | integer \| null | Restored original file size in bytes, or null on error. |
283 | `error_message` | string \| null | Human-readable error on failure, null on success. |
284
285 ## Hooks
286
287 ### Filter: `mcp_adapter_default_server_config`
288
289 Subscribed by `ConfigSubscriber::customize_mcp_server()`. Sets `server_name` and `server_description`. All other keys (`server_id`, `server_route`, `tools`) are preserved.
290
291 | Param | Type | Description |
292 |-------|------|-------------|
293 | `$config` | `array` | Default server configuration from the adapter. |
294
295 Returns the modified `$config` array.
296
297 ### Action: `wp_abilities_api_categories_init`
298
299 Subscribed by `AbilitiesSubscriber::register_categories()`. Registers the `imagify` ability category with label `Imagify` and a description string. No-ops on WP < 6.9.
300
301 ### Action: `wp_abilities_api_init`
302
303 Subscribed by `AbilitiesSubscriber::register_abilities()`. Loops over injected `AbilitiesInterface` instances calling `->register()`. No-ops on WP < 6.9.
304
305 ## Abilities
306
307 ### `imagify/update-settings`
308
309 Registered by `Imagify\Abilities\UpdateSettings`. Accepts a partial settings object and updates only the supplied keys.
310
311 | Key | Value |
312 |-----|-------|
313 | Slug | `imagify/update-settings` |
314 | Class | `Imagify\Abilities\UpdateSettings` |
315 | Permission | `manage_options` capability |
316 | Annotations | `readonly: false`, `destructive: false`, `idempotent: true` |
317 | MCP public | `true` |
318
319 **Input:** a partial associative array of Imagify setting key-value pairs. Only supplied keys are changed; others remain unchanged.
320
321 **Output on success:**
322 ```json
323 {
324 "updated": ["<key>", ...],
325 "settings": { "<key>": "<value>", ... }
326 }
327 ```
328 `updated` lists only the keys whose value actually changed. `settings` contains the full post-update settings (excluding `api_key` and `version`).
329
330 **Error codes:**
331 - `imagify_unknown_setting` — a supplied key is not a recognized Imagify setting.
332 - `imagify_invalid_value` — a supplied value fails the constrained-field validation (`optimization_level`, `optimization_format`, `display_nextgen_method`, `display_webp_method`).
333 - `imagify_api_key_immutable` — the `api_key` key was supplied while `IMAGIFY_API_KEY` constant is defined.
334
335 **Constrained fields:**
336 - `optimization_level`: integer `0`, `1`, or `2`
337 - `optimization_format`: `"off"`, `"webp"`, or `"avif"`
338 - `display_nextgen_method` / `display_webp_method`: `"picture"` or `"rewrite"`
339
340 All other keys pass through to `Imagify_Options::set()`, which fires the `sanitize_option_<name>` WP filter for a final sanitization pass.
341
342 ### `imagify/get-account`
343
344 Registered by `Imagify\Abilities\GetAccount`. Returns the current Imagify account status, quota consumption, plan details, and whether the configured API key is valid.
345
346 | Key | Value |
347 |-----|-------|
348 | Slug | `imagify/get-account` |
349 | Class | `Imagify\Abilities\GetAccount` |
350 | Permission | `manage_options` capability |
351 | Annotations | `readonly: true`, `destructive: false`, `idempotent: true` |
352 | MCP public | `true` |
353
354 No input parameters.
355
356 **Output schema:**
357
358 | Field | Type | Description |
359 |-------|------|-------------|
360 | `plan_label` | string | Human-readable plan name (e.g. "Free", "Basic"). Empty string when the API key is invalid. |
361 | `quota` | string \| number | Total monthly quota for the plan. |
362 | `consumed_current_month_quota` | string \| number | Quota consumed in the current billing month. |
363 | `extra_quota` | string \| number | Extra (paid) quota purchased above the plan limit. |
364 | `extra_quota_consumed` | string \| number | Extra quota already consumed this month. |
365 | `next_date_update` | string | ISO date of the next quota reset. Empty string when the API key is invalid. |
366 | `is_api_key_valid` | boolean | `true` when the configured API key is valid; `false` otherwise. |
367
368 When `is_api_key_valid` is `false`, all numeric quota fields return `0` and string fields return `""`.
369
370 ### `imagify/get-media-status`
371
372 Registered by `Imagify\Abilities\GetMediaStatus`. Returns the optimization status and key metrics for a given WordPress media library attachment.
373
374 | Key | Value |
375 |-----|-------|
376 | Slug | `imagify/get-media-status` |
377 | Class | `Imagify\Abilities\GetMediaStatus` |
378 | Permission | `manage_options` capability |
379 | Annotations | `readonly: true`, `destructive: false`, `idempotent: true` |
380 | MCP public | `true` |
381
382 **Input schema:**
383
384 | Field | Type | Required | Description |
385 |-------|------|----------|-------------|
386 | `media_id` | integer | yes | WordPress attachment ID. |
387
388 **Output schema:**
389
390 | Field | Type | Description |
391 |-------|------|-------------|
392 | `status` | `"success"` \| `"error"` \| `"unoptimized"` | Optimization status. `"success"` covers both optimized and already-optimized states; `"unoptimized"` means no optimization data exists yet. |
393 | `optimization_level` | integer \| null | 0 = lossless, 1 = aggressive, 2 = ultra. `null` when not yet optimized. |
394 | `original_size` | integer | File size in bytes before optimization (reads from filesystem when no optimization data exists). |
395 | `optimized_size` | integer | File size in bytes after optimization. `0` when not yet optimized. |
396 | `webp_available` | boolean | `true` when a WebP version of the full-size image has been generated. |
397 | `avif_available` | boolean | `true` when an AVIF version of the full-size image has been generated. |
398 | `error_message` | string \| null | Human-readable error message when `status` is `"error"`. `null` otherwise. |
399
400 **Error behaviour:** If `media_id` is `0` or negative, or the attachment does not exist in the database, returns `status: "error"` with a descriptive `error_message`.
401
402 ### `imagify/get-nextgen-coverage`
403
404 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.
405
406 | Key | Value |
407 |-----|-------|
408 | Slug | `imagify/get-nextgen-coverage` |
409 | Class | `Imagify\Abilities\GetNextgenCoverage` |
410 | Permission | `manage_options` capability |
411 | Annotations | `readonly: true`, `destructive: false`, `idempotent: true` |
412 | MCP public | `true` |
413
414 No input parameters. The count is served from a cached stat (not a live query).
415
416 **Output schema:**
417
418 | Field | Type | Description |
419 |-------|------|-------------|
420 | `missing_nextgen_count` | integer | Number of optimized media items that do not have a next-gen version. |
421 | `nextgen_format` | string | The currently configured next-gen format (`"off"`, `"webp"`, or `"avif"`). |
422
423 ### `imagify/get-settings`
424
425 Registered by `Imagify\Abilities\GetSettings`. Returns all Imagify configuration options and their current values.
426
427 | Key | Value |
428 |-----|-------|
429 | Slug | `imagify/get-settings` |
430 | Class | `Imagify\Abilities\GetSettings` |
431 | Permission | `manage_options` capability |
432 | Annotations | `readonly: true`, `destructive: false`, `idempotent: true` |
433 | MCP public | `true` |
434
435 No input parameters.
436
437 **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.
438
439 The output shape mirrors the input schema of `imagify/update-settings` — callers can pass the returned object back directly as input to `update-settings`.
440
441 ### `imagify/get-stats`
442
443 Registered by `Imagify\Abilities\GetStats`. Returns aggregated optimization statistics for both WP media library and custom folders.
444
445 | Key | Value |
446 |-----|-------|
447 | Slug | `imagify/get-stats` |
448 | Class | `Imagify\Abilities\GetStats` |
449 | Permission | `manage_options` capability |
450 | Annotations | `readonly: true`, `destructive: false`, `idempotent: true` |
451 | MCP public | `true` |
452
453 No input parameters.
454
455 **Output schema:**
456
457 | Field | Type | Description |
458 |-------|------|-------------|
459 | `wp.count_optimized` | integer | Number of successfully optimized WP media attachments. |
460 | `wp.count_errors` | integer | Number of WP media attachments in error state. |
461 | `wp.original_size` | integer | Total original size in bytes across all WP media (before optimization). |
462 | `wp.optimized_size` | integer | Total optimized size in bytes across all WP media (after optimization). |
463 | `wp.savings_percent` | number | Overall savings percentage for WP media. |
464 | `custom-folders.count_optimized` | integer | Number of successfully optimized files in custom folders. |
465 | `custom-folders.count_errors` | integer | Number of files in custom folders in error state. |
466 | `custom-folders.original_size` | integer | Total original size in bytes across all custom folder files. |
467 | `custom-folders.optimized_size` | integer | Total optimized size in bytes across all custom folder files. |
468 | `custom-folders.savings_percent` | number | Overall savings percentage for custom folder files. |
469
470 The `wp` and `custom-folders` objects are always present. Fields default to `0` when no data is available.
471
472 ## Adding a new ability
473
474 1. Create `classes/Abilities/<AbilityName>.php` implementing `AbilitiesInterface`. See `OptimizeMedia` as a reference implementation.
475 2. Add the ability as a shared service and append it to the `AbilitiesSubscriber` arguments in `classes/MCP/ServiceProvider.php`:
476 ```php
477 $this->getContainer()->addShared( MyAbility::class );
478 $this->getContainer()->addShared( AbilitiesSubscriber::class )
479 ->addArguments( [ OptimizeMedia::class, MyAbility::class ] );
480 ```
481 3. The loop in `AbilitiesSubscriber::register_abilities()` calls `->register()` on every injected ability automatically — no manual call is needed.
482 4. Add the class to the `$provides` array in `ServiceProvider`.
483 5. Document the new ability in this file under "Registered abilities".
484
485 ## Patch
486
487 `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`.
488