PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / wordpress / mcp-adapter / includes / Domain / Resources / McpResourceValidator.php
ameliabooking / vendor / wordpress / mcp-adapter / includes / Domain / Resources Last commit date
McpResource.php 2 months ago McpResourceValidator.php 2 months ago RegisterAbilityAsMcpResource.php 2 months ago
McpResourceValidator.php
202 lines
1 <?php
2 /**
3 * MCP Resource Validator class for validating MCP resources according to the specification.
4 *
5 * @package McpAdapter
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Domain\Resources;
11
12 use WP\MCP\Domain\Utils\McpValidator;
13 use WP\McpSchema\Server\Resources\DTO\Resource as ResourceDto;
14 use WP_Error;
15
16 /**
17 * Validates MCP resources against the Model Context Protocol specification.
18 *
19 * Provides minimal, resource-efficient validation to ensure resources conform
20 * to the MCP schema requirements without heavy processing overhead.
21 *
22 * @link https://modelcontextprotocol.io/specification/2025-11-25/server/resources
23 */
24 class McpResourceValidator {
25
26 /**
27 * Validate the MCP resource data array against the MCP schema.
28 *
29 * @param array $resource_data The resource data to validate.
30 * @param string $context Optional context for error messages.
31 *
32 * @return bool|\WP_Error True if valid, WP_Error if validation fails.
33 */
34 public static function validate_resource_data( array $resource_data, string $context = '' ) {
35 $validation_errors = self::get_validation_errors( $resource_data );
36
37 if ( ! empty( $validation_errors ) ) {
38 $error_message = $context ? "[{$context}] " : '';
39 $error_message .= sprintf(
40 /* translators: %s: comma-separated list of validation errors */
41 __( 'Resource validation failed: %s', 'mcp-adapter' ),
42 implode( ', ', $validation_errors )
43 );
44 return new WP_Error( 'mcp_resource_validation_failed', esc_html( $error_message ) );
45 }
46
47 return true;
48 }
49
50 /**
51 * Validate a Resource DTO against the MCP schema.
52 *
53 * @param \WP\McpSchema\Server\Resources\DTO\Resource $resource_dto The resource DTO to validate.
54 *
55 * @return bool|\WP_Error True if valid, WP_Error otherwise.
56 */
57 public static function validate_resource_dto( ResourceDto $resource_dto ) {
58 $errors = array();
59
60 // Validate URI.
61 if ( ! McpValidator::validate_resource_uri( $resource_dto->getUri() ) ) {
62 $errors[] = __( 'Resource URI must be a valid URI string', 'mcp-adapter' );
63 }
64
65 // Validate the MIME type if present.
66 $mime_type = $resource_dto->getMimeType();
67 if ( $mime_type && ! McpValidator::validate_mime_type( $mime_type ) ) {
68 $errors[] = __( 'Resource MIME type is invalid', 'mcp-adapter' );
69 }
70
71 // Validate icons if present.
72 $icons = $resource_dto->getIcons();
73 if ( ! empty( $icons ) ) {
74 $icons_array = array_map( static fn( $icon ) => $icon->toArray(), $icons );
75 $icons_result = McpValidator::validate_icons_array( $icons_array );
76 $icons_errors = self::format_icon_validation_errors( $icons_result );
77 $errors = array_merge( $errors, $icons_errors );
78 }
79
80 // Validate annotations if present.
81 $annotations = $resource_dto->getAnnotations();
82 if ( $annotations ) {
83 $annotation_errors = McpValidator::get_annotation_validation_errors( $annotations->toArray() );
84 $errors = array_merge( $errors, $annotation_errors );
85 }
86
87 if ( ! empty( $errors ) ) {
88 return new WP_Error(
89 'mcp_resource_validation_failed',
90 sprintf(
91 /* translators: %s: list of validation errors */
92 __( 'Resource validation failed: %s', 'mcp-adapter' ),
93 implode( '; ', $errors )
94 )
95 );
96 }
97
98 return true;
99 }
100
101 /**
102 * Validate an McpResource instance against the MCP schema.
103 *
104 * @param \WP\MCP\Domain\Resources\McpResource $the_resource The resource instance to validate.
105 *
106 * @return bool|\WP_Error True if valid, WP_Error if validation fails.
107 */
108 public static function validate_resource_instance( McpResource $the_resource ) {
109 return self::validate_resource_dto( $the_resource->get_protocol_dto() );
110 }
111
112 /**
113 * Get validation errors for MCP resource contents.
114 *
115 * NOTE: This validates the `resource` object used by:
116 * - `resources/read` results (`TextResourceContents` / `BlobResourceContents`)
117 * - `content` blocks of type `resource` (`EmbeddedResource.resource`)
118 *
119 * It does NOT validate the `Resource` metadata object returned by `resources/list`.
120 * For `Resource` DTO validation (resources/list), use validate_resource_dto() instead.
121 *
122 * This validator focuses on the MCP-required fields and ignores unknown fields to remain
123 * forward-compatible with future schema versions.
124 *
125 * @param array $resource_data The resource contents object to validate.
126 *
127 * @return array Array of validation errors, empty if valid.
128 */
129 public static function get_validation_errors( array $resource_data ): array {
130 $errors = array();
131
132 // Validate the required URI field.
133 if ( empty( $resource_data['uri'] ) || ! is_string( $resource_data['uri'] ) ) {
134 $errors[] = __( 'Resource URI is required and must be a non-empty string', 'mcp-adapter' );
135 } elseif ( ! McpValidator::validate_resource_uri( $resource_data['uri'] ) ) {
136 $errors[] = __( 'Resource URI must be a valid URI format', 'mcp-adapter' );
137 }
138
139 // Validate content: at least one of text/blob must be present and correctly typed.
140 // Use array_key_exists to allow empty strings as valid content.
141 $has_text_key = array_key_exists( 'text', $resource_data );
142 $has_blob_key = array_key_exists( 'blob', $resource_data );
143
144 $has_text = $has_text_key && is_string( $resource_data['text'] );
145 $has_blob = $has_blob_key && is_string( $resource_data['blob'] );
146
147 if ( ! $has_text && ! $has_blob ) {
148 $errors[] = __( 'Resource contents must include at least one of: text (string) or blob (base64 string)', 'mcp-adapter' );
149 }
150
151 if ( $has_text_key && ! is_string( $resource_data['text'] ) ) {
152 $errors[] = __( 'Resource text content must be a string when provided', 'mcp-adapter' );
153 }
154
155 if ( $has_blob_key && ! is_string( $resource_data['blob'] ) ) {
156 $errors[] = __( 'Resource blob content must be a string when provided', 'mcp-adapter' );
157 }
158
159 // Validate blob content if present and typed.
160 if ( $has_blob && ! McpValidator::validate_base64( $resource_data['blob'] ) ) {
161 $errors[] = __( 'Resource blob content must be valid base64-encoded data', 'mcp-adapter' );
162 }
163
164 // Validate mimeType if present (optional).
165 if ( isset( $resource_data['mimeType'] ) ) {
166 if ( ! is_string( $resource_data['mimeType'] ) ) {
167 $errors[] = __( 'Resource mimeType must be a string if provided', 'mcp-adapter' );
168 } elseif ( ! McpValidator::validate_mime_type( $resource_data['mimeType'] ) ) {
169 $errors[] = __( 'Resource mimeType must be a valid MIME type format', 'mcp-adapter' );
170 }
171 }
172
173 return $errors;
174 }
175
176 /**
177 * Format icon validation errors from the validation result.
178 *
179 * @param array{valid: array, errors: array} $icons_result The result from validate_icons_array.
180 *
181 * @return array Array of formatted error messages.
182 */
183 private static function format_icon_validation_errors( array $icons_result ): array {
184 $errors = array();
185
186 if ( ! empty( $icons_result['errors'] ) ) {
187 foreach ( $icons_result['errors'] as $error_group ) {
188 foreach ( $error_group['errors'] as $error ) {
189 $errors[] = sprintf(
190 /* translators: 1: icon index, 2: error message */
191 __( 'Icon at index %1$d: %2$s', 'mcp-adapter' ),
192 $error_group['index'],
193 $error
194 );
195 }
196 }
197 }
198
199 return $errors;
200 }
201 }
202