Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
Utils
/
Utils
/
Results
:
McpValidator.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * MCP Validator utility class for validating MCP component data. * * @package McpAdapter */ declare( strict_types=1 ); namespace WP\MCP\Domain\Utils; /** * Utility class for validating MCP component data according to MCP specification. * * Provides shared validation implementations used across multiple MCP component * validators and registration classes. Each method focuses on a specific validation concern. */ class McpValidator { /** * Validate ISO 8601 timestamp format. * * Checks if a string is a valid ISO 8601 timestamp by attempting to parse * it using multiple ISO 8601 format variations. * * @param string $timestamp The timestamp to validate. * * @return bool True if valid ISO 8601 timestamp, false otherwise. */ public static function validate_iso8601_timestamp( string $timestamp ): bool { // Try to parse as DateTime with ISO 8601 format. $datetime = \DateTime::createFromFormat( \DateTime::ATOM, $timestamp ); if ( $datetime && $datetime->format( \DateTime::ATOM ) === $timestamp ) { return true; } // Try alternative ISO 8601 formats. $formats = array( 'Y-m-d\TH:i:s\Z', // UTC format 'Y-m-d\TH:i:sP', // With timezone offset 'Y-m-d\TH:i:s.u\Z', // With microseconds UTC 'Y-m-d\TH:i:s.uP', // With microseconds and timezone ); foreach ( $formats as $format ) { $datetime = \DateTime::createFromFormat( $format, $timestamp ); if ( $datetime && $datetime->format( $format ) === $timestamp ) { return true; } } return false; } /** * Validate an MCP component name. * * Validates that a name follows MCP naming conventions: * - Must not be empty * - Must not exceed the maximum length * - Must only contain letters, numbers, hyphens (-), and underscores (_) * * @param string $name The name to validate. * @param int $max_length Maximum allowed length. Default is 255. * * @return bool True if valid, false otherwise. */ public static function validate_name( string $name, int $max_length = 255 ): bool { // Names should not be empty. if ( empty( $name ) ) { return false; } // Check length constraints. if ( strlen( $name ) > $max_length ) { return false; } // Only allow letters, numbers, hyphens, and underscores. return (bool) preg_match( '/^[a-zA-Z0-9_-]+$/', $name ); } /** * Validate a tool or prompt name (max 255 characters). * * @param string $name The name to validate. * * @return bool True if valid, false otherwise. */ public static function validate_tool_or_prompt_name( string $name ): bool { return self::validate_name( $name, 255 ); } /** * Validate an argument name (max 64 characters). * * @param string $name The name to validate. * * @return bool True if valid, false otherwise. */ public static function validate_argument_name( string $name ): bool { return self::validate_name( $name, 64 ); } /** * Validate general MIME type format. * * Validates that a MIME type follows the standard format: type/subtype * where both type and subtype contain valid characters. * * @param string $mime_type The MIME type to validate. * * @return bool True if valid MIME type format, false otherwise. */ public static function validate_mime_type( string $mime_type ): bool { return (bool) preg_match( '/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_]*$/', $mime_type ); } /** * Validate image MIME type. * * Checks if the MIME type is a valid image type according to MCP specification. * * @param string $mime_type The MIME type to validate. * * @return bool True if valid image MIME type, false otherwise. */ public static function validate_image_mime_type( string $mime_type ): bool { return str_starts_with( strtolower( $mime_type ), 'image/' ); } /** * Validate audio MIME type. * * Checks if the MIME type is a valid audio type according to MCP specification. * * @param string $mime_type The MIME type to validate. * * @return bool True if valid audio MIME type, false otherwise. */ public static function validate_audio_mime_type( string $mime_type ): bool { return str_starts_with( strtolower( $mime_type ), 'audio/' ); } /** * Validate base64 content. * * Checks if a string is valid base64-encoded content. * * @param string $content The content to validate as base64. * * @return bool True if valid base64, false otherwise. */ public static function validate_base64( string $content ): bool { // Base64 content should not be empty. if ( empty( $content ) ) { return false; } // Reject whitespace-only strings (they decode to empty string but aren't valid base64 content). if ( trim( $content ) === '' ) { return false; } // Check if it's valid base64 encoding. return base64_decode( $content, true ) !== false; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode } /** * Validate a resource URI format. * * Per MCP spec: "The URI can use any protocol; it is up to the server how to interpret it." * This validates basic URI structure per RFC 3986. * * @param string $uri The URI to validate. * * @return bool True if valid, false otherwise. */ public static function validate_resource_uri( string $uri ): bool { // URI should not be empty. if ( empty( $uri ) ) { return false; } // Check reasonable length constraints. if ( strlen( $uri ) > 2048 ) { return false; } // Basic URI validation: must have scheme followed by colon (RFC 3986). // This accepts any protocol as per MCP specification. return (bool) preg_match( '/^[a-zA-Z][a-zA-Z0-9+.-]*:.+/', $uri ); } /** * Validate a role value according to MCP specification. * * Valid roles are "user" or "assistant". * * @param string $role The role to validate. * * @return bool True if valid, false otherwise. */ public static function validate_role( string $role ): bool { return in_array( $role, array( 'user', 'assistant' ), true ); } /** * Validate an array of roles according to MCP specification. * * All roles must be strings and must be either "user" or "assistant". * * @param array $roles The roles array to validate. * * @return bool True if all roles are valid, false otherwise. */ public static function validate_roles_array( array $roles ): bool { if ( empty( $roles ) ) { return false; } foreach ( $roles as $role ) { if ( ! is_string( $role ) || ! self::validate_role( $role ) ) { return false; } } return true; } /** * Validate a priority value according to MCP specification. * * Priority must be a number between 0.0 and 1.0 (inclusive). * * @param mixed $priority The priority value to validate. * * @return bool True if valid, false otherwise. */ public static function validate_priority( $priority ): bool { if ( ! is_numeric( $priority ) ) { return false; } $priority_float = (float) $priority; return $priority_float >= 0.0 && $priority_float <= 1.0; } /** * Get validation errors for tool-specific MCP annotations. * * Validates tool annotation fields per MCP 2025-06-18 specification: * - readOnlyHint, destructiveHint, idempotentHint, openWorldHint must be booleans * - title must be a non-empty string * * Only validates known tool annotation fields. Unknown fields are ignored. * * @param array $annotations The annotations to validate. * * @return array Array of validation errors, empty if valid. */ public static function get_tool_annotation_validation_errors( array $annotations ): array { $errors = array(); foreach ( $annotations as $field => $value ) { switch ( $field ) { case 'readOnlyHint': case 'destructiveHint': case 'idempotentHint': case 'openWorldHint': if ( ! is_bool( $value ) ) { $errors[] = sprintf( /* translators: %s: annotation field name */ __( 'Tool annotation field %s must be a boolean', 'mcp-adapter' ), $field ); } break; case 'title': if ( ! is_string( $value ) ) { $errors[] = sprintf( /* translators: %s: annotation field name */ __( 'Tool annotation field %s must be a string', 'mcp-adapter' ), $field ); break; } if ( empty( trim( $value ) ) ) { $errors[] = sprintf( /* translators: %s: annotation field name */ __( 'Tool annotation field %s must be a non-empty string', 'mcp-adapter' ), $field ); } break; default: // Unknown fields are ignored to allow forward compatibility. break; } } return $errors; } /** * Get validation errors for shared MCP annotations. * * Validates shared annotation fields per MCP 2025-06-18 specification: * - audience must be a non-empty array of valid Role values ("user", "assistant") * - lastModified must be a valid ISO 8601 formatted string * - priority must be a number between 0.0 and 1.0 * * Only validates known shared annotation fields. Unknown fields are ignored. * Used by resources, prompts, and tools. * * @param array $annotations The annotations to validate. * * @return array Array of validation errors, empty if valid. */ public static function get_annotation_validation_errors( array $annotations ): array { $errors = array(); foreach ( $annotations as $field => $value ) { switch ( $field ) { case 'audience': if ( ! is_array( $value ) ) { $errors[] = __( 'Annotation field audience must be an array', 'mcp-adapter' ); break; } if ( empty( $value ) ) { $errors[] = __( 'Annotation field audience must be a non-empty array', 'mcp-adapter' ); break; } if ( ! self::validate_roles_array( $value ) ) { $errors[] = __( 'Annotation field audience must contain only valid roles ("user" or "assistant")', 'mcp-adapter' ); } break; case 'lastModified': if ( ! is_string( $value ) || empty( trim( $value ) ) ) { $errors[] = __( 'Annotation field lastModified must be a non-empty string', 'mcp-adapter' ); break; } if ( ! self::validate_iso8601_timestamp( trim( $value ) ) ) { $errors[] = __( 'Annotation field lastModified must be a valid ISO 8601 timestamp', 'mcp-adapter' ); } break; case 'priority': if ( ! is_numeric( $value ) ) { $errors[] = __( 'Annotation field priority must be a number', 'mcp-adapter' ); break; } if ( ! self::validate_priority( $value ) ) { $errors[] = __( 'Annotation field priority must be between 0.0 and 1.0', 'mcp-adapter' ); } break; default: // Unknown fields are ignored to allow forward compatibility. break; } } return $errors; } }