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 / Infrastructure / ErrorHandling / McpErrorFactory.php

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

481 lines 14.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Factory class for creating MCP error responses.
4 *
5 * @package McpAdapter
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Infrastructure\ErrorHandling;
11
12 use WP\McpSchema\Common\JsonRpc\DTO\Error;
13 use WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse;
14 use WP\McpSchema\Common\McpConstants;
15
16 /**
17 * Factory for creating standardized MCP error responses.
18 *
19 * This class provides static methods for creating various types of JSON-RPC
20 * error responses according to the MCP specification. All methods return
21 * typed DTOs from php-mcp-schema for type safety and protocol compliance.
22 */
23 class McpErrorFactory {
24
25 /**
26 * Standard JSON-RPC error codes as defined in the specification.
27 */
28 public const PARSE_ERROR = McpConstants::PARSE_ERROR;
29 public const INVALID_REQUEST = McpConstants::INVALID_REQUEST;
30 public const METHOD_NOT_FOUND = McpConstants::METHOD_NOT_FOUND;
31 public const INVALID_PARAMS = McpConstants::INVALID_PARAMS;
32 public const INTERNAL_ERROR = McpConstants::INTERNAL_ERROR;
33
34 /**
35 * Implementation-defined server error codes (in -32000 to -32099 range as per JSON-RPC spec).
36 * Using conservative, well-established error codes only.
37 */
38 public const SERVER_ERROR = -32000; // Generic server error (includes MCP disabled)
39 public const TIMEOUT_ERROR = -32001; // Request timeout
40 public const RESOURCE_NOT_FOUND = -32002; // Resource not found
41 public const TOOL_NOT_FOUND = -32003; // Tool not found
42 public const PROMPT_NOT_FOUND = -32004; // Prompt not found
43 public const SESSION_NOT_FOUND = -32005; // Session not found or expired
44 public const PERMISSION_DENIED = -32008; // Access denied/forbidden
45 public const UNAUTHORIZED = -32010; // Authentication required
46
47 /**
48 * Create a parse error response.
49 *
50 * @param string|int|null $id The request ID.
51 * @param string $details Optional additional details.
52 *
53 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
54 */
55 public static function parse_error( $id, string $details = '' ): JSONRPCErrorResponse {
56 $message = __( 'Parse error', 'mcp-adapter' );
57 if ( $details ) {
58 $message .= ': ' . $details;
59 }
60
61 return self::create_error_response( $id, self::PARSE_ERROR, $message );
62 }
63
64 /**
65 * Create a standardized JSON-RPC error response DTO.
66 *
67 * @param string|int|null $id The request ID (JSON-RPC allows string, int, or null).
68 * @param int $code The error code.
69 * @param string $message The error message.
70 * @param mixed|null $data Optional additional error data.
71 *
72 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
73 */
74 public static function create_error_response( $id, int $code, string $message, $data = null ): JSONRPCErrorResponse {
75 return JSONRPCErrorResponse::fromArray(
76 array(
77 'jsonrpc' => McpConstants::JSONRPC_VERSION,
78 'error' => self::create_error( $code, $message, $data ),
79 'id' => $id,
80 )
81 );
82 }
83
84 /**
85 * Create an Error DTO.
86 *
87 * @param int $code The error code.
88 * @param string $message The error message.
89 * @param mixed|null $data Optional additional error data.
90 *
91 * @return \WP\McpSchema\Common\JsonRpc\DTO\Error
92 */
93 public static function create_error( int $code, string $message, $data = null ): Error {
94 return Error::fromArray(
95 array(
96 'code' => $code,
97 'message' => $message,
98 'data' => $data,
99 )
100 );
101 }
102
103 /**
104 * Create a method not found error response.
105 *
106 * @param string|int|null $id The request ID.
107 * @param string $method The method that was not found.
108 *
109 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
110 */
111 public static function method_not_found( $id, string $method ): JSONRPCErrorResponse {
112 return self::create_error_response(
113 $id,
114 self::METHOD_NOT_FOUND,
115 sprintf(
116 /* translators: %s: method name */
117 __( 'Method not found: %s', 'mcp-adapter' ),
118 $method
119 )
120 );
121 }
122
123 /**
124 * Create an invalid params error response.
125 *
126 * @param string|int|null $id The request ID.
127 * @param string $details Optional additional details.
128 *
129 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
130 */
131 public static function invalid_params( $id, string $details = '' ): JSONRPCErrorResponse {
132 $message = __( 'Invalid params', 'mcp-adapter' );
133 if ( $details ) {
134 $message .= ': ' . $details;
135 }
136
137 return self::create_error_response( $id, self::INVALID_PARAMS, $message );
138 }
139
140 /**
141 * Create an internal error response.
142 *
143 * @param string|int|null $id The request ID.
144 * @param string $details Optional additional details.
145 *
146 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
147 */
148 public static function internal_error( $id, string $details = '' ): JSONRPCErrorResponse {
149 $message = __( 'Internal error', 'mcp-adapter' );
150 if ( $details ) {
151 $message .= ': ' . $details;
152 }
153
154 return self::create_error_response( $id, self::INTERNAL_ERROR, $message );
155 }
156
157 /**
158 * Create an MCP disabled error response.
159 *
160 * @param string|int|null $id The request ID.
161 *
162 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
163 */
164 public static function mcp_disabled( $id ): JSONRPCErrorResponse {
165 return self::create_error_response(
166 $id,
167 self::SERVER_ERROR,
168 __( 'MCP functionality is currently disabled', 'mcp-adapter' )
169 );
170 }
171
172 /**
173 * Create a validation error response (uses standard invalid params error).
174 *
175 * @param string|int|null $id The request ID.
176 * @param string $details Validation error details.
177 *
178 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
179 */
180 public static function validation_error( $id, string $details ): JSONRPCErrorResponse {
181 return self::create_error_response(
182 $id,
183 self::INVALID_PARAMS,
184 sprintf(
185 /* translators: %s: validation details */
186 __( 'Validation error: %s', 'mcp-adapter' ),
187 $details
188 )
189 );
190 }
191
192 /**
193 * Create a missing parameter error response.
194 *
195 * @param string|int|null $id The request ID.
196 * @param string $parameter The missing parameter name.
197 *
198 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
199 */
200 public static function missing_parameter( $id, string $parameter ): JSONRPCErrorResponse {
201 return self::create_error_response(
202 $id,
203 self::INVALID_PARAMS,
204 sprintf(
205 /* translators: %s: parameter name */
206 __( 'Missing required parameter: %s', 'mcp-adapter' ),
207 $parameter
208 )
209 );
210 }
211
212 /**
213 * Create a resource not found error response.
214 *
215 * @param string|int|null $id The request ID.
216 * @param string $resource_uri The resource identifier.
217 *
218 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
219 */
220 public static function resource_not_found( $id, string $resource_uri ): JSONRPCErrorResponse {
221 return self::create_error_response(
222 $id,
223 self::RESOURCE_NOT_FOUND,
224 sprintf(
225 /* translators: %s: resource identifier */
226 __( 'Resource not found: %s', 'mcp-adapter' ),
227 $resource_uri
228 )
229 );
230 }
231
232 /**
233 * Create a tool not found error response.
234 *
235 * @param string|int|null $id The request ID.
236 * @param string $tool The tool name.
237 *
238 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
239 */
240 public static function tool_not_found( $id, string $tool ): JSONRPCErrorResponse {
241 return self::create_error_response(
242 $id,
243 self::TOOL_NOT_FOUND,
244 sprintf(
245 /* translators: %s: tool name */
246 __( 'Tool not found: %s', 'mcp-adapter' ),
247 $tool
248 )
249 );
250 }
251
252 /**
253 * Create an ability not found error response.
254 *
255 * @param string|int|null $id The request ID.
256 * @param string $ability The ability name.
257 *
258 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
259 */
260 public static function ability_not_found( $id, string $ability ): JSONRPCErrorResponse {
261 return self::create_error_response(
262 $id,
263 self::TOOL_NOT_FOUND,
264 sprintf(
265 /* translators: %s: ability name */
266 __( 'Ability not found: %s', 'mcp-adapter' ),
267 $ability
268 )
269 );
270 }
271
272 /**
273 * Create a prompt not found error response.
274 *
275 * @param string|int|null $id The request ID.
276 * @param string $prompt The prompt name.
277 *
278 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
279 */
280 public static function prompt_not_found( $id, string $prompt ): JSONRPCErrorResponse {
281 return self::create_error_response(
282 $id,
283 self::PROMPT_NOT_FOUND,
284 sprintf(
285 /* translators: %s: prompt name */
286 __( 'Prompt not found: %s', 'mcp-adapter' ),
287 $prompt
288 )
289 );
290 }
291
292 /**
293 * Create a session not found error response.
294 *
295 * Used when an MCP session ID is invalid or expired. Maps to HTTP 404
296 * per the MCP specification requirement for invalid/expired sessions.
297 *
298 * @param string|int|null $id The request ID.
299 * @param string $details Optional additional details.
300 *
301 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
302 */
303 public static function session_not_found( $id, string $details = '' ): JSONRPCErrorResponse {
304 $message = __( 'Session not found', 'mcp-adapter' );
305 if ( $details ) {
306 $message .= ': ' . $details;
307 }
308
309 return self::create_error_response( $id, self::SESSION_NOT_FOUND, $message );
310 }
311
312 /**
313 * Create a permission denied error response.
314 *
315 * @param string|int|null $id The request ID.
316 * @param string $details Optional additional details.
317 *
318 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
319 */
320 public static function permission_denied( $id, string $details = '' ): JSONRPCErrorResponse {
321 $message = __( 'Permission denied', 'mcp-adapter' );
322 if ( $details ) {
323 $message .= ': ' . $details;
324 }
325
326 return self::create_error_response( $id, self::PERMISSION_DENIED, $message );
327 }
328
329 /**
330 * Create an unauthorized error response.
331 *
332 * @param string|int|null $id The request ID.
333 * @param string $details Optional additional details.
334 *
335 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
336 */
337 public static function unauthorized( $id, string $details = '' ): JSONRPCErrorResponse {
338 $message = __( 'Unauthorized', 'mcp-adapter' );
339 if ( $details ) {
340 $message .= ': ' . $details;
341 }
342
343 return self::create_error_response( $id, self::UNAUTHORIZED, $message );
344 }
345
346 /**
347 * Determine if an MCP error should return HTTP 200 or an HTTP error status.
348 *
349 * This method helps distinguish between transport-level errors (which should
350 * return HTTP error codes) and application-level errors (which should return
351 * HTTP 200 with a JSON-RPC error response).
352 *
353 * @param \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse|array $error_response The MCP error response (DTO or array).
354 *
355 * @return int The appropriate HTTP status code.
356 */
357 public static function get_http_status_for_error( $error_response ): int {
358 // Handle DTO
359 if ( $error_response instanceof JSONRPCErrorResponse ) {
360 return self::mcp_error_to_http_status( $error_response->getError()->getCode() );
361 }
362
363 // Handle legacy array format
364 if ( ! isset( $error_response['error']['code'] ) ) {
365 return 500; // Invalid error response structure
366 }
367
368 return self::mcp_error_to_http_status( $error_response['error']['code'] );
369 }
370
371 /**
372 * Translate MCP error code to appropriate HTTP status code.
373 *
374 * Maps JSON-RPC error codes to HTTP status codes according to best practices:
375 * - Transport-level errors (malformed JSON-RPC) → HTTP 4xx
376 * - Application-level errors (business logic) → HTTP 200 with JSON-RPC error
377 *
378 * @param int|string|float $mcp_error_code The MCP/JSON-RPC error code (integer, float, or string).
379 *
380 * @return int The appropriate HTTP status code.
381 */
382 public static function mcp_error_to_http_status( $mcp_error_code ): int {
383 // Cast to integer for comparison (handles float from DTOs)
384 $code = is_numeric( $mcp_error_code ) ? (int) $mcp_error_code : 0;
385
386 switch ( $code ) {
387 // Transport-level errors - these indicate malformed requests
388 case self::PARSE_ERROR: // Invalid JSON - syntactic error
389 return 400;
390
391 case self::INVALID_REQUEST: // Invalid JSON-RPC structure - syntactic error
392 return 400;
393
394 // Authentication and authorization errors
395 case self::UNAUTHORIZED: // Authentication required
396 return 401;
397
398 case self::PERMISSION_DENIED: // Access forbidden
399 return 403;
400
401 // Resource not found errors
402 case self::RESOURCE_NOT_FOUND:
403 case self::TOOL_NOT_FOUND:
404 case self::PROMPT_NOT_FOUND:
405 case self::SESSION_NOT_FOUND:
406 case self::METHOD_NOT_FOUND:
407 return 404;
408
409 // Server errors
410 case self::INTERNAL_ERROR:
411 case self::SERVER_ERROR:
412 return 500;
413
414 case self::TIMEOUT_ERROR:
415 return 504;
416
417 // Application-level errors - return 200 with JSON-RPC error
418 case self::INVALID_PARAMS:
419 default:
420 return 200;
421 }
422 }
423
424 /**
425 * Validate JSON-RPC message structure.
426 *
427 * @param mixed $message The message to validate.
428 *
429 * @return true|\WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse Returns true if valid, or JSONRPCErrorResponse DTO if invalid.
430 */
431 public static function validate_jsonrpc_message( $message ) {
432 if ( ! is_array( $message ) ) {
433 return self::invalid_request( null, __( 'Message must be a JSON object', 'mcp-adapter' ) );
434 }
435
436 // Must have jsonrpc field with value "2.0".
437 if ( ! isset( $message['jsonrpc'] ) || McpConstants::JSONRPC_VERSION !== $message['jsonrpc'] ) {
438 return self::invalid_request(
439 null,
440 sprintf(
441 /* translators: %s: JSON-RPC version */
442 __( 'jsonrpc version must be "%s"', 'mcp-adapter' ),
443 McpConstants::JSONRPC_VERSION
444 )
445 );
446 }
447
448 // Must be either a request/notification (has method) or a response (has result/error).
449 $is_request_or_notification = isset( $message['method'] );
450 $is_response = isset( $message['result'] ) || isset( $message['error'] );
451
452 if ( ! $is_request_or_notification && ! $is_response ) {
453 return self::invalid_request( null, __( 'Message must have either method or result/error field', 'mcp-adapter' ) );
454 }
455
456 // Responses must have an id field.
457 if ( $is_response && ! isset( $message['id'] ) ) {
458 return self::invalid_request( null, __( 'Response messages must have an id field', 'mcp-adapter' ) );
459 }
460
461 return true;
462 }
463
464 /**
465 * Create an invalid request error response.
466 *
467 * @param string|int|null $id The request ID.
468 * @param string $details Optional additional details.
469 *
470 * @return \WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
471 */
472 public static function invalid_request( $id, string $details = '' ): JSONRPCErrorResponse {
473 $message = __( 'Invalid Request', 'mcp-adapter' );
474 if ( $details ) {
475 $message .= ': ' . $details;
476 }
477
478 return self::create_error_response( $id, self::INVALID_REQUEST, $message );
479 }
480 }
481