PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
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
← All changes | docs/api/mcp.md +220 -6 2.3.0 → trunk View file →
@@ -35,13 +35,44 @@
35 35 | Registered by | `wordpress/mcp-adapter` `DefaultServerFactory::create()` on `mcp_adapter_init` |
36 36
37 37 The endpoint returns HTTP 200 with the adapter's default three-tool set plus all registered Imagify abilities.
38 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 +
39 66 ## Classes
40 67
41 68 | Class | Responsibility |
42 69 |-------|----------------|
43 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 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`). |
44 75 | `Imagify\Abilities\GetAccount` | Ability `imagify/get-account` — returns account quota, plan details, and API key validity. |
45 76 | `Imagify\Abilities\GetMediaStatus` | Ability `imagify/get-media-status` — returns optimization status and metrics for a media attachment. |
46 77 | `Imagify\Abilities\GetNextgenCoverage` | Ability `imagify/get-nextgen-coverage` — returns the count of optimized images missing a next-gen version and the configured format. |
47 78 | `Imagify\Abilities\GetSettings` | Ability `imagify/get-settings` — returns all Imagify configuration options (redacting `api_key` and `version`). |
@@ -46,8 +77,9 @@
46 77 | `Imagify\Abilities\GetNextgenCoverage` | Ability `imagify/get-nextgen-coverage` — returns the count of optimized images missing a next-gen version and the configured format. |
47 78 | `Imagify\Abilities\GetSettings` | Ability `imagify/get-settings` — returns all Imagify configuration options (redacting `api_key` and `version`). |
48 79 | `Imagify\Abilities\GetStats` | Ability `imagify/get-stats` — returns optimization statistics for WP media and custom folders. |
49 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`). |
50 82 | `Imagify\Abilities\UpdateSettings` | MCP ability: updates one or more Imagify configuration settings. |
51 83 | `Imagify\MCP\ConfigSubscriber` | Customizes the MCP server name and description via `mcp_adapter_default_server_config`. |
52 84 | `Imagify\MCP\AbilitiesSubscriber` | Registers the `imagify` ability category and all injected abilities. |
53 85 | `Imagify\MCP\ServiceProvider` | DI wiring — registered in `config/providers.php`. |
@@ -69,8 +101,156 @@
69 101 - `execute()` — returns the tool-result value (array, string, or any MCP-compatible type).
70 102
71 103 ## Registered abilities
72 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 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 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 +
73 253 ### `imagify/optimize-media`
74 254
75 255 Registered by `Imagify\Abilities\OptimizeMedia`. Optimizes a specific WordPress media library attachment on demand.
76 256
@@ -87,19 +267,51 @@
87 267 **Input schema:**
88 268
89 269 | Field | Type | Required | Description |
90 270 |-------|------|----------|-------------|
91 -| `media_id` | integer | yes | WordPress attachment ID. |
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. |
92 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 flow](#credit-confirmation-flow). |
93 276
94 277 **Output schema:**
95 278
96 279 | Field | Type | Description |
97 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 +|-------|------|-------------|
98 312 | `status` | `"success"` \| `"error"` | Result status. |
99 -| `original_size` | integer \| null | File size in bytes before optimization, or null on error. |
100 -| `optimized_size` | integer \| null | File size in bytes after optimization (may be null if the background job is not yet complete). |
101 -| `savings_percent` | float \| null | Percentage savings, or null on error or when sizes are unavailable. |
313 +| `restored_size` | integer \| null | Restored original file size in bytes, or null on error. |
102 314 | `error_message` | string \| null | Human-readable error on failure, null on success. |
103 315
104 316 ## Hooks
105 317
@@ -201,9 +413,11 @@
201 413 **Input schema:**
202 414
203 415 | Field | Type | Required | Description |
204 416 |-------|------|----------|-------------|
205 -| `media_id` | integer | yes | WordPress attachment ID. |
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. |
206 420
207 421 **Output schema:**
208 422
209 423 | Field | Type | Description |
@@ -215,9 +429,9 @@
215 429 | `webp_available` | boolean | `true` when a WebP version of the full-size image has been generated. |
216 430 | `avif_available` | boolean | `true` when an AVIF version of the full-size image has been generated. |
217 431 | `error_message` | string \| null | Human-readable error message when `status` is `"error"`. `null` otherwise. |
218 432
219 -**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`.
433 +**Error behaviour:** If no identifier resolves to an attachment, returns `status: "error"` with a descriptive `error_message`. See [Identifying a media](#identifying-a-media).
220 434
221 435 ### `imagify/get-nextgen-coverage`
222 436
223 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.