PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.2
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.2
3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / abilities / class-evf-abilities.php
everest-forms / includes / abilities Last commit date
class-evf-abilities-handlers.php 3 weeks ago class-evf-abilities-registry.php 3 weeks ago class-evf-abilities.php 3 weeks ago class-evf-field-schemas.php 3 weeks ago class-evf-form-builder.php 3 weeks ago class-evf-mcp-server.php 3 weeks ago
class-evf-abilities.php
643 lines
1 <?php
2 /**
3 * WordPress Abilities API integration for Everest Forms.
4 *
5 * Registers a starter set of abilities (forms, entries, analytics) against
6 * the WordPress Abilities API when available, and exposes them through a
7 * minimal MCP-over-HTTP endpoint so external MCP clients can call them.
8 *
9 * @package EverestForms\Abilities
10 */
11
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * Abilities integration.
16 */
17 class EVF_Abilities {
18
19 /**
20 * Namespace used for ability ids and the MCP route.
21 */
22 const NAMESPACE_ID = 'everest-forms';
23
24 /**
25 * Bare ability names that modify site state.
26 *
27 * These flip the MCP `destructiveHint` annotation on `tools/list`, which
28 * is how MCP clients (Claude Desktop, ChatGPT connectors, etc.) know to
29 * show a confirmation prompt before invoking the tool. Without this list
30 * the client treats every tool as safe and skips the approval UI.
31 *
32 * @var string[]
33 */
34 const DESTRUCTIVE = array(
35 'create-form',
36 'update-form',
37 'delete-form',
38 'duplicate-form',
39 'update-form-status',
40 'activate-addon',
41 'create-entry',
42 'update-entry-fields',
43 'update-entry-status',
44 'delete-entry',
45 'bulk-delete-entries',
46 'set-entry-starred',
47 'set-entry-viewed',
48 );
49
50 /**
51 * Whether a bare ability name (e.g. "delete-form") mutates site state.
52 *
53 * @param string $name Bare ability name.
54 * @return bool
55 */
56 public static function is_destructive( $name ) {
57 return in_array( (string) $name, self::DESTRUCTIVE, true );
58 }
59
60 /**
61 * Init hooks.
62 */
63 public static function init() {
64 // Always populate our internal registry — MCP reads from it directly,
65 // which keeps tools/list deterministic regardless of the Abilities API
66 // lifecycle. Registered on `init` (not `plugins_loaded`) because the
67 // ability definitions contain translated labels/descriptions, and
68 // requesting translations before `init` triggers the WordPress 6.7+
69 // "translation loading was triggered too early" notice. Consumers run
70 // later (`wp_abilities_api_init`, `rest_api_init`), so `init` is early
71 // enough.
72 add_action( 'init', array( __CLASS__, 'self_register' ), 20 );
73
74 // Additionally mirror into the WP Abilities API when present, so other
75 // Abilities-aware consumers (e.g. the official REST controller + MCP
76 // adapters) see them. Categories MUST be registered on their own
77 // earlier hook; abilities reference the category on wp_abilities_api_init.
78 add_action( 'wp_abilities_api_categories_init', array( __CLASS__, 'register_category' ) );
79 add_action( 'wp_abilities_api_init', array( __CLASS__, 'register_abilities' ) );
80
81 add_action( 'rest_api_init', array( __CLASS__, 'register_mcp_route' ) );
82 add_action( 'admin_notices', array( __CLASS__, 'maybe_show_missing_api_notice' ) );
83 }
84
85 /**
86 * Whether the WordPress Abilities API is available on this site.
87 *
88 * @return bool
89 */
90 public static function has_abilities_api() {
91 return function_exists( 'wp_register_ability' );
92 }
93
94 /**
95 * Show an admin notice if the Abilities API isn't installed yet, so
96 * site owners know how to light up full integration.
97 */
98 public static function maybe_show_missing_api_notice() {
99 if ( self::has_abilities_api() ) {
100 return;
101 }
102 if ( ! current_user_can( 'manage_options' ) ) {
103 return;
104 }
105 // Only on Everest Forms screens to avoid noise.
106 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
107 if ( ! $screen || false === strpos( (string) $screen->id, 'everest-forms' ) ) {
108 return;
109 }
110 echo '<div class="notice notice-info"><p><strong>Everest Forms</strong>: WordPress Abilities API not detected. The plugin\'s MCP endpoint still works at <code>/wp-json/everest-forms/v1/mcp</code>, but to expose abilities to other Abilities-aware plugins, install the <a href="https://github.com/WordPress/abilities-api" target="_blank" rel="noopener">WordPress Abilities API</a>.</p></div>';
111 }
112
113 /**
114 * Category slug used when registering abilities with the WP Abilities API.
115 */
116 const CATEGORY = 'everest-forms';
117
118 /**
119 * Register abilities with the official WP Abilities API.
120 *
121 * Three things are required for abilities to surface through the Abilities
122 * REST API and any MCP adapter plugin built on top of it:
123 * 1. a registered `category` (Abilities API 6.9+ requires each ability to
124 * reference one),
125 * 2. `meta.show_in_rest = true` — abilities default to NOT exposed in
126 * REST, so without this the Abilities REST API (and therefore any MCP
127 * adapter) returns ZERO abilities, even though they're registered,
128 * 3. `meta.annotations` (readonly/destructive/idempotent) so the adapter
129 * can render the right confirmation UI.
130 */
131 public static function register_category() {
132 if ( function_exists( 'wp_register_ability_category' ) ) {
133 wp_register_ability_category(
134 self::CATEGORY,
135 array(
136 'label' => __( 'Everest Forms', 'everest-forms' ),
137 'description' => __( 'Form, entry, and analytics operations exposed by Everest Forms.', 'everest-forms' ),
138 )
139 );
140 }
141 }
142
143 public static function register_abilities() {
144 if ( ! self::has_abilities_api() ) {
145 return;
146 }
147
148 foreach ( self::ability_definitions() as $def ) {
149 $args = $def['args'];
150 $destructive = self::is_destructive( $def['name'] );
151
152 if ( ! isset( $args['category'] ) ) {
153 $args['category'] = self::CATEGORY;
154 }
155
156 // Ensure the ability is exposed via REST (default is false) and
157 // carries behavior annotations for adapter UIs.
158 $meta = isset( $args['meta'] ) && is_array( $args['meta'] ) ? $args['meta'] : array();
159 $meta['show_in_rest'] = true;
160 $meta['annotations'] = wp_parse_args(
161 isset( $meta['annotations'] ) && is_array( $meta['annotations'] ) ? $meta['annotations'] : array(),
162 array(
163 'readonly' => ! $destructive,
164 'destructive' => $destructive,
165 'idempotent' => ! $destructive,
166 )
167 );
168 $args['meta'] = $meta;
169
170 wp_register_ability( self::NAMESPACE_ID . '/' . $def['name'], $args );
171 }
172 }
173
174 /**
175 * Register abilities into our internal registry. Always runs.
176 */
177 public static function self_register() {
178 EVF_Abilities_Registry::instance()->bulk_register( self::ability_definitions() );
179 }
180
181 /**
182 * Ability definitions.
183 *
184 * @return array
185 */
186 protected static function ability_definitions() {
187 return array(
188 array(
189 'name' => 'list-forms',
190 'args' => array(
191 'label' => __( 'List forms', 'everest-forms' ),
192 'description' => __( 'List Everest Forms forms with id, title and status.', 'everest-forms' ),
193 'input_schema' => array(
194 'type' => 'object',
195 'properties' => array(
196 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 50 ),
197 'status' => array( 'type' => 'string', 'enum' => array( 'any', 'publish', 'draft', 'trash' ), 'default' => 'publish' ),
198 ),
199 ),
200 'output_schema' => array( 'type' => 'array' ),
201 'execute_callback' => array( 'EVF_Abilities_Handlers', 'list_forms' ),
202 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_forms' ),
203 ),
204 ),
205 array(
206 'name' => 'get-form',
207 'args' => array(
208 'label' => __( 'Get form', 'everest-forms' ),
209 'description' => __( 'Get a single form including its fields and settings.', 'everest-forms' ),
210 'input_schema' => array(
211 'type' => 'object',
212 'required' => array( 'form_id' ),
213 'properties' => array(
214 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
215 ),
216 ),
217 'output_schema' => array( 'type' => 'object' ),
218 'execute_callback' => array( 'EVF_Abilities_Handlers', 'get_form' ),
219 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_forms' ),
220 ),
221 ),
222 array(
223 'name' => 'create-form',
224 'args' => array(
225 'label' => __( 'Create form', 'everest-forms' ),
226 'description' => __( 'Create a new Everest Forms form. By default the new form is created **inactive** (the Active toggle is off and post_status is `inactive`) so it does not start collecting submissions until you explicitly publish it — pass `status: "publish"` to make it live on creation. Accepts a flat `fields` array (one field per row), or a richer `layout` array of rows for multi-column layouts. Each layout row may carry `part: N` to assign it to a multi-step page; pass `multi_part: { parts: [{name:"..."}] }` to enable the Multi-Part Forms addon and label the steps. Pass `conversational: { enabled: true, slug, title, description }` to render the form one-question-at-a-time via the Conversational Forms addon (mutually exclusive with multi_part). Fields support `choices` (select/radio/checkbox/country), `default_value`, `description`, `placeholder`, `required`, `sublabels`, plus any type-specific keys. Unknown field types AND missing required addons (Multi-Part, Conversational Forms, Coupons, etc.) return a structured error naming what to activate. Pass `dry_run: true` to validate without persisting.', 'everest-forms' ),
227 'input_schema' => array(
228 'type' => 'object',
229 'required' => array( 'title' ),
230 'properties' => array(
231 'title' => array( 'type' => 'string', 'minLength' => 1 ),
232 'description' => array( 'type' => 'string' ),
233 'template' => array( 'type' => 'string', 'default' => 'blank' ),
234 'status' => array( 'type' => 'string', 'enum' => array( 'inactive', 'publish' ), 'default' => 'inactive' ),
235 'settings' => array( 'type' => 'object' ),
236 'fields' => array(
237 'type' => 'array',
238 'items' => array( 'type' => 'object' ),
239 ),
240 'layout' => array(
241 'type' => 'array',
242 'items' => array(
243 'type' => 'object',
244 'properties' => array(
245 'row' => array(
246 'type' => 'array',
247 'items' => array( 'type' => 'object' ),
248 ),
249 ),
250 ),
251 ),
252 'multi_part' => array(
253 'type' => 'object',
254 'properties' => array(
255 'enabled' => array( 'type' => 'boolean' ),
256 'parts' => array(
257 'type' => 'array',
258 'items' => array(
259 'type' => 'object',
260 'properties' => array(
261 'name' => array( 'type' => 'string' ),
262 ),
263 ),
264 ),
265 ),
266 ),
267 'conversational' => array(
268 'type' => 'object',
269 'properties' => array(
270 'enabled' => array( 'type' => 'boolean' ),
271 'slug' => array( 'type' => 'string' ),
272 'title' => array( 'type' => 'string' ),
273 'description' => array( 'type' => 'string' ),
274 ),
275 ),
276 'dry_run' => array( 'type' => 'boolean', 'default' => false ),
277 ),
278 ),
279 'output_schema' => array( 'type' => 'object' ),
280 'execute_callback' => array( 'EVF_Abilities_Handlers', 'create_form' ),
281 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_create_forms' ),
282 ),
283 ),
284 array(
285 'name' => 'update-form',
286 'args' => array(
287 'label' => __( 'Update form', 'everest-forms' ),
288 'description' => __( 'Update an existing form. Patch top-level `title`/`description`/`settings` (deep-merged), append new fields via `fields` or `layout` (same schema as create-form), and patch existing fields by id via `form_fields_patch` ({ field_id: { key: value } }). Validates field types against installed addons and returns structured errors on unknown types. Pass `dry_run: true` to preview without persisting.', 'everest-forms' ),
289 'input_schema' => array(
290 'type' => 'object',
291 'required' => array( 'form_id' ),
292 'properties' => array(
293 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
294 'title' => array( 'type' => 'string' ),
295 'description' => array( 'type' => 'string' ),
296 'settings' => array( 'type' => 'object' ),
297 'fields' => array(
298 'type' => 'array',
299 'items' => array( 'type' => 'object' ),
300 ),
301 'layout' => array(
302 'type' => 'array',
303 'items' => array( 'type' => 'object' ),
304 ),
305 'form_fields_patch' => array( 'type' => 'object' ),
306 'multi_part' => array( 'type' => 'object' ),
307 'dry_run' => array( 'type' => 'boolean', 'default' => false ),
308 ),
309 ),
310 'output_schema' => array( 'type' => 'object' ),
311 'execute_callback' => array( 'EVF_Abilities_Handlers', 'update_form' ),
312 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_forms' ),
313 ),
314 ),
315 array(
316 'name' => 'update-form-status',
317 'args' => array(
318 'label' => __( 'Update form status', 'everest-forms' ),
319 'description' => __( 'Change a form\'s WordPress post_status (publish/draft/trash). Useful for archiving forms or restoring trashed ones without deleting them.', 'everest-forms' ),
320 'input_schema' => array(
321 'type' => 'object',
322 'required' => array( 'form_id', 'status' ),
323 'properties' => array(
324 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
325 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'trash' ) ),
326 ),
327 ),
328 'output_schema' => array( 'type' => 'object' ),
329 'execute_callback' => array( 'EVF_Abilities_Handlers', 'update_form_status' ),
330 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_forms' ),
331 ),
332 ),
333 array(
334 'name' => 'bulk-delete-entries',
335 'args' => array(
336 'label' => __( 'Bulk delete entries', 'everest-forms' ),
337 'description' => __( 'Delete multiple entries in one call by their ids. By default permanent; set permanent=false to move them to trash. Returns counts of deleted and skipped entries.', 'everest-forms' ),
338 'input_schema' => array(
339 'type' => 'object',
340 'required' => array( 'entry_ids' ),
341 'properties' => array(
342 'entry_ids' => array(
343 'type' => 'array',
344 'items' => array( 'type' => 'integer', 'minimum' => 1 ),
345 ),
346 'permanent' => array( 'type' => 'boolean', 'default' => true ),
347 ),
348 ),
349 'output_schema' => array( 'type' => 'object' ),
350 'execute_callback' => array( 'EVF_Abilities_Handlers', 'bulk_delete_entries' ),
351 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_delete_entries' ),
352 ),
353 ),
354 array(
355 'name' => 'delete-form',
356 'args' => array(
357 'label' => __( 'Delete form', 'everest-forms' ),
358 'description' => __( 'Delete a form (and its entries) by id.', 'everest-forms' ),
359 'input_schema' => array(
360 'type' => 'object',
361 'required' => array( 'form_id' ),
362 'properties' => array(
363 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
364 ),
365 ),
366 'output_schema' => array( 'type' => 'object' ),
367 'execute_callback' => array( 'EVF_Abilities_Handlers', 'delete_form' ),
368 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_delete_forms' ),
369 ),
370 ),
371 array(
372 'name' => 'duplicate-form',
373 'args' => array(
374 'label' => __( 'Duplicate form', 'everest-forms' ),
375 'description' => __( 'Duplicate an existing form. Returns the new form id.', 'everest-forms' ),
376 'input_schema' => array(
377 'type' => 'object',
378 'required' => array( 'form_id' ),
379 'properties' => array(
380 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
381 ),
382 ),
383 'output_schema' => array( 'type' => 'object' ),
384 'execute_callback' => array( 'EVF_Abilities_Handlers', 'duplicate_form' ),
385 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_create_forms' ),
386 ),
387 ),
388 array(
389 'name' => 'activate-addon',
390 'args' => array(
391 'label' => __( 'Activate an Everest Forms addon', 'everest-forms' ),
392 'description' => __( 'Activate an installed Everest Forms addon plugin by its plugin file (e.g. "everest-forms-survey-polls-quiz/everest-forms-survey-polls-quiz.php"). TWO-STEP: calling without confirm:true returns status:"confirmation_required" and does NOT activate — you must ask the user to confirm, then call again with confirm:true. Activating plugins changes site state, so never confirm on the user\'s behalf. If the plugin is not installed, the response indicates the user must install/upgrade it from their Everest Forms account.', 'everest-forms' ),
393 'input_schema' => array(
394 'type' => 'object',
395 'required' => array( 'plugin' ),
396 'properties' => array(
397 'plugin' => array( 'type' => 'string', 'minLength' => 3 ),
398 'confirm' => array( 'type' => 'boolean', 'default' => false, 'description' => 'Must be true to actually activate. Only set this after the user has explicitly agreed.' ),
399 ),
400 ),
401 'output_schema' => array( 'type' => 'object' ),
402 'execute_callback' => array( 'EVF_Abilities_Handlers', 'activate_addon' ),
403 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_activate_plugins' ),
404 ),
405 ),
406 array(
407 'name' => 'list-addons',
408 'args' => array(
409 'label' => __( 'List Everest Forms addons', 'everest-forms' ),
410 'description' => __( 'List installed Everest Forms addons with their active/inactive state. Use before create-form or update-form to decide whether a requested field type (likert, file-upload, etc.) is available.', 'everest-forms' ),
411 'input_schema' => array( 'type' => 'object' ),
412 'output_schema' => array( 'type' => 'array' ),
413 'execute_callback' => array( 'EVF_Abilities_Handlers', 'list_addons' ),
414 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_forms' ),
415 ),
416 ),
417 array(
418 'name' => 'describe-field-type',
419 'args' => array(
420 'label' => __( 'Describe field type', 'everest-forms' ),
421 'description' => __( 'Return the schema for a single field type — accepted keys, whether it requires choices, and the addon that ships it. Useful before constructing a complex field.', 'everest-forms' ),
422 'input_schema' => array(
423 'type' => 'object',
424 'required' => array( 'type' ),
425 'properties' => array(
426 'type' => array( 'type' => 'string' ),
427 ),
428 ),
429 'output_schema' => array( 'type' => 'object' ),
430 'execute_callback' => array( 'EVF_Abilities_Handlers', 'describe_field_type' ),
431 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_forms' ),
432 ),
433 ),
434 array(
435 'name' => 'list-field-types',
436 'args' => array(
437 'label' => __( 'List field types', 'everest-forms' ),
438 'description' => __( 'List all registered Everest Forms field types (id, name, group, icon) usable when building or editing forms.', 'everest-forms' ),
439 'input_schema' => array( 'type' => 'object' ),
440 'output_schema' => array( 'type' => 'array' ),
441 'execute_callback' => array( 'EVF_Abilities_Handlers', 'list_field_types' ),
442 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_forms' ),
443 ),
444 ),
445 array(
446 'name' => 'list-entries',
447 'args' => array(
448 'label' => __( 'List entries', 'everest-forms' ),
449 'description' => __( 'List entries for a given form. Supports status filter (unread, read, starred, approved, denied, pending, spam, trash, publish).', 'everest-forms' ),
450 'input_schema' => array(
451 'type' => 'object',
452 'required' => array( 'form_id' ),
453 'properties' => array(
454 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
455 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 25 ),
456 'offset' => array( 'type' => 'integer', 'minimum' => 0, 'default' => 0 ),
457 'search' => array( 'type' => 'string' ),
458 'status' => array( 'type' => 'string', 'enum' => array( '', 'unread', 'read', 'starred', 'approved', 'denied', 'pending', 'spam', 'trash', 'publish' ) ),
459 ),
460 ),
461 'output_schema' => array( 'type' => 'array' ),
462 'execute_callback' => array( 'EVF_Abilities_Handlers', 'list_entries' ),
463 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_entries' ),
464 ),
465 ),
466 array(
467 'name' => 'get-entry',
468 'args' => array(
469 'label' => __( 'Get entry', 'everest-forms' ),
470 'description' => __( 'Get a single entry with all field values.', 'everest-forms' ),
471 'input_schema' => array(
472 'type' => 'object',
473 'required' => array( 'entry_id' ),
474 'properties' => array(
475 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
476 ),
477 ),
478 'output_schema' => array( 'type' => 'object' ),
479 'execute_callback' => array( 'EVF_Abilities_Handlers', 'get_entry' ),
480 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_entries' ),
481 ),
482 ),
483 array(
484 'name' => 'delete-entry',
485 'args' => array(
486 'label' => __( 'Delete entry', 'everest-forms' ),
487 'description' => __( 'Delete a form entry. By default permanent; set permanent=false to move it to trash instead.', 'everest-forms' ),
488 'input_schema' => array(
489 'type' => 'object',
490 'required' => array( 'entry_id' ),
491 'properties' => array(
492 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
493 'permanent' => array( 'type' => 'boolean', 'default' => true ),
494 ),
495 ),
496 'output_schema' => array( 'type' => 'object' ),
497 'execute_callback' => array( 'EVF_Abilities_Handlers', 'delete_entry' ),
498 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_delete_entries' ),
499 ),
500 ),
501 array(
502 'name' => 'update-entry-status',
503 'args' => array(
504 'label' => __( 'Update entry status', 'everest-forms' ),
505 'description' => __( 'Set the status of an entry to approved, denied, pending, spam, publish, or trash.', 'everest-forms' ),
506 'input_schema' => array(
507 'type' => 'object',
508 'required' => array( 'entry_id', 'status' ),
509 'properties' => array(
510 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
511 'status' => array( 'type' => 'string', 'enum' => array( 'approved', 'denied', 'pending', 'spam', 'publish', 'trash' ) ),
512 ),
513 ),
514 'output_schema' => array( 'type' => 'object' ),
515 'execute_callback' => array( 'EVF_Abilities_Handlers', 'update_entry_status' ),
516 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_entries' ),
517 ),
518 ),
519 array(
520 'name' => 'set-entry-starred',
521 'args' => array(
522 'label' => __( 'Star/unstar entry', 'everest-forms' ),
523 'description' => __( 'Toggle the starred flag on an entry.', 'everest-forms' ),
524 'input_schema' => array(
525 'type' => 'object',
526 'required' => array( 'entry_id', 'starred' ),
527 'properties' => array(
528 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
529 'starred' => array( 'type' => 'boolean' ),
530 ),
531 ),
532 'output_schema' => array( 'type' => 'object' ),
533 'execute_callback' => array( 'EVF_Abilities_Handlers', 'set_entry_starred' ),
534 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_entries' ),
535 ),
536 ),
537 array(
538 'name' => 'set-entry-viewed',
539 'args' => array(
540 'label' => __( 'Mark entry read/unread', 'everest-forms' ),
541 'description' => __( 'Toggle the viewed (read) flag on an entry.', 'everest-forms' ),
542 'input_schema' => array(
543 'type' => 'object',
544 'required' => array( 'entry_id', 'viewed' ),
545 'properties' => array(
546 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
547 'viewed' => array( 'type' => 'boolean' ),
548 ),
549 ),
550 'output_schema' => array( 'type' => 'object' ),
551 'execute_callback' => array( 'EVF_Abilities_Handlers', 'set_entry_viewed' ),
552 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_entries' ),
553 ),
554 ),
555 array(
556 'name' => 'count-entries',
557 'args' => array(
558 'label' => __( 'Count entries', 'everest-forms' ),
559 'description' => __( 'Counts of entries broken down by status (total, unread, starred, approved, denied, pending, spam, trash) for a form or site-wide.', 'everest-forms' ),
560 'input_schema' => array(
561 'type' => 'object',
562 'properties' => array(
563 'form_id' => array( 'type' => 'integer', 'minimum' => 0, 'default' => 0 ),
564 ),
565 ),
566 'output_schema' => array( 'type' => 'object' ),
567 'execute_callback' => array( 'EVF_Abilities_Handlers', 'count_entries' ),
568 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_entries' ),
569 ),
570 ),
571 array(
572 'name' => 'create-entry',
573 'args' => array(
574 'label' => __( 'Create entry', 'everest-forms' ),
575 'description' => __( 'Create a new entry for a form. Pass fields as an object keyed by field id.', 'everest-forms' ),
576 'input_schema' => array(
577 'type' => 'object',
578 'required' => array( 'form_id', 'fields' ),
579 'properties' => array(
580 'form_id' => array( 'type' => 'integer', 'minimum' => 1 ),
581 'fields' => array( 'type' => 'object' ),
582 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'approved', 'denied', 'pending', 'spam' ), 'default' => 'publish' ),
583 ),
584 ),
585 'output_schema' => array( 'type' => 'object' ),
586 'execute_callback' => array( 'EVF_Abilities_Handlers', 'create_entry' ),
587 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_entries' ),
588 ),
589 ),
590 array(
591 'name' => 'update-entry-fields',
592 'args' => array(
593 'label' => __( 'Update entry field values', 'everest-forms' ),
594 'description' => __( 'Update one or more field values on an existing entry. Pass fields as an object keyed by field id.', 'everest-forms' ),
595 'input_schema' => array(
596 'type' => 'object',
597 'required' => array( 'entry_id', 'fields' ),
598 'properties' => array(
599 'entry_id' => array( 'type' => 'integer', 'minimum' => 1 ),
600 'fields' => array( 'type' => 'object' ),
601 ),
602 ),
603 'output_schema' => array( 'type' => 'object' ),
604 'execute_callback' => array( 'EVF_Abilities_Handlers', 'update_entry_fields' ),
605 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_edit_entries' ),
606 ),
607 ),
608 array(
609 'name' => 'analytics-summary',
610 'args' => array(
611 'label' => __( 'Analytics summary', 'everest-forms' ),
612 'description' => __( 'Summary stats: total forms, total entries, entries in last N days, top forms by submissions.', 'everest-forms' ),
613 'input_schema' => array(
614 'type' => 'object',
615 'properties' => array(
616 'days' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 365, 'default' => 30 ),
617 'form_id' => array( 'type' => 'integer', 'minimum' => 0, 'default' => 0 ),
618 ),
619 ),
620 'output_schema' => array( 'type' => 'object' ),
621 'execute_callback' => array( 'EVF_Abilities_Handlers', 'analytics_summary' ),
622 'permission_callback' => array( 'EVF_Abilities_Handlers', 'can_view_entries' ),
623 ),
624 ),
625 );
626 }
627
628 /**
629 * Register the in-plugin MCP HTTP endpoint.
630 */
631 public static function register_mcp_route() {
632 register_rest_route(
633 'everest-forms/v1',
634 '/mcp',
635 array(
636 'methods' => array( 'POST', 'GET' ),
637 'callback' => array( 'EVF_MCP_Server', 'handle_request' ),
638 'permission_callback' => array( 'EVF_MCP_Server', 'permission_check' ),
639 )
640 );
641 }
642 }
643