PluginProbe
ZIP AI – AI Website Builder & AI Agent / 0.0.13
ZIP AI – AI Website Builder & AI Agent v0.0.13
0.0.14 0.0.13 0.0.12 0.0.11 0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / inc / core / tool-registry.php

tool-registry.php in ZIP AI – AI Website Builder & AI Agent 0.0.13, at inc/core/tool-registry.php

297 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Tool Registry - Central registry for MCP tools with execution mode support
4 *
5 * @package zip-ai
6 */
7
8 namespace ZipAI\MCP\Classes\Core;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * The Tool_Registry Class.
17 * Provides API for third-party plugins to register MCP tools with different execution modes.
18 */
19 class Tool_Registry {
20
21 /**
22 * Registered tools with execution modes.
23 *
24 * @since 1.0.0
25 * @var array<string, array<string, mixed>>
26 */
27 private $tools = array();
28
29 /**
30 * Whether the Abilities API has already been folded into $tools.
31 *
32 * @since 0.0.11
33 * @var bool
34 */
35 private $synced = false;
36
37 /**
38 * Constructor of this class.
39 *
40 * @since 1.0.0
41 * @return void
42 */
43 public function __construct() {
44 // Allow third-party plugins to register tools after abilities API is ready.
45 add_action( 'wp_abilities_api_init', array( $this, 'trigger_tool_registration' ), 999 );
46
47 // Sync the tool list for the admin screens only. Nothing on the front end reads it,
48 // and reaching for the registry there boots the entire Abilities API.
49 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_tool_metadata' ), 20 );
50 }
51
52 /**
53 * Trigger the tool registration action for third-party plugins.
54 *
55 * @since 1.0.0
56 * @return void
57 */
58 public function trigger_tool_registration() {
59 /**
60 * Fires when ZipWP MCP is ready to accept tool registrations.
61 *
62 * Third-party plugins should hook into this action to register their tools.
63 *
64 * @since 1.0.0
65 */
66 do_action( 'zip_ai_register_tools', $this );
67 }
68
69 /**
70 * Register a new MCP tool.
71 *
72 * @param string $tool_name Unique tool name (e.g., 'myplugin/my-action').
73 * @param array<string, mixed> $args Tool configuration arguments.
74 * @return bool True on success, false on failure.
75 *
76 * @since 1.0.0
77 */
78 public function register_tool( $tool_name, $args ) {
79 // Validate required parameters.
80 if ( empty( $tool_name ) ) {
81 return false;
82 }
83
84 // Default arguments.
85 $defaults = array(
86 'description' => '',
87 'execution_mode' => 'rest_api', // 'rest_api' or 'js_hook'
88 'input_schema' => array(),
89 'examples' => array(),
90 'keywords' => array(),
91 'api_endpoint' => null, // For rest_api mode
92 'js_handler' => null, // For js_hook mode (JavaScript function name)
93 'capabilities' => array( 'edit_posts' ), // WordPress capabilities required
94 'callback' => null, // PHP callback for rest_api mode
95 'preview_mode' => 'none', // 'none', 'client', 'server'
96 );
97
98 /**
99 * Narrowed type for `$args`.
100 *
101 * @var array<string, mixed> $args
102 */
103 $args = wp_parse_args( $args, $defaults );
104
105 // Validate execution_mode.
106 if ( ! in_array( $args['execution_mode'], array( 'rest_api', 'js_hook' ), true ) ) {
107 return false;
108 }
109
110 // For rest_api mode, callback is required.
111 if ( 'rest_api' === $args['execution_mode'] && empty( $args['callback'] ) && empty( $args['api_endpoint'] ) ) {
112 return false;
113 }
114
115 // For js_hook mode, js_handler is required.
116 if ( 'js_hook' === $args['execution_mode'] && empty( $args['js_handler'] ) ) {
117 return false;
118 }
119
120 // Store the tool.
121 $this->tools[ $tool_name ] = $args;
122
123 // Register with WordPress Abilities API if available.
124 if ( class_exists( 'WP_Abilities_Registry' ) ) {
125 $this->register_with_abilities_api( $tool_name, $args );
126 }
127
128 return true;
129 }
130
131 /**
132 * Register tool with WordPress Abilities API.
133 *
134 * @param string $tool_name Tool name.
135 * @param array<string, mixed> $args Tool arguments.
136 * @return void
137 *
138 * @since 1.0.0
139 */
140 private function register_with_abilities_api( $tool_name, $args ) {
141 $registry = \WP_Abilities_Registry::get_instance();
142
143 if ( null === $registry ) {
144 return;
145 }
146
147 // Create ability configuration.
148 $ability_config = array(
149 'description' => $args['description'],
150 'input_schema' => $args['input_schema'],
151 'meta' => array(
152 'examples' => $args['examples'],
153 'keywords' => $args['keywords'],
154 'execution_mode' => $args['execution_mode'],
155 'js_handler' => $args['js_handler'],
156 'preview_mode' => $args['preview_mode'],
157 ),
158 );
159
160 // Add API endpoint for rest_api mode.
161 if ( 'rest_api' === $args['execution_mode'] && ! empty( $args['api_endpoint'] ) ) {
162 $ability_config['meta']['api_endpoint'] = $args['api_endpoint'];
163 }
164
165 // Add callback executor if provided.
166 if ( ! empty( $args['callback'] ) ) {
167 $ability_config['executor'] = $args['callback'];
168 }
169
170 // Register the ability.
171 $registry->register( $tool_name, $ability_config );
172 }
173
174 /**
175 * Get all registered tools.
176 *
177 * @return array<string, array<string, mixed>> Array of registered tools.
178 *
179 * @since 1.0.0
180 */
181 public function get_all_tools() {
182 $this->sync_from_abilities_api();
183 return $this->tools;
184 }
185
186 /**
187 * Get a specific tool by name.
188 *
189 * @param string $tool_name Tool name.
190 * @return array<string, mixed>|null Tool configuration or null if not found.
191 *
192 * @since 1.0.0
193 */
194 public function get_tool( $tool_name ) {
195 $this->sync_from_abilities_api();
196 return isset( $this->tools[ $tool_name ] ) ? $this->tools[ $tool_name ] : null;
197 }
198
199 /**
200 * Get tools by execution mode.
201 *
202 * @param string $mode Execution mode ('rest_api' or 'js_hook').
203 * @return array<string, array<string, mixed>> Array of tools matching the execution mode.
204 *
205 * @since 1.0.0
206 */
207 public function get_tools_by_mode( $mode ) {
208 $this->sync_from_abilities_api();
209 return array_filter(
210 $this->tools,
211 function ( $tool ) use ( $mode ) {
212 return isset( $tool['execution_mode'] ) && $tool['execution_mode'] === $mode;
213 }
214 );
215 }
216
217 /**
218 * Enqueue tool metadata for JavaScript.
219 *
220 * @since 1.0.0
221 * @return void
222 */
223 public function enqueue_tool_metadata() {
224 if ( ! is_user_logged_in() ) {
225 return;
226 }
227
228 // Sync tools from WordPress Abilities API
229 $this->sync_from_abilities_api();
230 }
231
232 /**
233 * Sync tools from WordPress Abilities API.
234 * This ensures all abilities registered via wp_register_ability() are included.
235 *
236 * @since 1.0.0
237 * @return void
238 */
239 private function sync_from_abilities_api() {
240 if ( $this->synced || ! class_exists( 'WP_Abilities_Registry' ) ) {
241 return;
242 }
243
244 // Reaching for the registry is what boots the Abilities API and registers every
245 // ability on the site, so only do it when the tool list is actually being read.
246 $registry = \WP_Abilities_Registry::get_instance();
247
248 if ( null === $registry ) {
249 return;
250 }
251
252 $this->synced = true;
253
254 $abilities = $registry->get_all_registered();
255
256 foreach ( $abilities as $ability_name => $ability ) {
257 $ability_name = (string) $ability_name;
258 // Skip if already registered via register_tool()
259 if ( isset( $this->tools[ $ability_name ] ) ) {
260 continue;
261 }
262
263 // Get metadata from ability (WP_Ability has get_meta(), not get_meta_data())
264 /**
265 * Narrowed type for `$meta`.
266 *
267 * @var array<string, mixed> $meta
268 */
269 $meta = $ability->get_meta();
270
271 // Determine execution mode
272 $execution_mode = $meta['execution_mode'] ?? 'rest_api';
273
274 // Add to tools array
275 $this->tools[ $ability_name ] = array(
276 'execution_mode' => $execution_mode,
277 'js_handler' => $meta['js_handler'] ?? null,
278 'preview_mode' => $meta['preview_mode'] ?? 'none',
279 'description' => $ability->get_description(),
280 );
281 }
282 }
283
284 /**
285 * Check if a tool uses JavaScript hook execution.
286 *
287 * @param string $tool_name Tool name.
288 * @return bool True if tool uses JS hooks, false otherwise.
289 *
290 * @since 1.0.0
291 */
292 public function is_js_hook_tool( $tool_name ) {
293 $tool = $this->get_tool( $tool_name );
294 return $tool && 'js_hook' === ( $tool['execution_mode'] ?? '' );
295 }
296 }
297