PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.1
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Modules / MCP / AbilitiesRegistrar.php

AbilitiesRegistrar.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.1, at app/Modules/MCP/AbilitiesRegistrar.php

134 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\MCP;
4
5 use FluentCart\App\Modules\MCP\Tools\ContextTools;
6 use FluentCart\App\Modules\MCP\Tools\OrderTools;
7 use FluentCart\App\Modules\MCP\Tools\CustomerTools;
8 use FluentCart\App\Modules\MCP\Tools\ProductTools;
9 use FluentCart\App\Modules\MCP\Tools\SubscriptionTools;
10 use FluentCart\App\Modules\MCP\Tools\CouponTools;
11 use FluentCart\App\Modules\MCP\Tools\LabelTools;
12 use FluentCart\App\Modules\MCP\Tools\ReportTools;
13
14 /**
15 * Single source of truth for every FluentCart MCP ability.
16 *
17 * Each tool class owns its own `definitions()` slice (schema next to code);
18 * this class merges them, wraps every execute_callback so unhandled exceptions
19 * become structured WP_Errors the agent can read (instead of the adapter's
20 * generic "Tool execution failed"), and registers each as a WP ability.
21 *
22 * Pro tools are NOT listed here — FluentCart Pro pushes its abilities via the
23 * `fluent_cart/mcp_loaded` action + `fluent_cart/mcp_ability_names` filter.
24 */
25 class AbilitiesRegistrar
26 {
27 /** Tool classes that expose a static definitions() method. */
28 private static function toolClasses()
29 {
30 return [
31 ContextTools::class,
32 OrderTools::class,
33 CustomerTools::class,
34 ProductTools::class,
35 SubscriptionTools::class,
36 CouponTools::class,
37 LabelTools::class,
38 ReportTools::class,
39 ];
40 }
41
42 public static function getDefinitions()
43 {
44 $defs = [];
45
46 foreach (self::toolClasses() as $class) {
47 if (class_exists($class) && method_exists($class, 'definitions')) {
48 $defs = array_merge($defs, (array) $class::definitions());
49 }
50 }
51
52 return $defs;
53 }
54
55 public static function register()
56 {
57 foreach (self::getDefinitions() as $name => $definition) {
58 $args = [
59 'label' => $definition['label'],
60 'description' => $definition['description'],
61 'category' => 'fluent-cart',
62 'execute_callback' => self::wrapExecuteCallback($name, $definition['execute_callback']),
63 'permission_callback' => $definition['permission_callback'],
64 'meta' => [
65 'show_in_rest' => true,
66 'mcp' => ['public' => true],
67 ],
68 ];
69
70 if (!empty($definition['input_schema'])) {
71 $args['input_schema'] = $definition['input_schema'];
72 }
73
74 if (!empty($definition['output_schema'])) {
75 $args['output_schema'] = $definition['output_schema'];
76 }
77
78 if (!empty($definition['annotations'])) {
79 // snake_case keys; the adapter's McpAnnotationMapper translates
80 // readonly -> readOnlyHint, destructive -> destructiveHint, etc.
81 $args['meta']['annotations'] = $definition['annotations'];
82 }
83
84 wp_register_ability($name, $args);
85 }
86 }
87
88 /**
89 * Convert any unhandled \Throwable from a tool into a structured WP_Error
90 * carrying the real message (and, under WP_DEBUG, the file + a short trace).
91 * Without this the agent only sees the adapter's generic failure surface and
92 * retries blindly against tools that may have partially succeeded.
93 */
94 private static function wrapExecuteCallback($toolName, $callback)
95 {
96 return function ($params) use ($toolName, $callback) {
97 try {
98 return call_user_func($callback, $params);
99 } catch (\Throwable $e) {
100 /**
101 * Fires when an MCP tool throws. Lets sites log/alert before the
102 * structured error reaches the agent.
103 *
104 * @since 1.0.0
105 *
106 * @param array $context { exception: \Throwable, tool: string, params: mixed }
107 */
108 do_action('fluent_cart/mcp_tool_exception', [
109 'exception' => $e,
110 'tool' => $toolName,
111 'params' => $params,
112 ]);
113
114 // Unexpected exceptions are treated as transient (retryable):
115 // the agent may legitimately retry once.
116 $details = ['tool' => $toolName, 'exception' => get_class($e), 'retryable' => true];
117
118 // File/line/trace help an operator debug, but this payload is
119 // forwarded to the remote agent/LLM — raw paths would leak the
120 // server's filesystem layout. So it's off by default and opt-in
121 // only; full detail is always available server-side via the
122 // action above. When enabled, the file is reduced to a basename.
123 $exposeDetails = apply_filters('fluent_cart/mcp_expose_error_details', false);
124 if ($exposeDetails) {
125 $details['file'] = basename($e->getFile()) . ':' . $e->getLine();
126 $details['trace'] = array_slice(explode("\n", $e->getTraceAsString()), 0, 5);
127 }
128
129 return \FluentCart\App\Modules\MCP\Support\MCPHelper::error('tool_failed', $e->getMessage(), $details);
130 }
131 };
132 }
133 }
134