PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / trunk
Code Engine – PHP Snippets, AI Functions & Automation for WordPress vtrunk
0.5.6 0.5.5 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.9 0.4.8 0.4.7 0.4.6 trunk 0.0.1 0.0.2 0.2.8 0.2.9 0.3.0 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 0.3.8 All 32 releases
code-engine / classes / mcp.php

mcp.php in Code Engine – PHP Snippets, AI Functions & Automation for WordPress trunk, at classes/mcp.php

751 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Meow_MWCODE_MCP {
4 private $core;
5 private $api;
6 // Per-request memo for the opted-in Callable list, so listing tools and executing
7 // one don't each reload every snippet from the database. Reset on any mutation.
8 private $mcp_functions_cache = null;
9
10 // Shared, model-facing explanation of what each scope means. Without this an agent
11 // sees a bare enum and has to guess which scope to pick.
12 const SCOPE_DESC = "Where the snippet lives and runs: 'function' = Callable, run on demand (via REST, AI Engine, MCP); 'persistent' = Global, always loaded on both the front-end and wp-admin; 'frontend' = loaded on the front-end only; 'backend' = loaded in wp-admin only; 'scheduled' = run automatically on a schedule (cron); 'content_php' = a PHP snippet output where its [code-engine id=...] shortcode/block is placed; 'content_js' = a JavaScript snippet emitted as a <script> tag via the same shortcode/block.";
13
14 // Scope enums that appear across every management tool. Kept in one place so the
15 // list can never drift between tools (all seven scopes the API actually accepts).
16 const SCOPES = ['function', 'backend', 'frontend', 'scheduled', 'persistent', 'content_php', 'content_js'];
17
18 public function __construct( $core ) {
19 $this->core = $core;
20
21 // Initialize everything on 'init' to ensure options are loaded
22 add_action( 'init', array( $this, 'init' ), 20 );
23 }
24
25 public function init() {
26 global $mwcode, $mwai;
27 $this->api = $mwcode;
28
29 // Nothing to do without AI Engine's MCP server.
30 if ( !isset( $mwai ) ) {
31 return;
32 }
33
34 // Two independent surfaces share AI Engine's MCP server, each behind its own
35 // global master switch (and AI Engine's own MCP auth gate upstream):
36 // - the management/internal API (the 'mcp_support' option)
37 // - individual Callable functions, opted-in per snippet via 'functionMcp' and
38 // only exposed when the 'mcp_functions' option is enabled
39 // Either one alone is enough to justify hooking the filters.
40 add_filter( 'mwai_mcp_tools', array( $this, 'register_tools' ) );
41 add_filter( 'mwai_mcp_callback', array( $this, 'handle_tool_execution' ), 10, 4 );
42 }
43
44 public function register_tools( $tools ) {
45 // Individual Callable functions that opted in to MCP, exposed as first-class tools.
46 // Gated behind a global master switch (Settings > For Developers > MCP Functions)
47 // in addition to each snippet's per-function opt-in.
48 if ( $this->core->get_option( 'mcp_functions', false ) ) {
49 $tools = $this->register_function_tools( $tools );
50 }
51
52 // Code Engine's management/internal API (Settings > For Developers > MCP Support).
53 if ( $this->core->get_option( 'mcp_support', false ) ) {
54 $tools = $this->register_management_tools( $tools );
55 }
56
57 return $tools;
58 }
59
60 /**
61 * Map a Code Engine argument type to a JSON Schema type.
62 * Returns null for 'mixed'/unknown so the schema leaves the type open.
63 */
64 private function mcp_type( $type ) {
65 switch ( $type ) {
66 case 'number': return 'number';
67 case 'boolean': return 'boolean';
68 case 'array': return 'array';
69 case 'object': return 'object';
70 case 'string': return 'string';
71 default: return null;
72 }
73 }
74
75 /**
76 * Return the active Callable (function) snippets that opted in to MCP exposure.
77 */
78 private function get_mcp_functions() {
79 if ( $this->mcp_functions_cache !== null ) {
80 return $this->mcp_functions_cache;
81 }
82 global $mwcode;
83 if ( !isset( $mwcode ) || !method_exists( $mwcode, 'getSnippets' ) ) {
84 return ( $this->mcp_functions_cache = [] );
85 }
86 $functions = $mwcode->getSnippets( true, 'function' );
87 if ( empty( $functions ) ) {
88 return ( $this->mcp_functions_cache = [] );
89 }
90 return ( $this->mcp_functions_cache = array_values( array_filter( $functions, function ( $fn ) {
91 return !empty( $fn['functionMcp'] ) && !empty( $fn['functionName'] );
92 } ) ) );
93 }
94
95 /**
96 * Register each opted-in Callable function as its own MCP tool, named after the
97 * function, with an input schema derived from its declared arguments.
98 */
99 public function register_function_tools( $tools ) {
100 foreach ( $this->get_mcp_functions() as $fn ) {
101 $properties = [];
102 $required = [];
103
104 $argsDict = isset( $fn['functionArgsDict'] ) && is_array( $fn['functionArgsDict'] ) ? $fn['functionArgsDict'] : [];
105 foreach ( $argsDict as $argName => $arg ) {
106 $name = ltrim( $argName, '$' );
107 if ( $name === '' ) {
108 continue;
109 }
110 $prop = [];
111 $type = $this->mcp_type( $arg['type'] ?? 'string' );
112 if ( $type !== null ) {
113 $prop['type'] = $type;
114 }
115 if ( !empty( $arg['desc'] ) ) {
116 $prop['description'] = $arg['desc'];
117 }
118 $properties[ $name ] = $prop;
119 if ( !empty( $arg['required'] ) ) {
120 $required[] = $name;
121 }
122 }
123
124 $schema = [
125 'type' => 'object',
126 // Cast so an argument-less function still serializes as {} and not [].
127 'properties' => (object) $properties,
128 ];
129 if ( !empty( $required ) ) {
130 $schema['required'] = $required;
131 }
132
133 $tools[] = [
134 'name' => $fn['functionName'],
135 'description' => !empty( $fn['description'] ) ? $fn['description'] : ( 'Code Engine function: ' . $fn['functionName'] ),
136 'category' => 'Code Engine (Functions)',
137 'inputSchema' => $schema,
138 'annotations' => [ 'openWorldHint' => true ],
139 ];
140 }
141 return $tools;
142 }
143
144 public function register_management_tools( $tools ) {
145 // Get Snippet
146 $tools[] = [
147 'name' => 'mwcode_get_snippet',
148 'description' => 'Get a Code Engine snippet by its ID',
149 'category' => 'Code Engine',
150 'inputSchema' => [
151 'type' => 'object',
152 'properties' => [
153 'id' => [
154 'type' => 'integer',
155 'description' => 'The snippet ID'
156 ],
157 'options' => [
158 'type' => 'object',
159 'description' => 'Optional filtering options',
160 'properties' => [
161 'php_ready_args' => [
162 'type' => 'boolean',
163 'description' => 'When true (default), function argument names are returned PHP-ready with a leading $ (e.g. "$id"). Set false to get plain names (e.g. "id").'
164 ]
165 ]
166 ]
167 ],
168 'required' => ['id']
169 ]
170 ];
171
172 // Get Snippet by Name
173 $tools[] = [
174 'name' => 'mwcode_get_snippet_by_name',
175 'description' => 'Get a Code Engine snippet by its name',
176 'category' => 'Code Engine',
177 'inputSchema' => [
178 'type' => 'object',
179 'properties' => [
180 'name' => [
181 'type' => 'string',
182 'description' => 'The snippet name'
183 ],
184 'options' => [
185 'type' => 'object',
186 'description' => 'Optional filtering options',
187 'properties' => [
188 'php_ready_args' => [
189 'type' => 'boolean',
190 'description' => 'When true (default), function argument names are returned PHP-ready with a leading $ (e.g. "$id"). Set false to get plain names (e.g. "id").'
191 ]
192 ]
193 ]
194 ],
195 'required' => ['name']
196 ]
197 ];
198
199 // Get Snippets
200 $tools[] = [
201 'name' => 'mwcode_get_snippets',
202 'description' => 'Get all Code Engine snippets, optionally filtered by scope',
203 'category' => 'Code Engine',
204 'inputSchema' => [
205 'type' => 'object',
206 'properties' => [
207 'safe' => [
208 'type' => 'boolean',
209 'description' => 'When true (default), skip function snippets whose function name is empty or invalid. Leave true unless you specifically need to inspect malformed snippets.',
210 'default' => true
211 ],
212 'scope' => [
213 'type' => 'string',
214 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
215 'enum' => self::SCOPES
216 ]
217 ]
218 ]
219 ];
220
221 // Execute Snippet
222 $tools[] = [
223 'name' => 'mwcode_execute_snippet',
224 'description' => 'Execute a Code Engine snippet by its ID',
225 'category' => 'Code Engine',
226 'inputSchema' => [
227 'type' => 'object',
228 'properties' => [
229 'id' => [
230 'type' => 'integer',
231 'description' => 'The snippet ID'
232 ],
233 'args' => [
234 'type' => 'object',
235 'description' => 'Arguments to pass to the snippet (key-value pairs)',
236 'additionalProperties' => true
237 ]
238 ],
239 'required' => ['id']
240 ]
241 ];
242
243 // Execute Snippet by Name
244 $tools[] = [
245 'name' => 'mwcode_execute_snippet_by_name',
246 'description' => 'Execute a Code Engine snippet by its name',
247 'category' => 'Code Engine',
248 'inputSchema' => [
249 'type' => 'object',
250 'properties' => [
251 'name' => [
252 'type' => 'string',
253 'description' => 'The snippet name'
254 ],
255 'args' => [
256 'type' => 'object',
257 'description' => 'Arguments to pass to the snippet (key-value pairs)',
258 'additionalProperties' => true
259 ]
260 ],
261 'required' => ['name']
262 ]
263 ];
264
265 // Create Snippet
266 $tools[] = [
267 'name' => 'mwcode_create_snippet',
268 'description' => 'Create a new Code Engine snippet',
269 'category' => 'Code Engine',
270 'inputSchema' => [
271 'type' => 'object',
272 'properties' => [
273 'name' => [
274 'type' => 'string',
275 'description' => 'Name of the snippet'
276 ],
277 'code' => [
278 'type' => 'string',
279 'description' => 'Code of the snippet'
280 ],
281 'scope' => [
282 'type' => 'string',
283 'description' => 'Scope of the snippet. ' . self::SCOPE_DESC . ' Defaults to "function".',
284 'enum' => self::SCOPES,
285 'default' => 'function'
286 ],
287 'options' => [
288 'type' => 'object',
289 'description' => 'Additional options for the snippet',
290 'properties' => [
291 'target' => [
292 'type' => 'string',
293 'enum' => ['php', 'js'],
294 'description' => 'Target language (for function snippets)'
295 ],
296 'description' => [
297 'type' => 'string',
298 'description' => 'Description of the snippet'
299 ],
300 'args' => [
301 'type' => 'array',
302 'description' => 'Arguments for function snippets',
303 'items' => [ 'type' => 'string' ]
304 ],
305 'argsData' => [
306 'type' => 'object',
307 'description' => 'Argument data for function snippets',
308 'additionalProperties' => [
309 'type' => 'object',
310 'properties' => [
311 'type' => [ 'type' => 'string' ],
312 'description' => [ 'type' => 'string' ],
313 'default' => [ 'type' => 'string' ]
314 ]
315 ]
316 ],
317 'behavior' => [
318 'type' => 'string',
319 'enum' => ['dynamic', 'static'],
320 'description' => 'Behavior for function snippets'
321 ],
322 'mcp' => [
323 'type' => 'boolean',
324 'description' => 'For function snippets: expose this function as its own MCP tool in AI Engine, named after the function and callable directly by external agents. Defaults to false.'
325 ],
326 'tags' => [
327 'type' => 'array',
328 'description' => 'Array of tags',
329 'items' => [ 'type' => 'string' ]
330 ],
331 'priority' => [
332 'type' => 'integer',
333 'description' => 'Execution priority'
334 ],
335 'active' => [
336 'type' => 'boolean',
337 'description' => 'Active status'
338 ],
339 'endpoint' => [
340 'type' => 'string',
341 'description' => 'REST endpoint'
342 ],
343 'method' => [
344 'type' => 'string',
345 'enum' => ['GET', 'POST'],
346 'description' => 'HTTP method'
347 ],
348 'intervalHours' => [
349 'type' => 'integer',
350 'description' => 'Hours for scheduled snippets'
351 ],
352 'intervalMinutes' => [
353 'type' => 'integer',
354 'description' => 'Minutes for scheduled snippets'
355 ]
356 ]
357 ]
358 ],
359 'required' => ['name', 'code']
360 ]
361 ];
362
363 // Update Snippet
364 $tools[] = [
365 'name' => 'mwcode_update_snippet',
366 'description' => 'Update an existing Code Engine snippet',
367 'category' => 'Code Engine',
368 'inputSchema' => [
369 'type' => 'object',
370 'properties' => [
371 'id' => [
372 'type' => 'integer',
373 'description' => 'ID of the snippet to update'
374 ],
375 'params' => [
376 'type' => 'object',
377 'description' => 'Parameters to update',
378 'properties' => [
379 'name' => [ 'type' => 'string' ],
380 'code' => [ 'type' => 'string' ],
381 'scope' => [
382 'type' => 'string',
383 'description' => self::SCOPE_DESC,
384 'enum' => self::SCOPES
385 ],
386 'description' => [ 'type' => 'string' ],
387 'active' => [ 'type' => 'boolean' ],
388 'priority' => [ 'type' => 'integer' ],
389 'tags' => [
390 'type' => 'array',
391 'items' => [ 'type' => 'string' ]
392 ],
393 'endpoint' => [ 'type' => 'string' ],
394 'method' => [
395 'type' => 'string',
396 'enum' => ['GET', 'POST']
397 ],
398 'target' => [
399 'type' => 'string',
400 'enum' => ['php', 'js']
401 ],
402 'args' => [
403 'type' => 'array',
404 'items' => [ 'type' => 'string' ]
405 ],
406 'argsData' => [
407 'type' => 'object',
408 'additionalProperties' => true
409 ],
410 'behavior' => [
411 'type' => 'string',
412 'enum' => ['dynamic', 'static']
413 ],
414 'mcp' => [
415 'type' => 'boolean',
416 'description' => 'For function snippets: expose this function as its own MCP tool in AI Engine, named after the function and callable directly by external agents.'
417 ],
418 'intervalHours' => [ 'type' => 'integer' ],
419 'intervalMinutes' => [ 'type' => 'integer' ]
420 ]
421 ]
422 ],
423 'required' => ['id']
424 ]
425 ];
426
427 // Delete Snippet
428 $tools[] = [
429 'name' => 'mwcode_delete_snippet',
430 'description' => 'Delete a Code Engine snippet by its ID',
431 'category' => 'Code Engine',
432 'inputSchema' => [
433 'type' => 'object',
434 'properties' => [
435 'id' => [
436 'type' => 'integer',
437 'description' => 'The snippet ID'
438 ]
439 ],
440 'required' => ['id']
441 ]
442 ];
443
444 // Delete Snippet by Name
445 $tools[] = [
446 'name' => 'mwcode_delete_snippet_by_name',
447 'description' => 'Delete a Code Engine snippet by its name',
448 'category' => 'Code Engine',
449 'inputSchema' => [
450 'type' => 'object',
451 'properties' => [
452 'name' => [
453 'type' => 'string',
454 'description' => 'The snippet name'
455 ]
456 ],
457 'required' => ['name']
458 ]
459 ];
460
461 // Activate Snippet
462 $tools[] = [
463 'name' => 'mwcode_activate_snippet',
464 'description' => 'Activate a Code Engine snippet',
465 'category' => 'Code Engine',
466 'inputSchema' => [
467 'type' => 'object',
468 'properties' => [
469 'id' => [
470 'type' => 'integer',
471 'description' => 'The snippet ID'
472 ]
473 ],
474 'required' => ['id']
475 ]
476 ];
477
478 // Deactivate Snippet
479 $tools[] = [
480 'name' => 'mwcode_deactivate_snippet',
481 'description' => 'Deactivate a Code Engine snippet',
482 'category' => 'Code Engine',
483 'inputSchema' => [
484 'type' => 'object',
485 'properties' => [
486 'id' => [
487 'type' => 'integer',
488 'description' => 'The snippet ID'
489 ]
490 ],
491 'required' => ['id']
492 ]
493 ];
494
495 // Get Snippets by Scope
496 $tools[] = [
497 'name' => 'mwcode_get_snippets_by_scope',
498 'description' => 'Get Code Engine snippets filtered by scope',
499 'category' => 'Code Engine',
500 'inputSchema' => [
501 'type' => 'object',
502 'properties' => [
503 'scope' => [
504 'type' => 'string',
505 'description' => 'The scope to filter by. ' . self::SCOPE_DESC,
506 'enum' => self::SCOPES
507 ],
508 'filters' => [
509 'type' => 'object',
510 'description' => 'Additional filters',
511 'properties' => [
512 'active' => [
513 'type' => 'boolean',
514 'description' => 'Filter by active status'
515 ],
516 'tags' => [
517 'type' => 'array',
518 'description' => 'Filter by tags',
519 'items' => [ 'type' => 'string' ]
520 ]
521 ]
522 ]
523 ],
524 'required' => ['scope']
525 ]
526 ];
527
528 // Get Active Snippets
529 $tools[] = [
530 'name' => 'mwcode_get_active_snippets',
531 'description' => 'Get all active Code Engine snippets',
532 'category' => 'Code Engine',
533 'inputSchema' => [
534 'type' => 'object',
535 'properties' => [
536 'scope' => [
537 'type' => 'string',
538 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
539 'enum' => self::SCOPES
540 ]
541 ]
542 ]
543 ];
544
545 // Snippet Exists
546 $tools[] = [
547 'name' => 'mwcode_snippet_exists',
548 'description' => 'Check if a Code Engine snippet exists by ID',
549 'category' => 'Code Engine',
550 'inputSchema' => [
551 'type' => 'object',
552 'properties' => [
553 'id' => [
554 'type' => 'integer',
555 'description' => 'The snippet ID'
556 ]
557 ],
558 'required' => ['id']
559 ]
560 ];
561
562 // Snippet Exists by Name
563 $tools[] = [
564 'name' => 'mwcode_snippet_exists_by_name',
565 'description' => 'Check if a Code Engine snippet exists by name',
566 'category' => 'Code Engine',
567 'inputSchema' => [
568 'type' => 'object',
569 'properties' => [
570 'name' => [
571 'type' => 'string',
572 'description' => 'The snippet name'
573 ]
574 ],
575 'required' => ['name']
576 ]
577 ];
578
579 // Validate Snippet Code
580 $tools[] = [
581 'name' => 'mwcode_validate_snippet_code',
582 'description' => 'Validate snippet code syntax',
583 'category' => 'Code Engine',
584 'inputSchema' => [
585 'type' => 'object',
586 'properties' => [
587 'code' => [
588 'type' => 'string',
589 'description' => 'The snippet code to validate'
590 ],
591 'target' => [
592 'type' => 'string',
593 'description' => 'The target language',
594 'enum' => ['php', 'js'],
595 'default' => 'php'
596 ]
597 ],
598 'required' => ['code']
599 ]
600 ];
601
602 return $tools;
603 }
604
605 /**
606 * Execute a Callable function exposed via MCP. Returns $result unchanged when the
607 * tool name does not match one of our opted-in functions, so the filter chain
608 * continues to the management tools (and other plugins).
609 */
610 private function handle_function_execution( $result, $tool, $args ) {
611 // Master switch: even an opted-in Callable is unreachable via MCP unless the
612 // site has explicitly enabled function exposure. Returning $result unchanged
613 // lets the filter chain fall through to the management tools and other plugins.
614 if ( !$this->core->get_option( 'mcp_functions', false ) ) {
615 return $result;
616 }
617
618 $match = null;
619 foreach ( $this->get_mcp_functions() as $fn ) {
620 if ( $fn['functionName'] === $tool ) {
621 $match = $fn;
622 break;
623 }
624 }
625 if ( $match === null ) {
626 return $result;
627 }
628
629 if ( !$this->api ) {
630 return [ 'success' => false, 'error' => 'Code Engine API not initialized' ];
631 }
632
633 // getSnippets() rows expose the database id as 'id' (function metadata uses 'snippetId').
634 $snippetId = $match['id'] ?? $match['snippetId'] ?? null;
635
636 try {
637 $output = $this->api->executeSnippet( $snippetId, is_array( $args ) ? $args : [] );
638 return [ 'success' => true, 'data' => $output ];
639 }
640 // Snippet code is arbitrary PHP: a fatal surfaces as Error/TypeError/ParseError,
641 // none of which are Exceptions. Catch Throwable so a bad snippet can never take
642 // down the MCP request.
643 catch ( \Throwable $e ) {
644 return [ 'success' => false, 'error' => $e->getMessage() ];
645 }
646 }
647
648 public function handle_tool_execution( $result, $tool, $args, $id ) {
649 // Individual Callable functions exposed via MCP take priority (named after the function).
650 $handled = $this->handle_function_execution( $result, $tool, $args );
651 if ( $handled !== $result ) {
652 return $handled;
653 }
654
655 // Management/internal API tools are gated behind the master switch.
656 if ( !$this->core->get_option( 'mcp_support', false ) ) {
657 return $result;
658 }
659
660 // Only handle our tools
661 if ( strpos( $tool, 'mwcode_' ) !== 0 ) {
662 return $result;
663 }
664
665 // Ensure API is initialized
666 if ( !$this->api ) {
667 return [ 'success' => false, 'error' => 'Code Engine API not initialized' ];
668 }
669
670 try {
671 switch ( $tool ) {
672 case 'mwcode_get_snippet':
673 $data = $this->api->getSnippet( $args['id'], $args['options'] ?? [] );
674 return [ 'success' => true, 'data' => $data ];
675
676 case 'mwcode_get_snippet_by_name':
677 $data = $this->api->getSnippetByName( $args['name'], $args['options'] ?? [] );
678 return [ 'success' => true, 'data' => $data ];
679
680 case 'mwcode_get_snippets':
681 $data = $this->api->getSnippets( $args['safe'] ?? true, $args['scope'] ?? null );
682 return [ 'success' => true, 'data' => $data ];
683
684 case 'mwcode_execute_snippet':
685 $data = $this->api->executeSnippet( $args['id'], $args['args'] ?? [] );
686 return [ 'success' => true, 'data' => $data ];
687
688 case 'mwcode_execute_snippet_by_name':
689 $data = $this->api->executeSnippetByName( $args['name'], $args['args'] ?? [] );
690 return [ 'success' => true, 'data' => $data ];
691
692 case 'mwcode_create_snippet':
693 $data = $this->api->createSnippet(
694 $args['name'],
695 $args['code'],
696 $args['scope'] ?? 'function',
697 $args['options'] ?? []
698 );
699 return [ 'success' => true, 'data' => $data ];
700
701 case 'mwcode_update_snippet':
702 $data = $this->api->updateSnippet( $args['id'], $args['params'] ?? [] );
703 return [ 'success' => true, 'data' => $data ];
704
705 case 'mwcode_delete_snippet':
706 $success = $this->api->deleteSnippet( $args['id'] );
707 return [ 'success' => true, 'data' => [ 'deleted' => $success ] ];
708
709 case 'mwcode_delete_snippet_by_name':
710 $success = $this->api->deleteSnippetByName( $args['name'] );
711 return [ 'success' => true, 'data' => [ 'deleted' => $success ] ];
712
713 case 'mwcode_activate_snippet':
714 $success = $this->api->activateSnippet( $args['id'] );
715 return [ 'success' => true, 'data' => [ 'activated' => $success ] ];
716
717 case 'mwcode_deactivate_snippet':
718 $success = $this->api->deactivateSnippet( $args['id'] );
719 return [ 'success' => true, 'data' => [ 'deactivated' => $success ] ];
720
721 case 'mwcode_get_snippets_by_scope':
722 $data = $this->api->getSnippetsByScope( $args['scope'], $args['filters'] ?? [] );
723 return [ 'success' => true, 'data' => $data ];
724
725 case 'mwcode_get_active_snippets':
726 $data = $this->api->getActiveSnippets( $args['scope'] ?? null );
727 return [ 'success' => true, 'data' => $data ];
728
729 case 'mwcode_snippet_exists':
730 $exists = $this->api->snippetExists( $args['id'] );
731 return [ 'success' => true, 'data' => [ 'exists' => $exists ] ];
732
733 case 'mwcode_snippet_exists_by_name':
734 $exists = $this->api->snippetExistsByName( $args['name'] );
735 return [ 'success' => true, 'data' => [ 'exists' => $exists ] ];
736
737 case 'mwcode_validate_snippet_code':
738 $validation = $this->api->validateSnippetCode( $args['code'], $args['target'] ?? 'php' );
739 return [ 'success' => true, 'data' => $validation ];
740 }
741 }
742 // executeSnippet() runs arbitrary snippet PHP, whose fatals are Errors, not
743 // Exceptions. Catch Throwable so a broken snippet returns a clean error rather
744 // than crashing the MCP request.
745 catch ( \Throwable $e ) {
746 return [ 'success' => false, 'error' => $e->getMessage() ];
747 }
748
749 return $result;
750 }
751 }