PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.5.0
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.5.0
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 0.5.0, at classes/mcp.php

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