PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.3
WooCommerce v10.9.3
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / MCP / MCPAdapterProvider.php
woocommerce / src / Internal / MCP Last commit date
Transport 7 months ago MCPAdapterProvider.php 4 weeks ago
MCPAdapterProvider.php
265 lines
1 <?php
2 /**
3 * MCP Adapter Provider class file.
4 */
5
6 declare( strict_types=1 );
7
8 namespace Automattic\WooCommerce\Internal\MCP;
9
10 use Automattic\WooCommerce\Utilities\FeaturesUtil;
11 use Automattic\WooCommerce\Internal\Abilities\AbilitiesRegistry;
12 use Automattic\WooCommerce\Internal\Abilities\REST\RestAbilityFactory;
13 use Automattic\WooCommerce\Internal\MCP\Transport\WooCommerceRestTransport;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * MCP Adapter Provider class for WooCommerce.
19 *
20 * Manages MCP (Model Context Protocol) adapter initialization and server configuration.
21 * Abilities should be registered separately using the WordPress Abilities API.
22 */
23 class MCPAdapterProvider {
24
25 /**
26 * MCP server namespace.
27 *
28 * @var string
29 */
30 const MCP_NAMESPACE = 'woocommerce';
31
32 /**
33 * MCP server route.
34 *
35 * @var string
36 */
37 const MCP_ROUTE = 'mcp';
38
39 /**
40 * Whether MCP adapter is initialized.
41 *
42 * @var bool
43 */
44 private bool $initialized = false;
45
46 /**
47 * Constructor.
48 */
49 public function __construct() {
50 /*
51 * Hook into rest_api_init with priority 10 to initialize only on REST API requests.
52 * MCP adapter registers on rest_api_init with priority 20000, so we initialize earlier.
53 * This prevents unnecessary MCP initialization on favicon, cron, or admin requests.
54 */
55 add_action( 'rest_api_init', array( $this, 'maybe_initialize' ), 10 );
56 }
57
58 /**
59 * Check feature flag and initialize MCP adapter if enabled.
60 */
61 public function maybe_initialize(): void {
62 // Check if MCP integration feature is enabled.
63 if ( ! FeaturesUtil::feature_is_enabled( 'mcp_integration' ) ) {
64 return;
65 }
66
67 // Prevent double initialization.
68 if ( $this->initialized ) {
69 return;
70 }
71
72 $this->initialize_mcp_adapter();
73 $this->register_hooks();
74 $this->initialized = true;
75 }
76
77 /**
78 * Initialize the MCP adapter.
79 */
80 private function initialize_mcp_adapter(): void {
81 // Check if MCP adapter class exists (should be autoloaded by WooCommerce's composer).
82 if ( ! class_exists( 'WP\MCP\Core\McpAdapter' ) ) {
83 if ( function_exists( 'wc_get_logger' ) ) {
84 wc_get_logger()->warning(
85 'MCP adapter class not found. Skipping MCP initialization.',
86 array( 'source' => 'woocommerce-mcp' )
87 );
88 }
89 return;
90 }
91
92 // Initialize the MCP adapter instance - this triggers the rest_api_init hook registration.
93 \WP\MCP\Core\McpAdapter::instance();
94 }
95
96 /**
97 * Register WordPress hooks for MCP adapter.
98 */
99 private function register_hooks(): void {
100 // Initialize MCP server when MCP adapter is ready.
101 add_action( 'mcp_adapter_init', array( $this, 'initialize_mcp_server' ) );
102 }
103
104 /**
105 * Initialize MCP server.
106 *
107 * @param object $adapter MCP adapter instance.
108 */
109 public function initialize_mcp_server( $adapter ): void {
110 // Get filtered abilities for MCP server.
111 $abilities_ids = $this->get_woocommerce_mcp_abilities();
112
113 // Bail if no abilities are available.
114 if ( empty( $abilities_ids ) ) {
115 return;
116 }
117
118 /*
119 * Temporarily disable MCP validation during server creation.
120 * Workaround for validator bug with union types (e.g., ["integer", "null"]).
121 * This will be removed once the mcp-adapter validator bug is fixed.
122 *
123 * @see https://github.com/WordPress/mcp-adapter/issues/47
124 */
125 add_filter( 'mcp_validation_enabled', array( __CLASS__, 'disable_mcp_validation' ), 999 );
126
127 try {
128 // Create MCP server.
129 $adapter->create_server(
130 'woocommerce-mcp',
131 self::MCP_NAMESPACE,
132 self::MCP_ROUTE,
133 __( 'WooCommerce MCP Server', 'woocommerce' ),
134 __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ),
135 '1.0.0',
136 array( WooCommerceRestTransport::class ),
137 \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class,
138 \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class,
139 $abilities_ids,
140 );
141 } catch ( \Throwable $e ) {
142 if ( function_exists( 'wc_get_logger' ) ) {
143 wc_get_logger()->error(
144 'MCP server initialization failed: ' . $e->getMessage(),
145 array( 'source' => 'woocommerce-mcp' )
146 );
147 }
148 } finally {
149 // Re-enable MCP validation immediately after server creation.
150 remove_filter( 'mcp_validation_enabled', array( __CLASS__, 'disable_mcp_validation' ), 999 );
151 }
152 }
153
154 /**
155 * Get WooCommerce abilities for MCP server.
156 *
157 * Filters abilities to include only those explicitly exposed to the deprecated
158 * WooCommerce MCP endpoint, with a filter to override inclusion decisions.
159 *
160 * @return array Array of ability IDs for MCP server.
161 */
162 private function get_woocommerce_mcp_abilities(): array {
163 // Get all abilities from the registry.
164 $abilities_registry = wc_get_container()->get( AbilitiesRegistry::class );
165 $all_abilities_ids = $abilities_registry->get_abilities_ids();
166
167 // Filter abilities based on deprecated endpoint exposure metadata and custom filter.
168 $mcp_abilities = array_filter(
169 $all_abilities_ids,
170 static function ( $ability_id ) {
171 $include = self::should_include_ability_by_default( $ability_id );
172
173 // Allow filter to override inclusion decision.
174 /**
175 * Filter to override MCP ability inclusion decision.
176 *
177 * @since 10.3.0
178 *
179 * @param bool $include Whether to include the ability by default. True when the ability has
180 * `expose_in_deprecated_woocommerce_mcp => true` in its metadata
181 * (set automatically on REST-derived abilities by RestAbilityFactory).
182 * Migration note: this value no longer represents whether the ability uses
183 * the `woocommerce/` namespace.
184 * Return unchanged to keep the default, or return true/false to override.
185 * @param string $ability_id The ability ID.
186 */
187 return apply_filters( 'woocommerce_mcp_include_ability', $include, $ability_id );
188 }
189 );
190
191 // Re-index array.
192 return array_values( $mcp_abilities );
193 }
194
195 /**
196 * Check if an ability should be included in the deprecated WooCommerce MCP endpoint.
197 *
198 * REST-derived abilities can opt in to the deprecated WooCommerce MCP endpoint.
199 * Require explicit metadata so semantic/domain abilities can use WooCommerce
200 * namespaces without expanding the deprecated MCP tool list.
201 * This intentionally allows abilities from any namespace to opt in to the
202 * deprecated endpoint while keeping namespace and transport exposure separate.
203 *
204 * @param string $ability_id Ability ID.
205 * @return bool Whether to include the ability by default.
206 */
207 private static function should_include_ability_by_default( string $ability_id ): bool {
208 // Keep the pre-check to avoid triggering _doing_it_wrong() for stale or mocked registry IDs.
209 if ( function_exists( 'wp_get_ability' ) && function_exists( 'wp_has_ability' ) && wp_has_ability( $ability_id ) ) {
210 $ability = wp_get_ability( $ability_id );
211 if ( $ability ) {
212 // Strict boolean required: truthy values like 1 or "true" are intentionally excluded.
213 return true === $ability->get_meta_item(
214 RestAbilityFactory::EXPOSE_IN_DEPRECATED_MCP_META_KEY,
215 false
216 );
217 }
218 }
219
220 return false;
221 }
222
223 /**
224 * Temporarily disable MCP validation.
225 *
226 * Used as a callback for the mcp_validation_enabled filter to work around
227 * validator bugs with union types.
228 *
229 * @return bool Always returns false to disable validation.
230 */
231 public static function disable_mcp_validation(): bool {
232 return false;
233 }
234
235 /**
236 * Check if MCP adapter is initialized.
237 *
238 * @return bool Whether MCP adapter is initialized.
239 */
240 public function is_initialized(): bool {
241 return $this->initialized;
242 }
243
244 /**
245 * Check if the current request is for the MCP endpoint.
246 *
247 * @return bool True if this is an MCP endpoint request.
248 */
249 public static function is_mcp_request(): bool {
250 // Check if this is a REST request.
251 if ( ! wp_is_serving_rest_request() ) {
252 return false;
253 }
254
255 // Get the request URI.
256 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
257
258 // Build the MCP endpoint path dynamically from constants.
259 $mcp_endpoint = '/' . self::MCP_NAMESPACE . '/' . self::MCP_ROUTE;
260
261 // Check if the request is for the MCP endpoint.
262 return false !== strpos( $request_uri, $mcp_endpoint );
263 }
264 }
265