PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.5.7
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.5.7
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 +59 -25 0.5.00.5.7 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
@@ -19,11 +30,13 @@
19 30 if ( !isset( $mwai ) ) {
20 31 return;
21 32 }
22 33
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)
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
26 39 // Either one alone is enough to justify hooking the filters.
27 40 add_filter( 'mwai_mcp_tools', array( $this, 'register_tools' ) );
28 41 add_filter( 'mwai_mcp_callback', array( $this, 'handle_tool_execution' ), 10, 4 );
29 42 }
@@ -29,9 +42,13 @@
29 42 }
30 43
31 44 public function register_tools( $tools ) {
32 45 // Individual Callable functions that opted in to MCP, exposed as first-class tools.
33 - $tools = $this->register_function_tools( $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 + }
34 51
35 52 // Code Engine's management/internal API (Settings > For Developers > MCP Support).
36 53 if ( $this->core->get_option( 'mcp_support', false ) ) {
37 54 $tools = $this->register_management_tools( $tools );
@@ -58,19 +75,22 @@
58 75 /**
59 76 * Return the active Callable (function) snippets that opted in to MCP exposure.
60 77 */
61 78 private function get_mcp_functions() {
79 + if ( $this->mcp_functions_cache !== null ) {
80 + return $this->mcp_functions_cache;
81 + }
62 82 global $mwcode;
63 83 if ( !isset( $mwcode ) || !method_exists( $mwcode, 'getSnippets' ) ) {
64 - return [];
84 + return ( $this->mcp_functions_cache = [] );
65 85 }
66 86 $functions = $mwcode->getSnippets( true, 'function' );
67 87 if ( empty( $functions ) ) {
68 - return [];
88 + return ( $this->mcp_functions_cache = [] );
69 89 }
70 - return array_values( array_filter( $functions, function ( $fn ) {
90 + return ( $this->mcp_functions_cache = array_values( array_filter( $functions, function ( $fn ) {
71 91 return !empty( $fn['functionMcp'] ) && !empty( $fn['functionName'] );
72 - } ) );
92 + } ) ) );
73 93 }
74 94
75 95 /**
76 96 * Register each opted-in Callable function as its own MCP tool, named after the
@@ -139,9 +159,9 @@
139 159 'description' => 'Optional filtering options',
140 160 'properties' => [
141 161 'php_ready_args' => [
142 162 'type' => 'boolean',
143 - '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").'
144 164 ]
145 165 ]
146 166 ]
147 167 ],
@@ -166,9 +186,9 @@
166 186 'description' => 'Optional filtering options',
167 187 'properties' => [
168 188 'php_ready_args' => [
169 189 'type' => 'boolean',
170 - '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").'
171 191 ]
172 192 ]
173 193 ]
174 194 ],
@@ -185,20 +205,20 @@
185 205 'type' => 'object',
186 206 'properties' => [
187 207 'safe' => [
188 208 'type' => 'boolean',
189 - '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.',
190 210 'default' => true
191 211 ],
192 212 'scope' => [
193 213 'type' => 'string',
194 - 'description' => 'Optional scope filter',
195 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
214 + 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
215 + 'enum' => self::SCOPES
196 216 ]
197 217 ]
198 218 ]
199 219 ];
200 -
220 +
201 221 // Execute Snippet
202 222 $tools[] = [
203 223 'name' => 'mwcode_execute_snippet',
204 224 'description' => 'Execute a Code Engine snippet by its ID',
@@ -259,10 +279,10 @@
259 279 'description' => 'Code of the snippet'
260 280 ],
261 281 'scope' => [
262 282 'type' => 'string',
263 - 'description' => 'Scope of the snippet',
264 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent'],
283 + 'description' => 'Scope of the snippet. ' . self::SCOPE_DESC . ' Defaults to "function".',
284 + 'enum' => self::SCOPES,
265 285 'default' => 'function'
266 286 ],
267 287 'options' => [
268 288 'type' => 'object',
@@ -359,9 +379,10 @@
359 379 'name' => [ 'type' => 'string' ],
360 380 'code' => [ 'type' => 'string' ],
361 381 'scope' => [
362 382 'type' => 'string',
363 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
383 + 'description' => self::SCOPE_DESC,
384 + 'enum' => self::SCOPES
364 385 ],
365 386 'description' => [ 'type' => 'string' ],
366 387 'active' => [ 'type' => 'boolean' ],
367 388 'priority' => [ 'type' => 'integer' ],
@@ -480,10 +501,10 @@
480 501 'type' => 'object',
481 502 'properties' => [
482 503 'scope' => [
483 504 'type' => 'string',
484 - 'description' => 'The scope to filter by',
485 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
505 + 'description' => 'The scope to filter by. ' . self::SCOPE_DESC,
506 + 'enum' => self::SCOPES
486 507 ],
487 508 'filters' => [
488 509 'type' => 'object',
489 510 'description' => 'Additional filters',
@@ -513,15 +534,15 @@
513 534 'type' => 'object',
514 535 'properties' => [
515 536 'scope' => [
516 537 'type' => 'string',
517 - 'description' => 'Optional scope filter',
518 - 'enum' => ['function', 'backend', 'frontend', 'scheduled', 'persistent']
538 + 'description' => 'Optional scope filter. ' . self::SCOPE_DESC,
539 + 'enum' => self::SCOPES
519 540 ]
520 541 ]
521 542 ]
522 543 ];
523 -
544 +
524 545 // Snippet Exists
525 546 $tools[] = [
526 547 'name' => 'mwcode_snippet_exists',
527 548 'description' => 'Check if a Code Engine snippet exists by ID',
@@ -586,8 +607,15 @@
586 607 * tool name does not match one of our opted-in functions, so the filter chain
587 608 * continues to the management tools (and other plugins).
588 609 */
589 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 +
590 618 $match = null;
591 619 foreach ( $this->get_mcp_functions() as $fn ) {
592 620 if ( $fn['functionName'] === $tool ) {
593 621 $match = $fn;
@@ -608,9 +636,12 @@
608 636 try {
609 637 $output = $this->api->executeSnippet( $snippetId, is_array( $args ) ? $args : [] );
610 638 return [ 'success' => true, 'data' => $output ];
611 639 }
612 - catch ( Exception $e ) {
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 ) {
613 644 return [ 'success' => false, 'error' => $e->getMessage() ];
614 645 }
615 646 }
616 647
@@ -707,9 +738,12 @@
707 738 $validation = $this->api->validateSnippetCode( $args['code'], $args['target'] ?? 'php' );
708 739 return [ 'success' => true, 'data' => $validation ];
709 740 }
710 741 }
711 - 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 ) {
712 746 return [ 'success' => false, 'error' => $e->getMessage() ];
713 747 }
714 748
715 749 return $result;