PluginProbe
ZIP AI – AI Website Builder & AI Agent (Beta) / 0.0.8
ZIP AI – AI Website Builder & AI Agent (Beta) v0.0.8
0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / lib / mcp-adapter / includes / Core / McpAdapter.php

McpAdapter.php in ZIP AI – AI Website Builder & AI Agent (Beta) 0.0.8, at lib/mcp-adapter/includes/Core/McpAdapter.php

352 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WordPress MCP Registry - Main class for managing multiple MCP servers.
4 *
5 * @package WP\MCP\Core
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Core;
11
12 use WP\MCP\Abilities\DiscoverAbilitiesAbility;
13 use WP\MCP\Abilities\ExecuteAbilityAbility;
14 use WP\MCP\Abilities\GetAbilityInfoAbility;
15 use WP\MCP\Cli\McpCommand;
16 use WP\MCP\Infrastructure\ErrorHandling\Contracts\McpErrorHandlerInterface;
17 use WP\MCP\Infrastructure\ErrorHandling\NullMcpErrorHandler;
18 use WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface;
19 use WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler;
20 use WP\MCP\Servers\DefaultServerFactory;
21 use WP_Error;
22
23 /**
24 * WordPress MCP Registry - Main class for managing multiple MCP servers.
25 */
26 final class McpAdapter {
27
28 public const VERSION = '0.5.0';
29
30 /**
31 * Registry instance
32 *
33 * @var \WP\MCP\Core\McpAdapter
34 */
35 private static self $instance;
36 /**
37 * Track if the adapter has been initialized to prevent duplicate initialization
38 *
39 * @var bool
40 */
41 private static bool $initialized = false;
42 /**
43 * Registered servers
44 *
45 * @var \WP\MCP\Core\McpServer[]
46 */
47 private array $servers = array();
48
49 /**
50 * Get the registry instance
51 *
52 * @return \WP\MCP\Core\McpAdapter
53 */
54 public static function instance(): self {
55 if ( ! isset( self::$instance ) ) {
56 self::$instance = new self();
57
58 // In WP-CLI context, initialize immediately so commands have access to servers
59 if ( defined( 'WP_CLI' ) && constant( 'WP_CLI' ) ) {
60 add_action( 'init', array( self::$instance, 'init' ), 20 );
61 } else {
62 // Initialize for REST API requests with reasonable priority
63 add_action( 'rest_api_init', array( self::$instance, 'init' ), 15 );
64 }
65 }
66
67 return self::$instance;
68 }
69
70 /**
71 * Initialize the registry
72 *
73 * @internal For use by instance initialization only.
74 */
75 public function init(): void {
76 if ( self::$initialized ) {
77 return;
78 }
79
80 $this->maybe_create_default_server();
81
82 /**
83 * Fires after the MCP Adapter has been initialized.
84 *
85 * Use this action to register custom MCP servers. The adapter instance
86 * provides methods to create and configure additional servers beyond
87 * the default server.
88 *
89 * @since 0.1.0
90 *
91 * @param \WP\MCP\Core\McpAdapter $adapter The MCP Adapter singleton instance.
92 */
93 do_action( 'mcp_adapter_init', $this );
94 $this->register_wp_cli_commands();
95 self::$initialized = true;
96 }
97
98 /**
99 * Conditionally create the default server based on filter.
100 *
101 * @internal For use by adapter initialization only.
102 */
103 private function maybe_create_default_server(): void {
104 /**
105 * Filters whether the default MCP server should be created.
106 *
107 * Return false to prevent the default server from being created.
108 * This is useful when you want to define custom servers only.
109 *
110 * @since 0.3.0
111 *
112 * @param bool $create_default Whether to create the default server. Default true.
113 */
114 if ( ! apply_filters( 'mcp_adapter_create_default_server', true ) ) {
115 return;
116 }
117
118 // Register category before abilities
119 add_action( 'wp_abilities_api_categories_init', array( $this, 'register_default_category' ) );
120 add_action( 'wp_abilities_api_init', array( $this, 'register_default_abilities' ) );
121
122 add_action( 'mcp_adapter_init', array( DefaultServerFactory::class, 'create' ) );
123 }
124
125 /**
126 * Register WP-CLI commands if WP-CLI is available
127 *
128 * @internal For use by adapter initialization only.
129 */
130 private function register_wp_cli_commands(): void {
131 // Only register if WP-CLI is available
132 if ( ! defined( 'WP_CLI' ) || ! constant( 'WP_CLI' ) ) {
133 return;
134 }
135
136 if ( ! class_exists( '\WP_CLI' ) ) {
137 return;
138 }
139
140 \WP_CLI::add_command(
141 'mcp-adapter',
142 McpCommand::class,
143 array(
144 'shortdesc' => 'Manage MCP servers via WP-CLI.',
145 'longdesc' => 'Commands for managing and serving MCP servers, including STDIO transport.',
146 )
147 );
148 }
149
150 /**
151 * Create and register a new MCP server.
152 *
153 * @param string $server_id Unique identifier for the server.
154 * @param string $server_route_namespace Server route namespace.
155 * @param string $server_route Server route.
156 * @param string $server_name Server name.
157 * @param string $server_description Server description.
158 * @param string $server_version Server version.
159 * @param array<class-string<\WP\MCP\Transport\Contracts\McpTransportInterface>> $mcp_transports Array of MCP transport class names to initialize.
160 * @param class-string<\WP\MCP\Infrastructure\ErrorHandling\Contracts\McpErrorHandlerInterface>|null $error_handler The error handler class name. If null, NullMcpErrorHandler will be used.
161 * @param class-string<\WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface>|null $observability_handler The observability handler class name. If null, NullMcpObservabilityHandler will be used.
162 * @param list<string> $tools Ability names to register as tools.
163 * @param list<string> $resources Resources to register.
164 * @param list<string> $prompts Prompts to register.
165 * @param callable|null $transport_permission_callback Optional custom permission callback for transport-level authentication. If null, defaults to is_user_logged_in().
166 *
167 * @return \WP\MCP\Core\McpAdapter|\WP_Error McpAdapter instance on success, WP_Error on failure.
168 */
169 public function create_server( string $server_id, string $server_route_namespace, string $server_route, string $server_name, string $server_description, string $server_version, array $mcp_transports, ?string $error_handler, ?string $observability_handler = null, array $tools = array(), array $resources = array(), array $prompts = array(), ?callable $transport_permission_callback = null ) {
170 // Use NullMcpErrorHandler if no error handler is provided.
171 if ( ! $error_handler ) {
172 $error_handler = NullMcpErrorHandler::class;
173 }
174
175 // Validate error handler class exists and implements McpErrorHandlerInterface.
176 if ( ! class_exists( $error_handler ) ) {
177 return new WP_Error(
178 'invalid_error_handler',
179 sprintf(
180 /* translators: %s: error handler class name */
181 esc_html__( 'Error handler class "%s" does not exist.', 'mcp-adapter' ),
182 esc_html( $error_handler )
183 )
184 );
185 }
186
187 if ( ! in_array( McpErrorHandlerInterface::class, class_implements( $error_handler ) ?: array(), true ) ) {
188 return new WP_Error(
189 'invalid_error_handler',
190 sprintf(
191 /* translators: %s: error handler class name */
192 esc_html__( 'Error handler class "%s" must implement the McpErrorHandlerInterface.', 'mcp-adapter' ),
193 esc_html( $error_handler )
194 )
195 );
196 }
197
198 // Use NullMcpObservabilityHandler if no observability handler is provided.
199 if ( ! $observability_handler ) {
200 $observability_handler = NullMcpObservabilityHandler::class;
201 }
202
203 // Validate observability handler class exists and implements McpObservabilityHandlerInterface.
204 if ( ! class_exists( $observability_handler ) ) {
205 return new WP_Error(
206 'invalid_observability_handler',
207 sprintf(
208 /* translators: %s: observability handler class name */
209 esc_html__( 'Observability handler class "%s" does not exist.', 'mcp-adapter' ),
210 esc_html( $observability_handler )
211 )
212 );
213 }
214
215 if ( ! in_array( McpObservabilityHandlerInterface::class, class_implements( $observability_handler ) ?: array(), true ) ) {
216 return new WP_Error(
217 'invalid_observability_handler',
218 sprintf(
219 /* translators: %s: observability handler class name */
220 esc_html__( 'Observability handler class "%s" must implement the McpObservabilityHandlerInterface interface.', 'mcp-adapter' ),
221 esc_html( $observability_handler )
222 )
223 );
224 }
225
226 if ( ! doing_action( 'mcp_adapter_init' ) ) {
227 _doing_it_wrong(
228 __FUNCTION__,
229 esc_html__( 'MCP Servers must be created during the "mcp_adapter_init" action. Hook into "mcp_adapter_init" to register your server.', 'mcp-adapter' ),
230 '0.1.0'
231 );
232
233 return new WP_Error(
234 'invalid_timing',
235 esc_html__( 'MCP Server creation must be done during mcp_adapter_init action.', 'mcp-adapter' )
236 );
237 }
238
239 if ( isset( $this->servers[ $server_id ] ) ) {
240 _doing_it_wrong(
241 __FUNCTION__,
242 sprintf(
243 // translators: %s: server ID
244 esc_html__( 'Server with ID "%s" already exists. Each server must have a unique ID.', 'mcp-adapter' ),
245 esc_html( $server_id )
246 ),
247 '0.1.0'
248 );
249
250 return new WP_Error(
251 'duplicate_server_id',
252 // translators: %s: server ID.
253 sprintf( esc_html__( 'Server with ID "%s" already exists.', 'mcp-adapter' ), esc_html( $server_id ) )
254 );
255 }
256
257 // Create server with tools, resources, and prompts - let server handle all registration logic.
258 try {
259 $server = new McpServer(
260 $server_id,
261 $server_route_namespace,
262 $server_route,
263 $server_name,
264 $server_description,
265 $server_version,
266 $mcp_transports,
267 $error_handler,
268 $observability_handler,
269 $tools,
270 $resources,
271 $prompts,
272 $transport_permission_callback
273 );
274 } catch ( \Throwable $e ) {
275 return new WP_Error(
276 'server_creation_failed',
277 sprintf(
278 /* translators: 1: server ID, 2: error message */
279 esc_html__( 'Failed to create server "%1$s": %2$s', 'mcp-adapter' ),
280 esc_html( $server_id ),
281 esc_html( $e->getMessage() )
282 )
283 );
284 }
285
286 // Track server creation.
287 $server->get_observability_handler()->record_event(
288 'mcp.server.created',
289 array(
290 'status' => 'success',
291 'server_id' => $server_id,
292 'transport_count' => count( $mcp_transports ),
293 'tools_count' => count( $tools ),
294 'resources_count' => count( $resources ),
295 'prompts_count' => count( $prompts ),
296 )
297 );
298
299 // Add server to registry.
300 $this->servers[ $server_id ] = $server;
301
302 return $this;
303 }
304
305 /**
306 * Get a server by ID.
307 *
308 * @param string $server_id Server ID.
309 *
310 * @return \WP\MCP\Core\McpServer|null
311 */
312 public function get_server( string $server_id ): ?McpServer {
313 return $this->servers[ $server_id ] ?? null;
314 }
315
316 /**
317 * Get all registered servers
318 *
319 * @return \WP\MCP\Core\McpServer[]
320 */
321 public function get_servers(): array {
322 return $this->servers;
323 }
324
325 /**
326 * Register the default MCP category.
327 *
328 * @return void
329 */
330 public function register_default_category(): void {
331 wp_register_ability_category(
332 'mcp-adapter',
333 array(
334 'label' => 'MCP Adapter',
335 'description' => 'Abilities for the MCP Adapter',
336 )
337 );
338 }
339
340 /**
341 * Register the default MCP abilities.
342 *
343 * @return void
344 */
345 public function register_default_abilities(): void {
346 // Register the three core MCP abilities
347 DiscoverAbilitiesAbility::register();
348 GetAbilityInfoAbility::register();
349 ExecuteAbilityAbility::register();
350 }
351 }
352