PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / trunk
Code Engine – PHP Snippets, AI Functions & Automation for WordPress vtrunk
0.5.7 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 All 33 releases
← All changes | classes/mcp.php +241 -59 0.3.2trunk View file →
@@ -2,9 +2,20 @@
2 2
3 3 class Meow_MWCODE_MCP {
4 4 private $core;
5 5 private $api;
6 -
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 +
7 18 public function __construct( $core ) {
8 19 $this->core = $core;
9 20
10 21 // Initialize everything on 'init' to ensure options are loaded
@@ -13,23 +24,128 @@
13 24
14 25 public function init() {
15 26 global $mwcode, $mwai;
16 27 $this->api = $mwcode;
17 -
18 - // Only register MCP if enabled AND AI Engine is available
19 - if ( $this->core->get_option( 'mcp_support', false ) && isset( $mwai ) ) {
20 - // Register MCP tools
21 - add_filter( 'mwai_mcp_tools', array( $this, 'register_tools' ) );
22 -
23 - // Handle MCP tool execution
24 - add_filter( 'mwai_mcp_callback', array( $this, 'handle_tool_execution' ), 10, 4 );
28 +
29 + // Nothing to do without AI Engine's MCP server.
30 + if ( !isset( $mwai ) ) {
31 + return;
25 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 );
26 42 }
27 -
43 +
28 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 ) {
29 145 // Get Snippet
30 146 $tools[] = [
31 - 'name' => 'code_engine_get_snippet',
147 + 'name' => 'mwcode_get_snippet',
32 148 'description' => 'Get a Code Engine snippet by its ID',
33 149 'category' => 'Code Engine',
34 150 'inputSchema' => [
35 151 'type' => 'object',
@@ -43,9 +159,9 @@
43 159 'description' => 'Optional filtering options',
44 160 'properties' => [
45 161 'php_ready_args' => [
46 162 'type' => 'boolean',
47 - 'description' => 'If false, arguments will not be formatted for PHP (no $ before names)'
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").'
48 164 ]
49 165 ]
50 166 ]
51 167 ],
@@ -54,9 +170,9 @@
54 170 ];
55 171
56 172 // Get Snippet by Name
57 173 $tools[] = [
58 - 'name' => 'code_engine_get_snippet_by_name',
174 + 'name' => 'mwcode_get_snippet_by_name',
59 175 'description' => 'Get a Code Engine snippet by its name',
60 176 'category' => 'Code Engine',
61 177 'inputSchema' => [
62 178 'type' => 'object',
@@ -70,9 +186,9 @@
70 186 'description' => 'Optional filtering options',
71 187 'properties' => [
72 188 'php_ready_args' => [
73 189 'type' => 'boolean',
74 - 'description' => 'If false, arguments will not be formatted for PHP (no $ before names)'
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").'
75 191 ]
76 192 ]
77 193 ]
78 194 ],
@@ -81,9 +197,9 @@
81 197 ];
82 198
83 199 // Get Snippets
84 200 $tools[] = [
85 - 'name' => 'code_engine_get_snippets',
201 + 'name' => 'mwcode_get_snippets',
86 202 'description' => 'Get all Code Engine snippets, optionally filtered by scope',
87 203 'category' => 'Code Engine',
88 204 'inputSchema' => [
89 205 'type' => 'object',
@@ -89,23 +205,23 @@
89 205 'type' => 'object',
90 206 'properties' => [
91 207 'safe' => [
92 208 'type' => 'boolean',
93 - 'description' => 'Whether to filter out snippets with invalid names',
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.',
94 210 'default' => true
95 211 ],
96 212 'scope' => [
97 213 'type' => 'string',
98 - 'description' => 'Optional scope filter',
99 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
214 + 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
215 + 'enum' => self::SCOPES
100 216 ]
101 217 ]
102 218 ]
103 219 ];
104 -
220 +
105 221 // Execute Snippet
106 222 $tools[] = [
107 - 'name' => 'code_engine_execute_snippet',
223 + 'name' => 'mwcode_execute_snippet',
108 224 'description' => 'Execute a Code Engine snippet by its ID',
109 225 'category' => 'Code Engine',
110 226 'inputSchema' => [
111 227 'type' => 'object',
@@ -125,9 +241,9 @@
125 241 ];
126 242
127 243 // Execute Snippet by Name
128 244 $tools[] = [
129 - 'name' => 'code_engine_execute_snippet_by_name',
245 + 'name' => 'mwcode_execute_snippet_by_name',
130 246 'description' => 'Execute a Code Engine snippet by its name',
131 247 'category' => 'Code Engine',
132 248 'inputSchema' => [
133 249 'type' => 'object',
@@ -147,9 +263,9 @@
147 263 ];
148 264
149 265 // Create Snippet
150 266 $tools[] = [
151 - 'name' => 'code_engine_create_snippet',
267 + 'name' => 'mwcode_create_snippet',
152 268 'description' => 'Create a new Code Engine snippet',
153 269 'category' => 'Code Engine',
154 270 'inputSchema' => [
155 271 'type' => 'object',
@@ -163,10 +279,10 @@
163 279 'description' => 'Code of the snippet'
164 280 ],
165 281 'scope' => [
166 282 'type' => 'string',
167 - 'description' => 'Scope of the snippet',
168 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent'],
283 + 'description' => 'Scope of the snippet. ' . self::SCOPE_DESC . ' Defaults to "function".',
284 + 'enum' => self::SCOPES,
169 285 'default' => 'function'
170 286 ],
171 287 'options' => [
172 288 'type' => 'object',
@@ -202,8 +318,12 @@
202 318 'type' => 'string',
203 319 'enum' => ['dynamic', 'static'],
204 320 'description' => 'Behavior for function snippets'
205 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 + ],
206 326 'tags' => [
207 327 'type' => 'array',
208 328 'description' => 'Array of tags',
209 329 'items' => [ 'type' => 'string' ]
@@ -241,9 +361,9 @@
241 361 ];
242 362
243 363 // Update Snippet
244 364 $tools[] = [
245 - 'name' => 'code_engine_update_snippet',
365 + 'name' => 'mwcode_update_snippet',
246 366 'description' => 'Update an existing Code Engine snippet',
247 367 'category' => 'Code Engine',
248 368 'inputSchema' => [
249 369 'type' => 'object',
@@ -259,9 +379,10 @@
259 379 'name' => [ 'type' => 'string' ],
260 380 'code' => [ 'type' => 'string' ],
261 381 'scope' => [
262 382 'type' => 'string',
263 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
383 + 'description' => self::SCOPE_DESC,
384 + 'enum' => self::SCOPES
264 385 ],
265 386 'description' => [ 'type' => 'string' ],
266 387 'active' => [ 'type' => 'boolean' ],
267 388 'priority' => [ 'type' => 'integer' ],
@@ -289,8 +410,12 @@
289 410 'behavior' => [
290 411 'type' => 'string',
291 412 'enum' => ['dynamic', 'static']
292 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 + ],
293 418 'intervalHours' => [ 'type' => 'integer' ],
294 419 'intervalMinutes' => [ 'type' => 'integer' ]
295 420 ]
296 421 ]
@@ -300,9 +425,9 @@
300 425 ];
301 426
302 427 // Delete Snippet
303 428 $tools[] = [
304 - 'name' => 'code_engine_delete_snippet',
429 + 'name' => 'mwcode_delete_snippet',
305 430 'description' => 'Delete a Code Engine snippet by its ID',
306 431 'category' => 'Code Engine',
307 432 'inputSchema' => [
308 433 'type' => 'object',
@@ -317,9 +442,9 @@
317 442 ];
318 443
319 444 // Delete Snippet by Name
320 445 $tools[] = [
321 - 'name' => 'code_engine_delete_snippet_by_name',
446 + 'name' => 'mwcode_delete_snippet_by_name',
322 447 'description' => 'Delete a Code Engine snippet by its name',
323 448 'category' => 'Code Engine',
324 449 'inputSchema' => [
325 450 'type' => 'object',
@@ -334,9 +459,9 @@
334 459 ];
335 460
336 461 // Activate Snippet
337 462 $tools[] = [
338 - 'name' => 'code_engine_activate_snippet',
463 + 'name' => 'mwcode_activate_snippet',
339 464 'description' => 'Activate a Code Engine snippet',
340 465 'category' => 'Code Engine',
341 466 'inputSchema' => [
342 467 'type' => 'object',
@@ -351,9 +476,9 @@
351 476 ];
352 477
353 478 // Deactivate Snippet
354 479 $tools[] = [
355 - 'name' => 'code_engine_deactivate_snippet',
480 + 'name' => 'mwcode_deactivate_snippet',
356 481 'description' => 'Deactivate a Code Engine snippet',
357 482 'category' => 'Code Engine',
358 483 'inputSchema' => [
359 484 'type' => 'object',
@@ -368,9 +493,9 @@
368 493 ];
369 494
370 495 // Get Snippets by Scope
371 496 $tools[] = [
372 - 'name' => 'code_engine_get_snippets_by_scope',
497 + 'name' => 'mwcode_get_snippets_by_scope',
373 498 'description' => 'Get Code Engine snippets filtered by scope',
374 499 'category' => 'Code Engine',
375 500 'inputSchema' => [
376 501 'type' => 'object',
@@ -376,10 +501,10 @@
376 501 'type' => 'object',
377 502 'properties' => [
378 503 'scope' => [
379 504 'type' => 'string',
380 - 'description' => 'The scope to filter by',
381 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
505 + 'description' => 'The scope to filter by. ' . self::SCOPE_DESC,
506 + 'enum' => self::SCOPES
382 507 ],
383 508 'filters' => [
384 509 'type' => 'object',
385 510 'description' => 'Additional filters',
@@ -401,9 +526,9 @@
401 526 ];
402 527
403 528 // Get Active Snippets
404 529 $tools[] = [
405 - 'name' => 'code_engine_get_active_snippets',
530 + 'name' => 'mwcode_get_active_snippets',
406 531 'description' => 'Get all active Code Engine snippets',
407 532 'category' => 'Code Engine',
408 533 'inputSchema' => [
409 534 'type' => 'object',
@@ -409,18 +534,18 @@
409 534 'type' => 'object',
410 535 'properties' => [
411 536 'scope' => [
412 537 'type' => 'string',
413 - 'description' => 'Optional scope filter',
414 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
538 + 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
539 + 'enum' => self::SCOPES
415 540 ]
416 541 ]
417 542 ]
418 543 ];
419 -
544 +
420 545 // Snippet Exists
421 546 $tools[] = [
422 - 'name' => 'code_engine_snippet_exists',
547 + 'name' => 'mwcode_snippet_exists',
423 548 'description' => 'Check if a Code Engine snippet exists by ID',
424 549 'category' => 'Code Engine',
425 550 'inputSchema' => [
426 551 'type' => 'object',
@@ -435,9 +560,9 @@
435 560 ];
436 561
437 562 // Snippet Exists by Name
438 563 $tools[] = [
439 - 'name' => 'code_engine_snippet_exists_by_name',
564 + 'name' => 'mwcode_snippet_exists_by_name',
440 565 'description' => 'Check if a Code Engine snippet exists by name',
441 566 'category' => 'Code Engine',
442 567 'inputSchema' => [
443 568 'type' => 'object',
@@ -452,9 +577,9 @@
452 577 ];
453 578
454 579 // Validate Snippet Code
455 580 $tools[] = [
456 - 'name' => 'code_engine_validate_snippet_code',
581 + 'name' => 'mwcode_validate_snippet_code',
457 582 'description' => 'Validate snippet code syntax',
458 583 'category' => 'Code Engine',
459 584 'inputSchema' => [
460 585 'type' => 'object',
@@ -476,14 +601,68 @@
476 601
477 602 return $tools;
478 603 }
479 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 +
480 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 +
481 660 // Only handle our tools
482 - if ( strpos( $tool, 'code_engine_' ) !== 0 ) {
661 + if ( strpos( $tool, 'mwcode_' ) !== 0 ) {
483 662 return $result;
484 663 }
485 -
664 +
486 665 // Ensure API is initialized
487 666 if ( !$this->api ) {
488 667 return [ 'success' => false, 'error' => 'Code Engine API not initialized' ];
489 668 }
@@ -489,29 +668,29 @@
489 668 }
490 669
491 670 try {
492 671 switch ( $tool ) {
493 - case 'code_engine_get_snippet':
672 + case 'mwcode_get_snippet':
494 673 $data = $this->api->getSnippet( $args['id'], $args['options'] ?? [] );
495 674 return [ 'success' => true, 'data' => $data ];
496 675
497 - case 'code_engine_get_snippet_by_name':
676 + case 'mwcode_get_snippet_by_name':
498 677 $data = $this->api->getSnippetByName( $args['name'], $args['options'] ?? [] );
499 678 return [ 'success' => true, 'data' => $data ];
500 679
501 - case 'code_engine_get_snippets':
680 + case 'mwcode_get_snippets':
502 681 $data = $this->api->getSnippets( $args['safe'] ?? true, $args['scope'] ?? null );
503 682 return [ 'success' => true, 'data' => $data ];
504 683
505 - case 'code_engine_execute_snippet':
684 + case 'mwcode_execute_snippet':
506 685 $data = $this->api->executeSnippet( $args['id'], $args['args'] ?? [] );
507 686 return [ 'success' => true, 'data' => $data ];
508 687
509 - case 'code_engine_execute_snippet_by_name':
688 + case 'mwcode_execute_snippet_by_name':
510 689 $data = $this->api->executeSnippetByName( $args['name'], $args['args'] ?? [] );
511 690 return [ 'success' => true, 'data' => $data ];
512 691
513 - case 'code_engine_create_snippet':
692 + case 'mwcode_create_snippet':
514 693 $data = $this->api->createSnippet(
515 694 $args['name'],
516 695 $args['code'],
517 696 $args['scope'] ?? 'function',
@@ -518,50 +697,53 @@
518 697 $args['options'] ?? []
519 698 );
520 699 return [ 'success' => true, 'data' => $data ];
521 700
522 - case 'code_engine_update_snippet':
701 + case 'mwcode_update_snippet':
523 702 $data = $this->api->updateSnippet( $args['id'], $args['params'] ?? [] );
524 703 return [ 'success' => true, 'data' => $data ];
525 704
526 - case 'code_engine_delete_snippet':
705 + case 'mwcode_delete_snippet':
527 706 $success = $this->api->deleteSnippet( $args['id'] );
528 707 return [ 'success' => true, 'data' => [ 'deleted' => $success ] ];
529 708
530 - case 'code_engine_delete_snippet_by_name':
709 + case 'mwcode_delete_snippet_by_name':
531 710 $success = $this->api->deleteSnippetByName( $args['name'] );
532 711 return [ 'success' => true, 'data' => [ 'deleted' => $success ] ];
533 712
534 - case 'code_engine_activate_snippet':
713 + case 'mwcode_activate_snippet':
535 714 $success = $this->api->activateSnippet( $args['id'] );
536 715 return [ 'success' => true, 'data' => [ 'activated' => $success ] ];
537 716
538 - case 'code_engine_deactivate_snippet':
717 + case 'mwcode_deactivate_snippet':
539 718 $success = $this->api->deactivateSnippet( $args['id'] );
540 719 return [ 'success' => true, 'data' => [ 'deactivated' => $success ] ];
541 720
542 - case 'code_engine_get_snippets_by_scope':
721 + case 'mwcode_get_snippets_by_scope':
543 722 $data = $this->api->getSnippetsByScope( $args['scope'], $args['filters'] ?? [] );
544 723 return [ 'success' => true, 'data' => $data ];
545 724
546 - case 'code_engine_get_active_snippets':
725 + case 'mwcode_get_active_snippets':
547 726 $data = $this->api->getActiveSnippets( $args['scope'] ?? null );
548 727 return [ 'success' => true, 'data' => $data ];
549 728
550 - case 'code_engine_snippet_exists':
729 + case 'mwcode_snippet_exists':
551 730 $exists = $this->api->snippetExists( $args['id'] );
552 731 return [ 'success' => true, 'data' => [ 'exists' => $exists ] ];
553 732
554 - case 'code_engine_snippet_exists_by_name':
733 + case 'mwcode_snippet_exists_by_name':
555 734 $exists = $this->api->snippetExistsByName( $args['name'] );
556 735 return [ 'success' => true, 'data' => [ 'exists' => $exists ] ];
557 736
558 - case 'code_engine_validate_snippet_code':
737 + case 'mwcode_validate_snippet_code':
559 738 $validation = $this->api->validateSnippetCode( $args['code'], $args['target'] ?? 'php' );
560 739 return [ 'success' => true, 'data' => $validation ];
561 740 }
562 741 }
563 - catch ( Exception $e ) {
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 ) {
564 746 return [ 'success' => false, 'error' => $e->getMessage() ];
565 747 }
566 748
567 749 return $result;