*/ private const REQUIRED_SYMBOLS = [ McpAdapter::class, \WP\MCP\Core\McpServer::class, \WP\MCP\Transport\HttpTransport::class, \WP\MCP\Domain\Prompts\McpPrompt::class, \WP\MCP\Domain\Resources\McpResource::class, \WP\MCP\Abilities\McpAbilityExposure::class, \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, \WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface::class, // A trait, and the reason the check below uses three functions rather // than one: `class_exists()` is false for a trait, so a class-only // check called the library usable while // {@see \Albert\Logging\ObservabilityHandler} — which does // `use McpObservabilityHelperTrait;` — would fatal on load. \WP\MCP\Infrastructure\Observability\McpObservabilityHelperTrait::class, ]; /** * Whether the MCP library is present and offers what Albert calls. * * @return bool * @since 1.4.0 */ public static function adapter_available(): bool { return self::missing_classes() === []; } /** * Which of the classes Albert needs are not loadable. * * An empty array means the library is usable. A non-empty one means either * the library is absent altogether, or the copy that won resolution is older * than Albert requires — the two are reported differently, because the * remedies differ. * * @return array Fully-qualified class names. * @since 1.4.0 */ public static function missing_classes(): array { return array_values( array_filter( self::REQUIRED_SYMBOLS, static fn( string $name ): bool => ! class_exists( $name ) && ! interface_exists( $name ) && ! trait_exists( $name ) ) ); } /** * Whether any part of the library is loadable at all. * * Distinguishes "not installed" from "installed but too old": if even the * adapter entry point is absent, no copy was resolved, which on a source * install means the dependencies were never installed. * * @return bool * @since 1.4.0 */ public static function adapter_present(): bool { return class_exists( McpAdapter::class ); } /** * The file the resolved adapter was loaded from, for support diagnostics. * * Under Jetpack Autoloader this may well be another plugin's copy, which is * expected and is exactly why it is worth reporting. * * @return string|null Path, or null when nothing is loaded. * @since 1.4.0 */ public static function adapter_path(): ?string { if ( ! class_exists( McpAdapter::class ) ) { return null; } try { $file = ( new \ReflectionClass( McpAdapter::class ) )->getFileName(); } catch ( \ReflectionException $e ) { return null; } if ( ! is_string( $file ) ) { return null; } $plugins = wp_normalize_path( trailingslashit( WP_PLUGIN_DIR ) ); return str_replace( $plugins, '', wp_normalize_path( $file ) ); } }