-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Add MCP (Model Context Protocol) integration for WooCommerce #60901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Testing GuidelinesHi @prettyboymp @peterfabian @woocommerce/developer-advocacy, Apart from reviewing the code changes, please make sure to review the testing instructions (Guide) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed. Reminder: PR reviewers are required to document testing performed. This includes:
|
Test using WordPress PlaygroundThe changes in this pull request can be previewed and tested using a WordPress Playground instance. Test this pull request with WordPress Playground. Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a new commit. |
📝 WalkthroughWalkthroughAdds Model Context Protocol (MCP) integration: composer dependency for Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant WP as WordPress
participant WC as WooCommerce (class-woocommerce)
participant DI as DI Container
participant AbReg as AbilitiesRegistry
participant MCPProv as MCPAdapterProvider
WP->>WC: init_hooks()
WC->>DI: get(AbilitiesRegistry)
WC->>DI: get(MCPAdapterProvider)
Note right of MCPProv #DDEBF7: registers rest_api_init hooks for adapter/server init
WP->>MCPProv: rest_api_init -> maybe_initialize()
MCPProv->>AbReg: get_abilities_ids()
MCPProv->>WP: load MCP adapter & register MCP server (if feature enabled)
sequenceDiagram
autonumber
participant Client as MCP Client
participant MCP as WooCommerce MCP Server
participant Transport as WooCommerceRestTransport
participant WP as WordPress REST
participant Ctrl as WC REST Controller
Client->>MCP: Request (includes X-MCP-API-Key)
MCP->>Transport: validate_request()
Transport->>WP: DB lookup/authenticate API key
WP-->>Transport: wp_set_current_user + permissions
Transport->>WP: rest_do_request(...)
WP->>Ctrl: dispatch controller
Ctrl-->>WP: response
WP-->>MCP: response forwarded
MCP-->>Client: result
sequenceDiagram
autonumber
participant WP as WordPress
participant Bridge as AbilitiesRestBridge
participant Factory as RestAbilityFactory
participant Ctrl as WC REST Controller
WP->>Bridge: abilities_api_init
alt MCP context (MCPAdapterProvider::is_mcp_request)
Bridge->>Factory: register_controller_abilities(config)
Factory->>Ctrl: instantiate & introspect schemas/params
Factory->>WP: wp_register_ability(... ability_class=RestAbility)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (5)**/*.{php,js,jsx,ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
Files:
plugins/woocommerce/tests/**/*.php📄 CodeRabbit inference engine (.cursor/rules/woo-phpunit.mdc)
Files:
plugins/woocommerce/tests/php/src/**/*.php📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Files:
plugins/woocommerce/tests/php/**📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Files:
**/*.{php,js,ts,jsx,tsx}⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (5)📓 Common learnings📚 Learning: 2025-08-18T06:11:48.768ZApplied to files:
📚 Learning: 2025-09-11T07:18:56.840ZApplied to files:
📚 Learning: 2025-09-03T11:50:02.208ZApplied to files:
📚 Learning: 2025-09-03T16:06:52.193ZApplied to files:
🧬 Code graph analysis (1)plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php (5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (12)
plugins/woocommerce/composer.json (2)
35-39: Pin MCP Adapter source for reproducible builds.Using a VCS repo + dev branch is fine for a draft, but please pin to a tag or commit before merge to ensure repeatable builds and avoid surprise breakages when the trunk moves.
56-58: Avoid requiring dev-trunk in core.
wordpress/mcp-adapter: dev-trunkwill float. Prefer a tagged release or a specific commit reference (and update when cutting a WC release). Also confirmwordpress/abilities-apirange is correct against production sites.plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php (1)
29-37: Avoid implicit key generation on read.
get_api_key()creates a key when missing; this is invoked from the feature description, which can generate a secret merely by viewing the page. Split intoensure_api_key()(explicit generation) andget_api_key()(read-only), or gate generation behind an explicit admin action.plugins/woocommerce/includes/class-woocommerce.php (1)
358-360: Gate MCP services behind the feature flag to avoid unnecessary boot.Avoid instantiating MCP services unless the feature is enabled.
Apply this diff:
- $container->get( AbilitiesRegistry::class ); - $container->get( MCPAdapterProvider::class ); + if ( wc_get_container()->get( FeaturesController::class )->feature_is_enabled( 'mcp_integration' ) ) { + $container->get( AbilitiesRegistry::class ); + $container->get( MCPAdapterProvider::class ); + }plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
10-14: Remove unused import.
use WP_Error;is unused when instantiating errors with\WP_Error. Either drop the import or use the imported symbol consistently.plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (2)
30-37: Consider gating ability initialization behind the feature flag.If MCP is disabled, you may not want to register MCP-facing abilities at all. Optional, depending on intended reuse outside MCP.
44-63: Naming consistency.Method mixes camelCase with snake_case methods in the same class. Prefer
get_ability_ids()and keepgetAbilitiesIDs()as a deprecated alias for BC.- public function getAbilitiesIDs(): array { + /** @deprecated Use get_ability_ids(). */ + public function getAbilitiesIDs(): array { + return $this->get_ability_ids(); + } + + public function get_ability_ids(): array { ... }plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
35-37: Hook timing comment doesn’t match the hook; preferplugins_loaded.Use
plugins_loadedto ensure dependencies are loaded before checking the adapter class.- // Hook into WordPress plugins loaded to ensure proper timing - add_action( 'init', array( $this, 'maybe_initialize' ) ); + // Initialize after all plugins are loaded to ensure proper timing. + add_action( 'plugins_loaded', array( $this, 'maybe_initialize' ) );
89-101: Make server configuration overridable.Expose a filter so site owners can adjust namespace/route if needed.
- // Create MCP server - try { - $adapter->create_server( - 'woocommerce-mcp', - 'woocommerce', - 'mcp', - __( 'WooCommerce MCP Server', 'woocommerce' ), - __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ), - '1.0.0', - array( WooCommerceRestTransport::class ), - \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, - \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, - $abilities_ids, - ); + $args = apply_filters( + 'woocommerce_mcp_server_args', + array( + 'id' => 'woocommerce-mcp', + 'namespace' => 'woocommerce', + 'route' => 'mcp', + 'name' => __( 'WooCommerce MCP Server', 'woocommerce' ), + 'description' => __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ), + 'version' => '1.0.0', + 'transports' => array( WooCommerceRestTransport::class ), + 'error_handler' => \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, + 'observability' => \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, + 'abilities' => $abilities_ids, + ) + ); + try { + $adapter->create_server( + $args['id'], + $args['namespace'], + $args['route'], + $args['name'], + $args['description'], + $args['version'], + $args['transports'], + $args['error_handler'], + $args['observability'], + $args['abilities'] + );
61-69: Re-initialization safety when adapter loads late.If the adapter class isn’t available at this point, there’s no retry. Optionally add a late hook (e.g.,
rest_api_init) to re-attempt initialization.
76-77: Param type documentation.Add a docblock
@param \WP\MCP\Core\McpAdapter $adapterfor clarity since the method is public and untyped.
1-112: Security follow-up: avoid admin-context execution in transport.Given WooCommerceRestTransport currently elevates to the first admin user after API-key validation (see Transport/WooCommerceRestTransport.php lines 22–89), prioritize implementing per-key user association and least-privilege execution before enabling this by default.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
plugins/woocommerce/composer.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
plugins/woocommerce/composer.json(2 hunks)plugins/woocommerce/includes/class-woocommerce.php(2 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php(1 hunks)plugins/woocommerce/src/Internal/Features/FeaturesController.php(1 hunks)plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/includes/class-woocommerce.phpplugins/woocommerce/src/Internal/Features/FeaturesController.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.phpplugins/woocommerce/src/Internal/MCP/ApiKeyManager.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/includes/class-woocommerce.phpplugins/woocommerce/src/Internal/Features/FeaturesController.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.phpplugins/woocommerce/src/Internal/MCP/ApiKeyManager.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧬 Code graph analysis (7)
plugins/woocommerce/includes/class-woocommerce.php (2)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
MCPAdapterProvider(22-112)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (1)
AbilitiesRegistry(18-64)
plugins/woocommerce/src/Internal/Features/FeaturesController.php (1)
plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php (2)
ApiKeyManager(17-89)get_api_key(29-37)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php (2)
ApiKeyManager(17-89)validate_api_key(66-69)plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (1)
check_permission(113-116)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (4)
plugins/woocommerce/includes/class-woocommerce.php (1)
WooCommerce(45-1643)plugins/woocommerce/woocommerce.php (1)
WC(46-48)plugins/woocommerce/includes/wc-core-functions.php (1)
get_woocommerce_currency(485-487)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
check_permission(31-79)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (2)
plugins/woocommerce/includes/class-woocommerce.php (2)
WooCommerce(45-1643)init(868-900)plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (2)
StoreInfoAbility(18-117)init(23-26)
plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
validate_api_key(87-89)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1643)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-64)__construct(23-25)getAbilitiesIDs(44-63)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
WooCommerceRestTransport(23-90)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
- GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 9/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (1)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (1)
113-116: Permission is fine; consider narrowing if more abilities ship.Keeping it at
view_woocommerce_reportsis reasonable for read-only store info. Revisit if abilities start exposing data beyond reports.
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (1)
38-62: Do not auto-expose everywoocommerce/*ability to MCP. Use an allowlist + filter.This recreates the previously flagged security issue: any extension can register a
woocommerce/*ability and it becomes MCP‑exposed (executed under admin context via the transport). Ship with a minimal core allowlist and a filter for opt‑in.Apply:
/** * Get WooCommerce ability IDs from the WordPress Abilities API. * * @return array Array of WooCommerce ability IDs. */ public function getAbilitiesIDs(): array { - // Check if the abilities API is available - if ( ! function_exists( 'wp_get_abilities' ) ) { - return array(); - } - - // Get all registered abilities - $all_abilities = wp_get_abilities(); - - // Filter for WooCommerce-specific abilities - $woocommerce_abilities = array(); - foreach ( array_keys( $all_abilities ) as $ability_id ) { - // Check if ability ID starts with 'woocommerce/' - if ( strpos( $ability_id, 'woocommerce/' ) === 0 ) { - $woocommerce_abilities[] = $ability_id; - } - } - - return $woocommerce_abilities; + $all_ids = array(); + if ( function_exists( 'wp_get_abilities' ) ) { + $all = wp_get_abilities(); + $all_ids = is_array( $all ) ? array_keys( $all ) : array(); + } + // Core allowlist (safe by default). + $default = array( + 'woocommerce/store-info', + ); + $allowed = array_values( array_intersect( $default, $all_ids ) ); + /** + * Filter the list of Woo abilities exposed to MCP. + * + * Extensions must explicitly opt in; no wildcard prefixing. + * + * @param string[] $allowed Allowlisted and registered ability IDs. + * @param string[] $all_ids All registered ability IDs. + */ + return apply_filters( 'woocommerce_mcp_ability_ids', $allowed, $all_ids ); }
🧹 Nitpick comments (5)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
33-36: Silence the unused parameter warning.Use the variable to appease linters.
Apply:
- protected function validate_output( $output ) { + protected function validate_output( $output ) { + // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $output = $output;(If you adopt the previous diff, this nit is covered.)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php (1)
99-111: Hook exists but consider early-return if Abilities API is missing.Minor: add a function_exists guard to avoid scheduling a no-op action in older WP environments.
Apply:
public static function init(): void { - // Register abilities when Abilities API is ready - add_action( 'abilities_api_init', array( __CLASS__, 'register_abilities' ) ); + if ( function_exists( 'wp_register_ability' ) ) { + add_action( 'abilities_api_init', array( __CLASS__, 'register_abilities' ) ); + } }plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (1)
30-36: Init only when Abilities API exists.Small guard to avoid touching classes/hooks on older WP.
Apply:
private function init_abilities(): void { - // Initialize store info ability - StoreInfoAbility::init(); - - // Initialize REST bridge for REST endpoint abilities - AbilitiesRestBridge::init(); + if ( function_exists( 'wp_register_ability' ) ) { + StoreInfoAbility::init(); + AbilitiesRestBridge::init(); + } }plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
146-177: Input schema isn’t proper JSON Schema; avoid passing WP REST arg arrays verbatim.
get_collection_params()/get_endpoint_args_for_item_schema()include callbacks and WP-specific keys. Either convert to JSON Schema or relax input validation for these abilities.Option A (convert minimal fields): keep types/required/default and drop callbacks.
Option B (pragmatic): mark input schema as generic and rely on REST validation:- return array( - 'type' => 'object', - 'properties' => $controller->get_collection_params(), - ); + return array( 'type' => 'object' );Same for create/update.
25-37: Guard against missing Abilities API.Early-return if
wp_register_abilityis absent; you’ve done it inside, but avoid instantiating controllers unnecessarily.Apply:
public static function register_controller_abilities( array $config ): void { + if ( ! function_exists( 'wp_register_ability' ) ) { + return; + } $controller_class = $config['controller'];
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- plugins/woocommerce/src/Internal/MCP/ApiKeyManager.php
- plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
🧬 Code graph analysis (3)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
RestAbilityFactory(18-259)register_controller_abilities(25-37)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
plugins/woocommerce/includes/class-woocommerce.php (1)
init(868-900)plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (2)
StoreInfoAbility(18-117)init(23-26)plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php (2)
init(99-102)AbilitiesRestBridge(20-112)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
254-254: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
254-254: Avoid unused parameters such as '$operation'. (undefined)
(UnusedFormalParameter)
254-254: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php
33-33: Avoid unused parameters such as '$output'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (3)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (1)
101-108: Avoid loading all orders into memory; use wc_orders_count() (cached, HPOS-safe).wc_get_orders with limit -1 is unbounded and can OOM on large stores.
- // Orders using wc_get_orders for accurate counting - $completed_orders = wc_get_orders( array( - 'status' => 'completed', - 'limit' => -1, - 'return' => 'ids', - ) ); - $completed_count = count( $completed_orders ); + // Orders: use cached counter + $completed_count = (int) wc_orders_count( 'completed' );plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
113-116: Good: user context now matches the API key owner (no blanket admin).This addresses the prior “impersonate first admin” risk noted in earlier reviews. Nice improvement.
24-32: Require a WP_REST_Request; avoid nullable parameter here.
validate_request()unconditionally expects aWP_REST_Request, butcheck_permission()acceptsnull. Passingnullwould fatal when calling$request->get_header(...). Align the signature and drop the defaultnull. If the parent method is already typed, mirror it.Apply this diff (verify compatibility with the parent
RestTransportbefore merging):- public function check_permission( $request = null ) { + public function check_permission( \WP_REST_Request $request ) { return $this->validate_request( $request ); }If the parent is untyped, keep it untyped but still remove the nullable default:
- public function check_permission( $request = null ) { + public function check_permission( $request ) { return $this->validate_request( $request ); }
🧹 Nitpick comments (10)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (6)
41-51: Localize property description in input schema.Wrap the description with translation helpers.
- 'include_stats' => array( - 'type' => 'boolean', - 'description' => 'Whether to include basic store statistics (product count, order count, etc.)', - 'default' => false, - ), + 'include_stats' => array( + 'type' => 'boolean', + 'description' => __( 'Whether to include basic store statistics (product count, order count, etc.)', 'woocommerce' ), + 'default' => false, + ),
86-94: Use home_url() for the public-facing store URL.site_url() can point to wp-admin or a subdirectory; home_url() reflects the storefront address.
- 'store_url' => get_site_url(), + 'store_url' => home_url(),
109-113: Clarify whether order_count is “total orders” or “completed orders only”.If “total,” sum all wc-* statuses via wc_orders_count; if “completed,” rename the field or document it in schema.
Option (total orders + breakdown):
- $order_count = $completed_count; + $statuses = function_exists( 'wc_get_order_statuses' ) ? array_keys( wc_get_order_statuses() ) : array(); + $total_orders = 0; + $order_breakdown = array( 'completed' => $completed_count ); + foreach ( $statuses as $st ) { + // wc_orders_count accepts slugs without 'wc-' prefix. + $slug = preg_replace( '/^wc-/', '', $st ); + $count = (int) wc_orders_count( $slug ); + $total_orders += $count; + if ( ! isset( $order_breakdown[ $slug ] ) ) { + $order_breakdown[ $slug ] = $count; + } + } + $order_count = $total_orders;And in schema (if following this path), list known keys or keep it generic as an object. See earlier schema diff.
Also applies to: 62-70
96-124: Type-safety and defaults for input.Normalize include_stats to bool to avoid truthy surprises from non-bool inputs.
- if ( ! empty( $input['include_stats'] ) ) { + $include_stats = ! empty( $input['include_stats'] ) && (bool) $input['include_stats']; + if ( $include_stats ) {
79-87: Ask for tests covering permission and stats toggles.Please add unit/E2E tests for:
- permission gate behavior,
- include_stats true/false,
- order counting on HPOS and posts table stores,
- inclusion gating for admin_email (if adopted).
I can draft PHPUnit tests for the ability execution and schema conformance if helpful.
Also applies to: 126-137
101-113: Optional: small performance tidy-up.product_count uses wp_count_posts()->publish which is fine; consider caching the result for the request (transient not needed) if used multiple times. Not blocking.
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (4)
44-58: Localize and harden error messages; don’t leak header format details.User‑visible strings should be translatable. Also avoid revealing exact header format in errors.
Apply this diff:
- return new \WP_Error( - 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'missing_api_key', + __( 'API key required in the X-MCP-API-Key header.', 'woocommerce' ), + array( 'status' => 401 ) + ); } if ( strpos( $api_key, ':' ) === false ) { - return new \WP_Error( - 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'invalid_api_key', + __( 'Malformed API key.', 'woocommerce' ), + array( 'status' => 401 ) + ); }
45-49: Avoid credential enumeration in responses.Returning distinct messages/codes for “invalid consumer key” vs “invalid consumer secret” enables enumeration. Prefer a single
invalid_credentialscode/message for both failure paths.Apply this simplified diff if you agree:
- 'invalid_consumer_key', - __( 'Invalid credentials.', 'woocommerce' ), + 'invalid_credentials', + __( 'Invalid credentials.', 'woocommerce' ), @@ - 'invalid_consumer_secret', - __( 'Invalid credentials.', 'woocommerce' ), + 'invalid_credentials', + __( 'Invalid credentials.', 'woocommerce' ),Also applies to: 54-57, 98-101, 107-110
10-13: Unused imports.
use WP_REST_Request;anduse WP_Error;aren’t used (you reference FQCNs). Either use the imports or drop them.
40-70: Tests needed for auth paths and headers.Please add unit/integration tests covering:
- Missing header → 401.
- Malformed header → 401.
- Valid key/secret → sets current user to the key owner.
- Invalid key and invalid secret → 401 with the same error when enumeration mitigation is applied.
- Permission mismatch → 403.
- HTTPS enforcement toggle via
woocommerce_mcp_allow_insecure_transport.I can scaffold WP_UnitTestCase tests for these cases if helpful.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php(1 hunks)plugins/woocommerce/src/Internal/Features/FeaturesController.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- plugins/woocommerce/src/Internal/Features/FeaturesController.php
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
🧬 Code graph analysis (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (2)
plugins/woocommerce/includes/wc-core-functions.php (1)
get_woocommerce_currency(485-487)plugins/woocommerce/includes/wc-order-functions.php (1)
wc_get_orders(36-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (4)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (4)
20-27: OK: guarded registration with Abilities API.Registration is deferred until abilities_api_init and guarded by function_exists.
If the Abilities API can be disabled at runtime, ensure StoreInfoAbility::init() is called only once (it is; add_action handles duplicates). No action needed.
Also applies to: 31-36
114-117: Customer count approach is fine.count_users()['avail_roles']['customer'] is the right bucket; safe fallback handled.
1-18: File scaffolding looks good.Strict types, namespace, ABSPATH guard, and docblocks are appropriate.
101-113: wc_orders_count is present in this repo — prefer it for counting orders.Defined at plugins/woocommerce/includes/wc-order-functions.php:391 and referenced across tests and multiple files.
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
Outdated
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
Show resolved
Hide resolved
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (2)
39-39: Avoid front‑end initialization; defer to REST or guard with REST_REQUEST.Running on init adds overhead on non‑REST requests. Prefer rest_api_init or guard in maybe_initialize.
- add_action( 'init', array( $this, 'maybe_initialize' ) ); + add_action( 'init', array( $this, 'maybe_initialize' ) );public function maybe_initialize(): void { // Check if MCP integration feature is enabled if ( ! FeaturesUtil::feature_is_enabled( 'mcp_integration' ) ) { return; } + // Only proceed for REST requests to avoid front-end overhead. + if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { + return; + }Alternatively, move the hook to rest_api_init:
- add_action( 'init', array( $this, 'maybe_initialize' ) ); + add_action( 'rest_api_init', array( $this, 'maybe_initialize' ) );Please confirm timing with the MCP adapter’s mcp_adapter_init firing order.
Also applies to: 45-59
87-115: Harden server initialization: type‑check adapter and localize metadata.Guard against unexpected $adapter and localize server name/description.
public function initialize_mcp_server( $adapter ): void { // Get abilities from the registry $abilities_registry = wc_get_container()->get( AbilitiesRegistry::class ); $abilities_ids = $abilities_registry->getAbilitiesIDs(); // Bail if no abilities are available if ( empty( $abilities_ids ) ) { return; } + // Ensure adapter has the expected API. + if ( ! is_object( $adapter ) || ! method_exists( $adapter, 'create_server' ) ) { + return; + } // Temporarily disable MCP validation during server creation // Workaround for validator bug with union types (e.g., ["integer", "null"]) // TODO: Remove once mcp-adapter validator bug is fixed add_filter( 'mcp_validation_enabled', '__return_false', 999 ); try { // Create MCP server $adapter->create_server( 'woocommerce-mcp', 'woocommerce', 'mcp', - 'WooCommerce MCP Server', - 'AI-accessible WooCommerce operations via MCP', + __( 'WooCommerce MCP Server', 'woocommerce' ), + __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ), '1.0.0', array( WooCommerceRestTransport::class ), \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, $abilities_ids, );plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (2)
44-53: Do not expose admin_email by default; gate behind explicit flag and stronger cap.PII leakage risk. Add an input flag and require manage_options (or manage_woocommerce) before returning admin_email. Also localize the new description.
'input_schema' => array( 'type' => 'object', 'properties' => array( 'include_stats' => array( 'type' => 'boolean', - 'description' => 'Whether to include basic store statistics (product count, order count, etc.)', + 'description' => __( 'Whether to include basic store statistics (product count, order count, etc.)', 'woocommerce' ), 'default' => false, ), + 'include_sensitive' => array( + 'type' => 'boolean', + 'description' => __( 'Include sensitive fields (e.g., admin_email). Requires manage_options.', 'woocommerce' ), + 'default' => false, + ), ), ),$result = array( 'store_name' => get_bloginfo( 'name' ), - 'store_url' => get_site_url(), - 'admin_email' => get_bloginfo( 'admin_email' ), + 'store_url' => get_site_url(), 'woocommerce_version' => WC()->version, 'wordpress_version' => get_bloginfo( 'version' ), 'currency' => get_woocommerce_currency(), 'country' => WC()->countries->get_base_country(), ); + + // Optionally include PII if explicitly requested and caller has elevated caps. + if ( ! empty( $input['include_sensitive'] ) && current_user_can( 'manage_options' ) ) { + $result['admin_email'] = (string) get_bloginfo( 'admin_email' ); + }Also applies to: 59-59, 88-96
64-71: Schema/result mismatch: stats.order_breakdown is returned but not declared.Declare order_breakdown in the output schema to avoid validation/runtime mismatches. Keys match the returned breakdown.
'stats' => array( 'type' => 'object', 'properties' => array( 'product_count' => array( 'type' => 'integer' ), 'order_count' => array( 'type' => 'integer' ), 'customer_count' => array( 'type' => 'integer' ), + 'order_breakdown' => array( + 'type' => 'object', + 'properties' => array( + 'completed' => array( 'type' => 'integer' ), + 'processing' => array( 'type' => 'integer' ), + 'pending' => array( 'type' => 'integer' ), + 'on-hold' => array( 'type' => 'integer' ), + 'cancelled' => array( 'type' => 'integer' ), + 'refunded' => array( 'type' => 'integer' ), + 'failed' => array( 'type' => 'integer' ), + ), + ), ), ),Also applies to: 112-121, 128-133
🧹 Nitpick comments (4)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (4)
49-51: Localize schema description strings.Wrap the input_schema description in translation functions for i18n.
- 'description' => 'Whether to include basic store statistics (product count, order count, etc.)', + 'description' => __( 'Whether to include basic store statistics (product count, order count, etc.)', 'woocommerce' ),
103-123: Future‑proof order_count by summing all current wc- statuses.*If new statuses are added (extensions), they won’t be reflected. Sum over wc_get_order_statuses for order_count while retaining the explicit breakdown you already return.
- $order_count = $completed_count + $processing_count + $pending_count + $on_hold_count + $cancelled_count + $refunded_count + $failed_count; + $order_count = 0; + if ( function_exists( 'wc_get_order_statuses' ) ) { + foreach ( array_keys( wc_get_order_statuses() ) as $status ) { + $order_count += (int) wc_orders_count( $status ); + } + } else { + $order_count = $completed_count + $processing_count + $pending_count + $on_hold_count + $cancelled_count + $refunded_count + $failed_count; + }
90-90: Consider home_url() for public-facing store URL.home_url() better represents the storefront URL when WordPress and site URLs differ.
- 'store_url' => get_site_url(), + 'store_url' => home_url(),
75-77: Add tests to cover schema shape, PII gating, and permission checks.Please add unit/E2E tests ensuring:
- order_breakdown schema matches returned keys
- admin_email only returns when include_sensitive=true and user has manage_options
- permission_callback enforces access
I can scaffold tests for Abilities API execution and permission gating if helpful.
Also applies to: 144-147
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php
🧬 Code graph analysis (2)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
plugins/woocommerce/includes/class-woocommerce.php (1)
__construct(251-256)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-50)__construct(23-25)getAbilitiesIDs(40-49)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)
plugins/woocommerce/src/Internal/Abilities/StoreInfoAbility.php (4)
plugins/woocommerce/includes/class-woocommerce.php (1)
WooCommerce(45-1643)plugins/woocommerce/woocommerce.php (1)
WC(46-48)plugins/woocommerce/includes/wc-core-functions.php (1)
get_woocommerce_currency(485-487)plugins/woocommerce/includes/wc-order-functions.php (1)
wc_orders_count(391-419)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
60-66: Enforce permissions in ability permission_callback (don’t return true).Gate at the ability level; map to REST permissions and pass route for context.
- 'execute_callback' => function( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); - }, - 'permission_callback' => function( $input ) use ( $controller, $ability_config ) { - return self::check_permission( $controller, $ability_config['operation'], $input ); - }, + 'execute_callback' => function( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route ); + }, + 'permission_callback' => function( $input ) use ( $controller, $ability_config, $route ) { + return self::check_permission( $controller, $ability_config['operation'], $input, $route ); + },
240-244: Implement real permission checks (map to controller permission methods; fallback to REST probe).Return boolean or WP_Error.
- private static function check_permission( $controller, string $operation, array $input ): bool { - // We return true here and let the controller handle permissions - // The REST controller will check permissions when its methods are called - return true; - } + private static function check_permission( $controller, string $operation, array $input, string $route ) { + $perm_map = array( + 'list' => 'get_items_permissions_check', + 'get' => 'get_item_permissions_check', + 'create' => 'create_item_permissions_check', + 'update' => 'update_item_permissions_check', + 'delete' => 'delete_item_permissions_check', + ); + $method_map = array( + 'list' => 'GET', + 'get' => 'GET', + 'create' => 'POST', + 'update' => 'PUT', + 'delete' => 'DELETE', + ); + $req_method = $method_map[ $operation ] ?? 'GET'; + $request_route = rtrim( $route, '/' ); + if ( isset( $input['id'] ) && in_array( $operation, array( 'get', 'update', 'delete' ), true ) ) { + $request_route .= '/' . absint( $input['id'] ); + } + // Prefer controller permission method if present. + $perm_fn = $perm_map[ $operation ] ?? null; + if ( $perm_fn && method_exists( $controller, $perm_fn ) ) { + $request = new \WP_REST_Request( $req_method, $request_route ); + if ( isset( $input['id'] ) ) { + $request->set_param( 'id', absint( $input['id'] ) ); + } + $allowed = $controller->{$perm_fn}( $request ); + if ( true === $allowed ) { + return true; + } + if ( is_wp_error( $allowed ) ) { + return $allowed; + } + return new \WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to perform this action.', 'woocommerce' ), array( 'status' => 403 ) ); + } + // Fallback probe through REST. + $probe = new \WP_REST_Request( $req_method, $request_route ); + $response = rest_do_request( $probe ); + if ( is_wp_error( $response ) ) { + return $response; + } + $status = $response instanceof \WP_REST_Response ? $response->get_status() : 200; + return $status < 400; + }
🧹 Nitpick comments (13)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (13)
190-200: Handle unknown operations explicitly.Avoid defaulting to GET; surface a clear error.
- $method = $method_map[ $operation ] ?? 'GET'; + $method = $method_map[ $operation ] ?? null; + if ( null === $method ) { + return new \WP_Error( + 'woocommerce_mcp_unknown_operation', + sprintf( 'Unknown operation "%s".', $operation ), + array( 'status' => 400 ) + ); + }
201-207: Normalize route and sanitize id.Prevent double slashes and use absint.
- $request_route = $route; + $request_route = '/' . ltrim( rtrim( $route, '/' ), '/' ); if ( isset( $input['id'] ) && in_array( $operation, array( 'get', 'update', 'delete' ), true ) ) { - $request_route .= '/' . intval( $input['id'] ); + $request_route .= '/' . absint( $input['id'] ); unset( $input['id'] ); }
208-213: Send body for mutating requests.Some controllers expect body params; keep params for GET.
- $request = new \WP_REST_Request( $method, $request_route ); - foreach ( $input as $key => $value ) { - $request->set_param( $key, $value ); - } + $request = new \WP_REST_Request( $method, $request_route ); + if ( in_array( $method, array( 'POST', 'PUT', 'PATCH' ), true ) ) { + $request->set_body_params( $input ); + } else { + foreach ( $input as $key => $value ) { + $request->set_param( $key, $value ); + } + }
221-226: Return pagination metadata for list operations.Expose X-WP-Total and X-WP-TotalPages.
- // For list operations, wrap in data object to match schema - if ( 'list' === $operation ) { - return array( 'data' => $data ); - } + // For list operations, wrap in data object and include pagination meta. + if ( 'list' === $operation ) { + $headers = $response instanceof \WP_REST_Response ? $response->get_headers() : array(); + $meta = array(); + if ( isset( $headers['X-WP-Total'] ) ) { + $meta['total'] = (int) $headers['X-WP-Total']; + } + if ( isset( $headers['X-WP-TotalPages'] ) ) { + $meta['total_pages'] = (int) $headers['X-WP-TotalPages']; + } + return array( 'data' => $data, 'meta' => $meta ); + }
147-162: Align list output schema with meta.Add meta.total and meta.total_pages.
- return array( - 'type' => 'object', - 'properties' => array( - 'data' => array( - 'type' => 'array', - 'items' => $schema, - ), - ), - ); + return array( + 'type' => 'object', + 'properties' => array( + 'data' => array( + 'type' => 'array', + 'items' => $schema, + ), + 'meta' => array( + 'type' => 'object', + 'properties' => array( + 'total' => array( 'type' => 'integer', 'minimum' => 0 ), + 'total_pages' => array( 'type' => 'integer', 'minimum' => 0 ), + ), + ), + ), + );
86-91: Normalize WP REST arg defs into valid JSON Schema (capture required).WP param defs embed “required” per-field; JSON Schema expects top-level required[]. Convert args before returning schema.
- return array( - 'type' => 'object', - 'properties' => $controller->get_collection_params(), - ); + return self::convert_wp_args_to_json_schema( $controller->get_collection_params() );- return array( - 'type' => 'object', - 'properties' => $args, - ); + return self::convert_wp_args_to_json_schema( $args );- $args = $controller->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ); - $args['id'] = array( + $args = $controller->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ); + $args['id'] = array( 'type' => 'integer', 'description' => __( 'Unique identifier for the resource', 'woocommerce' ), + 'minimum' => 1, ); - return array( - 'type' => 'object', - 'properties' => $args, - 'required' => array( 'id' ), - ); + $schema = self::convert_wp_args_to_json_schema( $args ); + $schema['required'] = array_values( array_unique( array_merge( $schema['required'] ?? array(), array( 'id' ) ) ) ); + return $schema;Add helper:
+ /** + * Convert WP REST arg definitions to JSON Schema (object) and extract required fields. + * + * @param array $args WP-style arg defs. + * @return array JSON Schema with properties + required[]. + */ + private static function convert_wp_args_to_json_schema( array $args ): array { + $required = array(); + $properties = array(); + foreach ( $args as $name => $def ) { + $prop = array(); + foreach ( array( 'type', 'items', 'enum', 'format', 'properties', 'description', 'minimum', 'maximum', 'minItems', 'maxItems', 'default' ) as $k ) { + if ( isset( $def[ $k ] ) ) { + $prop[ $k ] = $def[ $k ]; + } + } + if ( isset( $def['sanitize_callback'] ) && 'absint' === $def['sanitize_callback'] ) { + $prop['minimum'] = $prop['minimum'] ?? 0; + } + $properties[ $name ] = $prop ?: array( 'type' => 'string' ); + if ( ! empty( $def['required'] ) ) { + $required[] = $name; + } + } + $schema = array( + 'type' => 'object', + 'properties' => $properties, + ); + if ( $required ) { + $schema['required'] = array_values( array_unique( $required ) ); + } + return $schema; + }Also applies to: 99-102, 105-118
121-134: Tighten ID schema for get/delete.Add minimum 1.
return array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => __( 'Unique identifier for the resource', 'woocommerce' ), + 'minimum' => 1, ), ), 'required' => array( 'id' ), );
47-51: Don’t fail silently when Abilities API is missing.Log a warning (Woo logger) to aid debugging.
- if ( ! function_exists( 'wp_register_ability' ) ) { - return; - } + if ( ! function_exists( 'wp_register_ability' ) ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->warning( + sprintf( 'Abilities API not available; skipping ability "%s".', $ability_config['id'] ?? '(unknown)' ), + array( 'source' => 'rest-ability-factory' ) + ); + } else { + error_log( sprintf( '[woocommerce] Abilities API not available; skipping ability "%s".', $ability_config['id'] ?? '(unknown)' ) ); + } + return; + }
70-72: Use WooCommerce logger instead of error_log.Consistent logging with context.
- error_log( "Failed to register ability {$ability_config['id']}: " . $e->getMessage() ); + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + sprintf( 'Failed to register ability %s: %s', $ability_config['id'] ?? '(unknown)', $e->getMessage() ), + array( 'source' => 'rest-ability-factory' ) + ); + } else { + error_log( sprintf( '[woocommerce] Failed to register ability %s: %s', $ability_config['id'] ?? '(unknown)', $e->getMessage() ) ); + }
25-37: Validate config keys and guard against missing entries.Harden against absent abilities/route keys; log and continue.
public static function register_controller_abilities( array $config ): void { - $controller_class = $config['controller']; + if ( empty( $config['controller'] ) || empty( $config['abilities'] ) || empty( $config['route'] ) ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->warning( 'Invalid REST ability config: missing controller/abilities/route.', array( 'source' => 'rest-ability-factory' ) ); + } + return; + } + $controller_class = $config['controller'];
147-179: Optional: mark delete output required fields.Ensure deleted/previous presence.
} elseif ( 'delete' === $operation ) { // For delete operations, return simple confirmation return array( 'type' => 'object', 'properties' => array( 'deleted' => array( 'type' => 'boolean' ), 'previous' => $schema, ), + 'required' => array( 'deleted' ), );
190-229: Nit: drop unused $controller param from execute_operation (cleans PHPMD warning).Signature not using it after above changes.
- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( string $operation, array $input, string $route ) {
60-62: Nit: align execute_callback with updated signature.Remove unused $controller capture.
- 'execute_callback' => function( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); - }, + 'execute_callback' => function( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route ); + },
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-03T11:50:02.208Z
Learnt from: tpaksu
PR: woocommerce/woocommerce#60735
File: plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Fulfillments/class-wc-rest-fulfillments-v4-controller-tests.php:10-10
Timestamp: 2025-09-03T11:50:02.208Z
Learning: For REST API controller tests in plugins/woocommerce/tests/**/*.php, test classes should extend WC_REST_Unit_Test_Case instead of WC_Unit_Test_Case because REST routes need to be registered before tests run.
Applied to files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
190-190: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
240-240: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
240-240: Avoid unused parameters such as '$operation'. (undefined)
(UnusedFormalParameter)
240-240: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
52-69: LGTM — rest_do_request dispatch verified; routes match registered endpoints.RestAbilityFactory::execute_operation dispatches WP_REST_Request to the routes configured in AbilitiesRestBridge ('/wc/v3/products' and '/wc/v3/orders' — plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php) and the corresponding controllers register those endpoints under namespace 'wc/v3' (plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php, class-wc-rest-orders-controller.php).
peterfabian
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see this is being initialized on all requests, even for favicon or cron requests. Is it possible to limit this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (5)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (5)
57-60: Require HTTPS for credentialed MCP calls (dev override allowed).Prevent API key leakage over plaintext HTTP; allow opt‑in for local/dev via a filter.
public function validate_request( \WP_REST_Request $request ) { + // Require TLS by default; allow explicit opt-in for non-SSL (e.g., local dev). + if ( ! is_ssl() && ! apply_filters( 'woocommerce_mcp_allow_insecure_transport', false, $request ) ) { + return new \WP_Error( + 'insecure_transport', + __( 'HTTPS is required for MCP requests.', 'woocommerce' ), + array( 'status' => 403 ) + ); + } // Get X-MCP-API-Key header
99-101: Do not sanitize secrets; trim only before hashing.
sanitize_text_field()mutates values and will break valid keys.- // Hash the consumer key as WooCommerce does - $hashed_consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) ); + // Hash the consumer key as WooCommerce does (trim only; do not mutate secrets). + $hashed_consumer_key = wc_api_hash( trim( (string) $consumer_key ) );
112-119: Unify auth errors (i18n), trim secret, and keep messages generic.Avoid leaking which credential failed; keep timing-safe compare and translate strings.
- return new \WP_Error( - 'invalid_consumer_key', - 'Consumer key is invalid.', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'invalid_consumer_key', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + ); @@ - if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { - return new \WP_Error( - 'invalid_consumer_secret', - 'Consumer secret is invalid.', - array( 'status' => 401 ) - ); + if ( ! hash_equals( $user_data->consumer_secret, trim( (string) $consumer_secret ) ) ) { + return new \WP_Error( + 'invalid_consumer_secret', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + );Also applies to: 121-128
130-137: Enforce allowed key permissions and ensure user exists before switching context.Prevents orphaned keys and too‑permissive scopes.
- // Store permissions for tool-level checking - self::$current_mcp_permissions = $user_data->permissions; - - // Set the current user - wp_set_current_user( $user_data->user_id ); + // Store permissions for tool-level checking. + self::$current_mcp_permissions = $user_data->permissions; + + // Enforce minimal permissions for MCP (filterable). + $allowed_permissions = apply_filters( 'woocommerce_mcp_allowed_key_permissions', array( 'read', 'read_write' ) ); + if ( ! in_array( self::$current_mcp_permissions, $allowed_permissions, true ) ) { + return new \WP_Error( + 'insufficient_api_key_permissions', + __( 'The API key does not grant required permissions for MCP.', 'woocommerce' ), + array( 'status' => 403 ) + ); + } + + // Ensure the user exists before switching context. + $user = get_user_by( 'id', (int) $user_data->user_id ); + if ( ! $user ) { + return new \WP_Error( + 'mcp_user_not_found', + __( 'The user associated with this API key no longer exists.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } + wp_set_current_user( $user->ID );
44-49: Fix nullable param: avoid fatal in strict mode and match parent signature.
validate_request()requires\WP_REST_Request; passingnullfromcheck_permission()can fatally error. Type the param and drop the default.- * @param WP_REST_Request|null $request The REST request object. + * @param \WP_REST_Request $request The REST request object. @@ - public function check_permission( $request = null ) { + public function check_permission( \WP_REST_Request $request ) { return $this->validate_request( $request ); }
🧹 Nitpick comments (9)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (4)
61-67: Localize error messages (i18n) and align phrasing.User‑facing strings must be translatable.
- return new \WP_Error( - 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'missing_api_key', + __( 'API key required in X-MCP-API-Key header. Format: consumer_key:consumer_secret', 'woocommerce' ), + array( 'status' => 401 ) + ); @@ - return new \WP_Error( - 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'invalid_api_key', + __( 'X-MCP-API-Key must be in format consumer_key:consumer_secret.', 'woocommerce' ), + array( 'status' => 401 ) + );Also applies to: 69-75
96-96: Strengthen types on private auth method.Add scalar types for clarity and static analysis.
- private function authenticate( $consumer_key, $consumer_secret ) { + private function authenticate( string $consumer_key, string $consumer_secret ) {
24-39: Avoid duplicate filter registration across multiple instances.Make the filter hook idempotent to prevent duplicate callbacks.
/** * Current MCP user's API key permissions. @@ private static $current_mcp_permissions = null; + + /** + * Whether the permission filter has been registered (avoid duplicate callbacks). + * + * @var bool + */ + private static $permission_filter_hooked = false; @@ - // Register permission filter callback - add_filter( 'woocommerce_check_rest_ability_permissions_for_method', array( $this, 'check_ability_permission' ), 10, 3 ); + // Register permission filter callback once. + if ( ! self::$permission_filter_hooked ) { + add_filter( 'woocommerce_check_rest_ability_permissions_for_method', array( $this, 'check_ability_permission' ), 10, 3 ); + self::$permission_filter_hooked = true; + }
156-178: Silence unused param warning and defer on unknown methods.Rename the unused param and return
$allowedfor unknown HTTP verbs.- public function check_ability_permission( $allowed, $method, $controller ) { + public function check_ability_permission( $allowed, $method, $_controller ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found @@ - default: - return false; + default: + // Unknown HTTP method; defer to prior filters/default. + return $allowed;plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (5)
10-10: Remove unused import.
WooCommerceRestTransportis not referenced in this file.-use Automattic\WooCommerce\Internal\MCP\Transport\WooCommerceRestTransport;
27-39: Guard config keys and normalize the route to avoid notices/misroutes.Ensure
controller,route, andabilitiesare present/typed; normalize the route once.- public static function register_controller_abilities( array $config ): void { - $controller_class = $config['controller']; + public static function register_controller_abilities( array $config ): void { + $controller_class = $config['controller'] ?? null; + $route = isset( $config['route'] ) ? '/' . ltrim( (string) $config['route'], '/' ) : null; + $abilities = isset( $config['abilities'] ) && is_array( $config['abilities'] ) ? $config['abilities'] : array(); - if ( ! class_exists( $controller_class ) ) { + if ( ! $controller_class || ! class_exists( $controller_class ) || ! $route ) { return; } $controller = new $controller_class(); - foreach ( $config['abilities'] as $ability_config ) { - self::register_single_ability( $controller, $ability_config, $config['route'] ); + foreach ( $abilities as $ability_config ) { + self::register_single_ability( $controller, $ability_config, $route ); } }
62-67: Drop unused$controllerin execute_operation and normalize route in one place.Removes PHPMD warning and makes routing resilient if a route without leading slash slips in.
- 'execute_callback' => function( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); + 'execute_callback' => function( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route ); },- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( string $operation, array $input, string $route ) { $method = self::get_http_method_for_operation( $operation ); - // Build final route - add ID for single item operations - $request_route = $route; + // Build final route - add ID for single item operations + $request_route = '/' . ltrim( $route, '/' ); if ( isset( $input['id'] ) && in_array( $operation, array( 'get', 'update', 'delete' ), true ) ) { $request_route .= '/' . intval( $input['id'] ); unset( $input['id'] ); }Also applies to: 192-201
71-74: Use WooCommerce logger instead of error_log.Aligns with WC logging and allows filtering by source.
- } catch ( \Throwable $e ) { - // Log the error for debugging but don't break the registration of other abilities - error_log( "Failed to register ability {$ability_config['id']}: " . $e->getMessage() ); - } + } catch ( \Throwable $e ) { + $logger = function_exists( 'wc_get_logger' ) ? wc_get_logger() : null; + if ( $logger ) { + $logger->error( + sprintf( 'MCP: Failed to register ability %s: %s', $ability_config['id'], $e->getMessage() ), + array( 'source' => 'mcp-abilities' ) + ); + } + }
250-262: Return WP_Error on denial for clearer failures; suppress PHPMD unused param.Improves DX by surfacing 403 with a message, and silences PHPMD for
$input.- * @return bool Whether permission is granted. + * @return bool|\WP_Error Whether permission is granted. */ - private static function check_permission( $controller, string $operation, array $input ): bool { + // @SuppressWarnings(PHPMD.UnusedFormalParameter) + private static function check_permission( $controller, string $operation, array $input ) { // Get HTTP method for the operation $method = self::get_http_method_for_operation( $operation ); /** * Filter to check REST ability permissions for HTTP method. * * @param bool $allowed Whether the operation is allowed. Default false. * @param string $method HTTP method (GET, POST, PUT, DELETE). * @param object $controller REST controller instance. */ - return apply_filters( 'woocommerce_check_rest_ability_permissions_for_method', false, $method, $controller ); + $allowed = apply_filters( 'woocommerce_check_rest_ability_permissions_for_method', false, $method, $controller ); + if ( $allowed ) { + return true; + } + return new \WP_Error( + 'rest_forbidden', + __( 'You are not allowed to perform this operation via MCP.', 'woocommerce' ), + array( 'status' => 403 ) + ); }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧬 Code graph analysis (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(250-262)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-180)check_permission(47-49)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
156-156: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
192-192: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
250-250: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
133-136: LGTM: set current user to API key owner (no admin impersonation).This resolves the prior concern about blanket admin context on valid requests.
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
27-39: Verify configured routes start with “/” and match controller namespace/rest_base. Automated verification failed (script error: /bin/bash: -c: line 11: syntax error near unexpected token `('). Manually confirm ability configs under plugins/woocommerce define 'route' values that begin with '/' and that each route corresponds to its controller's rest_base (see register_controller_abilities in plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php).
Initialization moved to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
88-116: Type‑check adapter and localize server strings.Avoid relying on exceptions for flow control and ensure UI strings are translatable.
Apply this diff:
public function initialize_mcp_server( $adapter ): void { + // Ensure adapter exposes the expected API. + if ( ! is_object( $adapter ) || ! method_exists( $adapter, 'create_server' ) ) { + return; + } @@ - $adapter->create_server( + $adapter->create_server( 'woocommerce-mcp', 'woocommerce', 'mcp', - 'WooCommerce MCP Server', - 'AI-accessible WooCommerce operations via MCP', + __( 'WooCommerce MCP Server', 'woocommerce' ), + __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ), '1.0.0', array( WooCommerceRestTransport::class ), \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, $abilities_ids, );
🧹 Nitpick comments (2)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (2)
98-101: Temporarily disabling validation: verify blast radius and track upstream.Disabling mcp_validation_enabled globally (even briefly) could affect other MCP servers created in the same request. If the filter supports context args, gate it by server id; otherwise, keep as is but link the upstream issue in the TODO so we know when to remove.
Could you add a reference to the upstream validator bug (issue URL/ID) in the TODO and confirm whether the mcp_validation_enabled filter receives arguments that would allow scoping?
Also applies to: 124-127
117-123: Enrich error log context.Include exception class/code and useful context to speed up debugging.
Apply this diff:
- if ( function_exists( 'wc_get_logger' ) ) { - wc_get_logger()->error( - 'MCP server initialization failed: ' . $e->getMessage(), - array( 'source' => 'woocommerce-mcp' ) - ); - } + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + sprintf( 'MCP server initialization failed: [%s:%s] %s', get_class( $e ), (string) $e->getCode(), $e->getMessage() ), + array( + 'source' => 'woocommerce-mcp', + 'server_id' => 'woocommerce-mcp', + 'abilities_count' => is_array( $abilities_ids ) ? count( $abilities_ids ) : 0, + ) + ); + }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (7)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1643)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-50)__construct(23-25)getAbilitiesIDs(40-49)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-180)__construct(34-39)plugins/woocommerce/src/Internal/Features/FeaturesController.php (2)
__construct(131-167)feature_is_enabled(718-730)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)
🔇 Additional comments (5)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
47-60: Good gating and idempotency.Feature flag check + per‑request initialized guard are appropriate.
66-73: Adapter boot approach is sound.class_exists guard + instance bootstrap is safe in REST context.
93-97: Bail on empty abilities: good defensive check.Prevents spinning up a server with no abilities.
124-127: Nice use of finally for cleanup.Guarantees validation is re‑enabled even on failure.
40-41: Adapter uses default priority 10 — original assumption is incorrect.WP\MCP\Core\McpAdapter registers its rest_api_init callback at priority 10 and dispatches mcp_adapter_init from that callback; our maybe_initialize also hooks at priority 10, so execution order is determined by hook registration / plugin load order. Make the order explicit (adjust priorities or verify load order) if maybe_initialize depends on mcp_adapter_init.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
62-67: Route‑level permission enforced. LGTM.Permission is checked via filter before execution; addresses earlier feedback.
🧹 Nitpick comments (6)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (6)
10-10: Remove unused import.
WooCommerceRestTransportisn’t referenced in this file.- use Automattic\WooCommerce\Internal\MCP\Transport\WooCommerceRestTransport;
27-39: Validate config and normalize route upfront.Harden against missing keys and malformed routes; avoid notices and 404s.
public static function register_controller_abilities( array $config ): void { - $controller_class = $config['controller']; + if ( empty( $config['controller'] ) || empty( $config['abilities'] ) || empty( $config['route'] ) || ! is_array( $config['abilities'] ) ) { + return; + } + $controller_class = $config['controller']; if ( ! class_exists( $controller_class ) ) { return; } $controller = new $controller_class(); - foreach ( $config['abilities'] as $ability_config ) { - self::register_single_ability( $controller, $ability_config, $config['route'] ); + $route = '/' . ltrim( (string) $config['route'], '/' ); + foreach ( $config['abilities'] as $ability_config ) { + self::register_single_ability( $controller, $ability_config, $route ); } }
55-75: Prefer WooCommerce logger over error_log.Use
wc_get_logger()with a context; avoids noisy PHP error logs and integrates with WC logging.- } catch ( \Throwable $e ) { - // Log the error for debugging but don't break the registration of other abilities - error_log( "Failed to register ability {$ability_config['id']}: " . $e->getMessage() ); - } + } catch ( \Throwable $e ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + sprintf( 'Failed to register ability %s: %s', $ability_config['id'], $e->getMessage() ), + array( 'source' => 'rest-ability-factory' ) + ); + } + }
156-215: Harden arg sanitation against unexpected shapes.Guard non‑array args; only copy array enums; avoid leaking unknown nested keys.
private static function sanitize_args_to_schema( array $args ): array { $properties = array(); $required = array(); foreach ( $args as $key => $arg ) { - $property = array(); + if ( ! is_array( $arg ) ) { + continue; + } + $property = array(); // Copy valid JSON Schema fields if ( isset( $arg['type'] ) ) { - $property['type'] = $arg['type']; + $property['type'] = $arg['type']; } if ( isset( $arg['description'] ) ) { $property['description'] = $arg['description']; } if ( isset( $arg['default'] ) ) { $property['default'] = $arg['default']; } - if ( isset( $arg['enum'] ) ) { - $property['enum'] = array_values( $arg['enum'] ); + if ( isset( $arg['enum'] ) && is_array( $arg['enum'] ) ) { + $property['enum'] = array_values( $arg['enum'] ); } - if ( isset( $arg['items'] ) ) { - $property['items'] = $arg['items']; + if ( isset( $arg['items'] ) && is_array( $arg['items'] ) ) { + $property['items'] = $arg['items']; } if ( isset( $arg['minimum'] ) ) { $property['minimum'] = $arg['minimum']; } if ( isset( $arg['maximum'] ) ) { $property['maximum'] = $arg['maximum']; } if ( isset( $arg['format'] ) ) { $property['format'] = $arg['format']; } - if ( isset( $arg['properties'] ) ) { - $property['properties'] = $arg['properties']; + if ( isset( $arg['properties'] ) && is_array( $arg['properties'] ) ) { + $property['properties'] = $arg['properties']; } // Convert readonly to readOnly (JSON Schema format) if ( isset( $arg['readonly'] ) && $arg['readonly'] ) { $property['readOnly'] = true; } // Collect required fields if ( isset( $arg['required'] ) && $arg['required'] === true ) { $required[] = $key; } $properties[ $key ] = $property; } $schema = array( 'type' => 'object', 'properties' => $properties, ); if ( ! empty( $required ) ) { - $schema['required'] = array_unique( $required ); + $schema['required'] = array_values( array_unique( $required ) ); } return $schema; }
267-297: Remove unused parameters and normalize route when executing.Silences PHPMD warnings and avoids malformed routes.
- 'execute_callback' => function( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); + 'execute_callback' => function( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route ); }, - 'permission_callback' => function( $input ) use ( $controller, $ability_config ) { - return self::check_permission( $controller, $ability_config['operation'], $input ); + 'permission_callback' => function( $input ) use ( $controller, $ability_config ) { + return self::check_permission( $controller, $ability_config['operation'] ); },- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( string $operation, array $input, string $route ) { $method = self::get_http_method_for_operation( $operation ); // Build final route - add ID for single item operations - $request_route = $route; + $request_route = '/' . ltrim( $route, '/' ); if ( isset( $input['id'] ) && in_array( $operation, array( 'get', 'update', 'delete' ), true ) ) { $request_route .= '/' . intval( $input['id'] ); unset( $input['id'] ); }- private static function check_permission( $controller, string $operation, array $input ): bool { + private static function check_permission( $controller, string $operation ): bool { // Get HTTP method for the operation $method = self::get_http_method_for_operation( $operation );Also applies to: 63-67, 325-337
278-281: Use query params for GET to avoid mixing transport layers.Minor: for GET, prefer
set_query_paramsfor clarity.- foreach ( $input as $key => $value ) { - $request->set_param( $key, $value ); - } + if ( 'GET' === $method ) { + $request->set_query_params( $input ); + } else { + foreach ( $input as $key => $value ) { + $request->set_param( $key, $value ); + } + }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-180)check_permission(47-49)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
267-267: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
325-325: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (4)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (4)
84-142: Input schema derivation looks sound.Ops map correctly; update includes required id; sensible fallback.
224-256: Output schema mapping is reasonable.List wrapping and delete confirmation shape make sense; fallback is safe.
328-336: Confirm filter ordering so abilities aren’t always denied.
apply_filters(..., false, ...)means deny by default; ensureWooCommerceRestTransportis constructed (and its filter registered) before these ability checks execute in MCP context.
34-34: Confirm zero‑arg controllers.
Automated checks couldn't find the controller class definitions in this repo (they appear to be WooCommerce core classes). Verify every controller registered via plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php can be instantiated with new $controller_class() (instantiation at plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php:34); if any require constructor args, replace with a factory or inject dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (5)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (5)
70-76: Localize error message for missing API key.The error message for missing API key is not localized, while other error messages in this file are using the
__()function.Apply this diff to localize the error message:
if ( empty( $api_key ) ) { return new \WP_Error( 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', + __( 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', 'woocommerce' ), array( 'status' => 401 ) ); }
121-137: Localize and unify error messages for invalid credentials.The error messages expose whether the consumer key or secret is invalid, which could aid attackers. Consider using a generic message for both cases. Also, these messages should be localized.
Apply this diff to unify and localize error messages:
// Check if user data was found if ( empty( $user_data ) ) { return new \WP_Error( 'invalid_consumer_key', - 'Consumer key is invalid.', + __( 'Invalid credentials.', 'woocommerce' ), array( 'status' => 401 ) ); } // Validate consumer secret using hash_equals for timing attack protection if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { return new \WP_Error( 'invalid_consumer_secret', - 'Consumer secret is invalid.', + __( 'Invalid credentials.', 'woocommerce' ), array( 'status' => 401 ) ); }
131-131: Apply trimming to consumer_secret for consistency.The consumer_secret should also be trimmed (but not sanitized) before comparison to ensure consistency with how the consumer_key is handled.
Apply this diff:
// Validate consumer secret using hash_equals for timing attack protection - if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { + if ( ! hash_equals( $user_data->consumer_secret, trim( (string) $consumer_secret ) ) ) {
108-109: Avoid sanitizing API keys as it may alter their values.Using
sanitize_text_field()on the consumer key can potentially alter the key value (e.g., stripping special characters), which would cause valid keys to fail authentication. API keys should only be trimmed to remove whitespace.Apply this diff to only trim the consumer key:
// Hash the consumer key as WooCommerce does - $hashed_consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) ); + $hashed_consumer_key = wc_api_hash( trim( (string) $consumer_key ) );
139-145: Validate API key permissions and ensure user exists before setting context.The code should verify that:
- The API key has appropriate permissions for MCP (at least 'read')
- The user associated with the API key still exists in the system
Apply this diff to add permission and user validation:
// Store permissions for tool-level checking self::$current_mcp_permissions = $user_data->permissions; + // Enforce minimal permissions for MCP (filterable). + $allowed_permissions = apply_filters( 'woocommerce_mcp_allowed_key_permissions', array( 'read', 'read_write' ) ); + if ( ! in_array( $user_data->permissions, $allowed_permissions, true ) ) { + return new \WP_Error( + 'insufficient_api_key_permissions', + __( 'The API key does not grant required permissions for MCP.', 'woocommerce' ), + array( 'status' => 403 ) + ); + } + + // Ensure the user exists before switching context. + $user = get_user_by( 'id', (int) $user_data->user_id ); + if ( ! $user ) { + return new \WP_Error( + 'mcp_user_not_found', + __( 'The user associated with this API key no longer exists.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } + // Set the current user - wp_set_current_user( $user_data->user_id ); + wp_set_current_user( $user->ID );
🧹 Nitpick comments (3)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
47-49: Consider adding proper parameter typing tocheck_permissionmethod.The method accepts
$request = nullbut then passes it directly tovalidate_requestwhich expects a typed\WP_REST_Requestparameter. This could cause a runtime error if null is passed.Apply this diff to add proper typing:
- public function check_permission( $request = null ) { + public function check_permission( \WP_REST_Request $request ) { return $this->validate_request( $request ); }
78-84: Localize error message for invalid API key format.The error message for invalid API key format is not localized.
Apply this diff to localize the error message:
if ( strpos( $api_key, ':' ) === false ) { return new \WP_Error( 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', + __( 'X-MCP-API-Key must be in format consumer_key:consumer_secret', 'woocommerce' ), array( 'status' => 401 ) ); }
165-187: Consider using the $controller parameter or documenting why it's unused.The static analysis tool flagged that
$controllerparameter is unused. While this may be intentional (as permissions are based purely on HTTP method and API key permissions), you might want to use it for more granular permission checks in the future.Consider either:
- Adding a comment explaining why
$controlleris intentionally unused- Using it for more granular permission checks based on the specific REST controller
Example documentation:
/** * Check REST ability permissions for HTTP method. * * @param bool $allowed Whether the operation is allowed. Default false. * @param string $method HTTP method (GET, POST, PUT, DELETE). * @param object $controller REST controller instance (unused - permissions based on method only). * @return bool Whether permission is granted. */
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(325-337)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
165-165: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
58-65: LGTM! Proper TLS enforcement with development override.The implementation correctly requires HTTPS by default while providing a filter for local development environments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
24-31: Add a scoped callback property to avoid clobbering third‑party filters.We add/remove a global
__return_falsecallback later; keeping our own callable avoids removing someone else’s identical callback.Apply this diff:
class MCPAdapterProvider { /** * Whether MCP adapter is initialized. * * @var bool */ private bool $initialized = false; + + /** + * Scoped callback used to temporarily disable MCP validation. + * @var callable|null + */ + private $validation_disable_cb = null;
65-73: Log when MCP adapter class is unavailable.Currently this fails silently; logging helps diagnose missing deps/config.
Apply this diff:
private function initialize_mcp_adapter(): void { // Check if MCP adapter class exists (should be autoloaded by WooCommerce's composer) if ( ! class_exists( 'WP\MCP\Core\McpAdapter' ) ) { - return; + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->warning( + 'MCP adapter class not found. Skipping MCP initialization.', + array( 'source' => 'woocommerce-mcp' ) + ); + } + return; }
97-101: Don’t remove other plugins’mcp_validation_enabledfilters.Using
__return_falserisks removing someone else’s identical callback at the same priority. Use a scoped closure stored on the instance.Apply this diff:
- // Temporarily disable MCP validation during server creation - // Workaround for validator bug with union types (e.g., ["integer", "null"]) - // TODO: Remove once mcp-adapter validator bug is fixed - add_filter( 'mcp_validation_enabled', '__return_false', 999 ); + // Temporarily disable MCP validation during server creation (workaround for union-types bug). + // Use an instance-scoped callback to avoid removing third-party filters. + $this->validation_disable_cb = static function (): bool { + return false; + }; + add_filter( 'mcp_validation_enabled', $this->validation_disable_cb, 999 ); @@ - // Re-enable MCP validation immediately after server creation - remove_filter( 'mcp_validation_enabled', '__return_false', 999 ); + // Re-enable MCP validation immediately after server creation + if ( null !== $this->validation_disable_cb ) { + remove_filter( 'mcp_validation_enabled', $this->validation_disable_cb, 999 ); + $this->validation_disable_cb = null; + }Also applies to: 124-126
102-116: Type-check the adapter and localize server labels.
- Add a lightweight type/method check (defensive; avoids hard errors if the hook is misused).
- Wrap name/description with
__()for i18n.Apply this diff:
- public function initialize_mcp_server( $adapter ): void { + public function initialize_mcp_server( $adapter ): void { // Get filtered abilities for MCP server $abilities_ids = $this->get_woocommerce_mcp_abilities(); @@ - try { + try { + // Ensure adapter has the expected API. + if ( ! is_object( $adapter ) || ! method_exists( $adapter, 'create_server' ) ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + 'MCP server initialization failed: invalid adapter instance (missing create_server).', + array( 'source' => 'woocommerce-mcp' ) + ); + } + return; + } // Create MCP server $adapter->create_server( 'woocommerce-mcp', 'woocommerce', 'mcp', - 'WooCommerce MCP Server', - 'AI-accessible WooCommerce operations via MCP', + __( 'WooCommerce MCP Server', 'woocommerce' ), + __( 'AI-accessible WooCommerce operations via MCP', 'woocommerce' ), '1.0.0', array( WooCommerceRestTransport::class ), \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, $abilities_ids, );
1-166: Add tests for key flows.
- Feature flag gating: ensure no init when disabled; init when enabled on REST only.
- Validation filter scoping: assert filter state is restored after exceptions.
- Abilities filtering: only
woocommerce/by default; custom include via filter.- Logging: assert error is logged on invalid adapter.
I can scaffold PHPUnit tests targeting these behaviors if helpful.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (5)
plugins/woocommerce/includes/class-woocommerce.php (2)
WooCommerce(45-1643)__construct(251-256)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)getAbilitiesIDs(39-48)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
🔇 Additional comments (8)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (8)
6-15: Good: strict types + ABSPATH guard.Solid baseline for safety and WP compatibility.
10-13: Imports look correct and minimal.Only what’s needed is imported; nice.
34-41: Initializing onrest_api_initis the right call.Prevents unnecessary boot on non‑REST hits and addresses the earlier review thread.
46-60: Feature flag + single‑shot guard: LGTM.Gates the integration and prevents duplicate init within the request.
78-81: Hook wiring is appropriate.Ties server setup to adapter readiness.
88-96: Bailing on empty abilities is correct.Prevents creating a server with no abilities.
163-165: Public probe method is helpful.Useful for integration tests and diagnostics.
137-156: No action required —str_starts_withis safe hereWooCommerce requires PHP 7.4+ and WordPress provides polyfills for PHP 8 string helpers (str_starts_with/str_contains via wp-includes/compat.php), so the namespace filter can remain as written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
103-106: Prefer runtime/feature detection instead of a version check for removing this workaroundcomposer.json requires wordpress/mcp-adapter: "dev-trunk" (composer.lock also lists version "dev-trunk" / commit 95981e865bde0424a177dacb6fb06be03cc76320), so there is no stable package version to reliably check. Replace the TODO/version-check suggestion with a runtime approach: if the adapter exposes a VERSION constant or get_version() accessor, use that; otherwise add a small validator compatibility test at runtime (e.g., attempt a minimal union-type validation) and only disable mcp validation when that test fails. Keep the existing try/finally that re-enables validation.
Location: plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (around the add_filter at ~lines 103–106).
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (6)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1643)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)getAbilitiesIDs(39-48)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-189)__construct(34-39)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
172-174: Avoid unused private methods such as 'disable_mcp_validation'. (undefined)
(UnusedPrivateMethod)
🔇 Additional comments (7)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (7)
34-41: LGTM! Well-structured hook registration with performance optimization.The constructor correctly hooks into
rest_api_initwith priority 10 to ensure MCP initialization only occurs on REST API requests, preventing unnecessary overhead on other requests like favicon or cron jobs. The priority ordering ensures proper sequencing with the MCP adapter's own initialization.
46-60: LGTM! Proper feature flag gating and initialization flow.The initialization logic correctly:
- Guards against feature flag disabled state
- Prevents double initialization
- Follows a clean initialization sequence
65-79: LGTM! Robust class existence checking and error handling.The method properly validates the MCP adapter class exists before attempting to use it, with appropriate logging when the class is not found. The singleton pattern usage is correct for triggering the adapter's initialization.
94-133: LGTM! Comprehensive server initialization with proper error handling.The server initialization method includes:
- Early return for empty abilities
- Temporary validation disable workaround for known MCP adapter bug
- Proper exception handling with logging
- Clean filter removal in finally block
- Localized strings for UI elements
The implementation addresses all the concerns from the previous review.
143-162: LGTM! Well-designed ability filtering with extensibility.The method provides a clean separation of concerns:
- Gets all abilities from the registry
- Filters by namespace (
woocommerce/) by default- Allows override via the
woocommerce_mcp_include_abilityfilter- Re-indexes the array properly
172-174: Method is used as a filter callback.The static analysis tool incorrectly flagged this as unused. The
disable_mcp_validationmethod is used as a callback for themcp_validation_enabledfilter on Line 106.
181-183: LGTM! Simple state accessor method.Provides a clean way to check initialization state externally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/changelog/add-mcp-to-woocommerce(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/changelog/add-mcp-to-woocommerce
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (2)
103-107: Document the temporary workaround with specific details.The comment mentions a "validator bug with union types" but lacks specifics about the issue or expected resolution timeline.
- // Temporarily disable MCP validation during server creation - // Workaround for validator bug with union types (e.g., ["integer", "null"]) - // TODO: Remove once mcp-adapter validator bug is fixed + // Temporarily disable MCP validation during server creation + // Workaround for MCP adapter validator incorrectly rejecting valid union types + // like ["integer", "null"] in JSON Schema validation + // TODO: Remove once wordpress/mcp-adapter issue is resolved
148-162: Consider caching filtered abilities for performance.The abilities filtering runs every time the server is initialized. For better performance, consider caching the filtered results if abilities don't change frequently during a request cycle.
+ /** + * Cached MCP abilities. + * + * @var array|null + */ + private ?array $cached_mcp_abilities = null; + /** * Get WooCommerce abilities for MCP server. * * Filters abilities to include only those with 'woocommerce/' namespace by default, * with a filter to allow inclusion of abilities from other namespaces. * * @return array Array of ability IDs for MCP server. */ private function get_woocommerce_mcp_abilities(): array { + // Return cached abilities if available + if ( $this->cached_mcp_abilities !== null ) { + return $this->cached_mcp_abilities; + } + // Get all abilities from the registry $abilities_registry = wc_get_container()->get( AbilitiesRegistry::class ); $all_abilities_ids = $abilities_registry->getAbilitiesIDs(); // Filter abilities based on namespace and custom filter $mcp_abilities = array_filter( $all_abilities_ids, function( $ability_id ) { // Include WooCommerce abilities by default $include = str_starts_with( $ability_id, 'woocommerce/' ); // Allow filter to override inclusion decision return apply_filters( 'woocommerce_mcp_include_ability', $include, $ability_id ); } ); - // Re-index array - return array_values( $mcp_abilities ); + // Re-index array and cache result + $this->cached_mcp_abilities = array_values( $mcp_abilities ); + return $this->cached_mcp_abilities; }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
plugins/woocommerce/composer.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
plugins/woocommerce/composer.json(2 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/woocommerce/composer.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (6)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1643)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)getAbilitiesIDs(39-48)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-189)__construct(34-39)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
🔇 Additional comments (2)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (2)
65-79: LGTM! Improved error handling and class existence check.The implementation properly guards against missing dependencies and provides appropriate logging for debugging purposes.
94-133: Verify the MCP adapter parameter type and validate abilities filtering.The method accepts an untyped
$adapterparameter but calls methods on it without validation. Additionally, the abilities filtering logic should be verified to ensure it works correctly with the current abilities structure.
f086674 to
c216491
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (8)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
70-84: i18n and stricter header validation for X‑MCP‑API‑Key.Localize messages and ensure both parts are present/non-empty.
- if ( empty( $api_key ) ) { + if ( empty( $api_key ) ) { return new \WP_Error( 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', + __( 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', 'woocommerce' ), array( 'status' => 401 ) ); } - if ( strpos( $api_key, ':' ) === false ) { + if ( strpos( $api_key, ':' ) === false ) { return new \WP_Error( 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', + __( 'X-MCP-API-Key must be in format consumer_key:consumer_secret', 'woocommerce' ), array( 'status' => 401 ) ); } - list( $consumer_key, $consumer_secret ) = explode( ':', $api_key, 2 ); + list( $consumer_key, $consumer_secret ) = explode( ':', $api_key, 2 ); + if ( '' === trim( (string) $consumer_key ) || '' === trim( (string) $consumer_secret ) ) { + return new \WP_Error( + 'invalid_api_key', + __( 'X-MCP-API-Key must include both consumer key and secret.', 'woocommerce' ), + array( 'status' => 401 ) + ); + }
165-187: Silence PHPMD: $controller is unused here.No behavior change.
public function check_ability_permission( $allowed, $method, $controller ) { + // $controller is part of the filter signature; not used here. + unset( $controller );plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (5)
27-39: Guard required config and derive route when missing.Avoid fatals when 'route' isn’t provided.
public static function register_controller_abilities( array $config ): void { $controller_class = $config['controller']; if ( ! class_exists( $controller_class ) ) { return; } - $controller = new $controller_class(); + $controller = new $controller_class(); + $route = $config['route'] ?? null; + if ( ! $route && property_exists( $controller, 'namespace' ) && property_exists( $controller, 'rest_base' ) ) { + $route = '/' . ltrim( (string) $controller->namespace, '/' ) . '/' . trim( (string) $controller->rest_base, '/' ); + } + if ( ! $route ) { + return; + } - foreach ( $config['abilities'] as $ability_config ) { - self::register_single_ability( $controller, $ability_config, $config['route'] ); + foreach ( $config['abilities'] as $ability_config ) { + self::register_single_ability( $controller, $ability_config, $route ); } }
71-75: Prefer wc_get_logger over error_log.Aligns with WooCommerce logging and respects filters.
- } catch ( \Throwable $e ) { - // Log the error for debugging but don't break the registration of other abilities - error_log( "Failed to register ability {$ability_config['id']}: " . $e->getMessage() ); + } catch ( \Throwable $e ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + "Failed to register ability {$ability_config['id']}: " . $e->getMessage(), + array( 'source' => 'woocommerce-mcp' ) + ); + } }
271-275: Normalize route formatting.Ensure a leading slash and strip trailing slashes to avoid dispatch edge cases.
- $request_route = $route; + $request_route = '/' . ltrim( (string) $route, '/' ); + $request_route = rtrim( $request_route, '/' );
267-299: Remove unused $controller parameter from execute_operation (or unset).Either drop it and update the call site, or unset to satisfy static analysis.
- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( string $operation, array $input, string $route ) {And update the caller:
- 'execute_callback' => function( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); + 'execute_callback' => function( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route ); },If you prefer not to change the signature now:
+ // $controller is unused; keep signature for BC. + unset( $controller );
325-337: Silence PHPMD: $input unused in check_permission.No behavior change.
private static function check_permission( $controller, string $operation, array $input ): bool { + unset( $input ); // Get HTTP method for the operation $method = self::get_http_method_for_operation( $operation );plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
143-162: Feature-scope filter looks good; minor: tolerate non-string IDs.Cast ability_id to string before str_starts_with to avoid accidental warnings.
- function( $ability_id ) { + function( $ability_id ) { + $ability_id = (string) $ability_id; // Include WooCommerce abilities by default - $include = str_starts_with( $ability_id, 'woocommerce/' ); + $include = str_starts_with( $ability_id, 'woocommerce/' );
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
plugins/woocommerce/bin/composer/phpcs/composer.lockis excluded by!**/*.lockplugins/woocommerce/bin/composer/phpunit/composer.lockis excluded by!**/*.lockplugins/woocommerce/composer.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
plugins/woocommerce/changelog/add-mcp-to-woocommerce(1 hunks)plugins/woocommerce/composer.json(2 hunks)plugins/woocommerce/includes/class-woocommerce.php(2 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/Features/FeaturesController.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- plugins/woocommerce/changelog/add-mcp-to-woocommerce
🚧 Files skipped from review as they are similar to previous changes (5)
- plugins/woocommerce/composer.json
- plugins/woocommerce/src/Internal/Features/FeaturesController.php
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php
- plugins/woocommerce/includes/class-woocommerce.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧬 Code graph analysis (3)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (6)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1645)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)getAbilitiesIDs(39-48)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-189)__construct(34-39)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-189)check_permission(47-49)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(325-337)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
267-267: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
325-325: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php
33-33: Avoid unused parameters such as '$output'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
165-165: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
- GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (5)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
33-36: Silence PHPMD unused param warning in validate_output.Unset the parameter to avoid false positives without changing behavior.
protected function validate_output( $output ) { - // Skip validation - trust that REST controllers return valid data + // Skip validation - trust that REST controllers return valid data. + unset( $output ); // silence static analysis (unused param) return true; }plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
34-41: Nice: initialization moved to rest_api_init.This limits MCP setup to REST traffic and addresses prior concerns. Please confirm admin‑area flows (e.g., tools hitting REST internally) still work as expected.
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
139-146: Enforce minimal key permissions and ensure user exists before switching context.Prevents orphaned keys and overly permissive access.
// Store permissions for tool-level checking self::$current_mcp_permissions = $user_data->permissions; - // Set the current user - wp_set_current_user( $user_data->user_id ); + // Enforce minimal permissions for MCP (filterable). + $allowed_permissions = apply_filters( 'woocommerce_mcp_allowed_key_permissions', array( 'read', 'read_write' ) ); + if ( ! in_array( $user_data->permissions, $allowed_permissions, true ) ) { + return new \WP_Error( + 'insufficient_api_key_permissions', + __( 'The API key does not grant required permissions for MCP.', 'woocommerce' ), + array( 'status' => 403 ) + ); + } + + // Ensure the user exists before switching context. + $user = get_user_by( 'id', (int) $user_data->user_id ); + if ( ! $user ) { + return new \WP_Error( + 'mcp_user_not_found', + __( 'The user associated with this API key no longer exists.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } + + // Set the current user. + wp_set_current_user( $user->ID );
108-110: Do not mutate secrets; trim only. Localize auth errors.sanitize_text_field() can alter secrets. Use trim casts; keep timing-safe compare.
- $hashed_consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) ); + $hashed_consumer_key = wc_api_hash( trim( (string) $consumer_key ) ); @@ - if ( empty( $user_data ) ) { + if ( empty( $user_data ) ) { return new \WP_Error( 'invalid_consumer_key', - 'Consumer key is invalid.', + __( 'Invalid credentials.', 'woocommerce' ), array( 'status' => 401 ) ); } @@ - if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { + if ( ! hash_equals( $user_data->consumer_secret, trim( (string) $consumer_secret ) ) ) { return new \WP_Error( 'invalid_consumer_secret', - 'Consumer secret is invalid.', + __( 'Invalid credentials.', 'woocommerce' ), array( 'status' => 401 ) ); }Also applies to: 130-137
47-49: Type-safety: prevent null from reaching validate_request.As written, a null $request would cause a TypeError in validate_request. Either type-hint here or guard/null-check.
- public function check_permission( $request = null ) { - return $this->validate_request( $request ); - } + public function check_permission( $request = null ) { + if ( ! $request instanceof \WP_REST_Request ) { + return new \WP_Error( + 'invalid_request', + __( 'Invalid request object.', 'woocommerce' ), + array( 'status' => 400 ) + ); + } + return $this->validate_request( $request ); + }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (10)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
167-189: Silence unused-parameter warning for $controller
$controlleris required by the filter signature but unused here. Unset to satisfy static analysis.public function check_ability_permission( $allowed, $method, $controller ) { + // Ensure no unused-parameter warnings. + unset( $controller );plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
33-36: Address PHPMD unused parameterAvoid the
$outputunused-parameter warning.protected function validate_output( $output ) { - // Skip validation - trust that REST controllers return valid data. - return true; + // Skip validation - trust that REST controllers return valid data. + unset( $output ); + return true; }plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php (1)
27-96: Make configurations filterable to allow extensions to tailor abilitiesExpose the configurations array via a filter so sites/plugins can adjust without forking.
- private static function get_configurations(): array { - return array( + private static function get_configurations(): array { + $configs = array( /* products + orders configs */ - ); + ); + /** Allow extensions to modify/extend exposed abilities. */ + return apply_filters( 'woocommerce_mcp_rest_bridge_configurations', $configs ); }plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (3)
57-60: Hook before initialize to avoid missing the init signalIf the adapter fires
mcp_adapter_initduring/soon after instantiation, registering the hook after callinginstance()risks missing it. Register first, then initialize.- $this->initialize_mcp_adapter(); - $this->register_hooks(); + $this->register_hooks(); + $this->initialize_mcp_adapter(); $this->initialized = true;
94-101: Type-check the adapter before useDefensively verify the adapter instance exposes
create_server()before calling it.public function initialize_mcp_server( $adapter ): void { + if ( ! is_object( $adapter ) || ! method_exists( $adapter, 'create_server' ) ) { + return; + }Also applies to: 108-121
148-157: Avoid PHP 8-only str_starts_with; use portable checkKeep BC with environments that might still run <8.0.
- $include = str_starts_with( $ability_id, 'woocommerce/' ); + $include = 0 === strpos( (string) $ability_id, 'woocommerce/' );plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (4)
10-10: Remove unused import
WooCommerceRestTransportisn’t referenced in this file.-use Automattic\WooCommerce\Internal\MCP\Transport\WooCommerceRestTransport;
71-75: Prefer Woo logger over error_logUse
wc_get_logger()for consistency and routing via WC logging settings.- } catch ( \Throwable $e ) { - // Log the error for debugging but don't break the registration of other abilities. - // Log error for debugging purposes. - error_log( "Failed to register ability {$ability_config['id']}: " . $e->getMessage() ); + } catch ( \Throwable $e ) { + if ( function_exists( 'wc_get_logger' ) ) { + wc_get_logger()->error( + "Failed to register ability {$ability_config['id']}: " . $e->getMessage(), + array( 'source' => 'woocommerce-mcp' ) + ); + } }
268-276: Silence unused $controller in execute_operationThis param isn’t used; rename to underscore or unset to satisfy PHPMD.
- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( $_controller, string $operation, array $input, string $route ) {
326-338: Silence unused $input in check_permissionAvoid PHPMD warning while preserving signature.
private static function check_permission( $controller, string $operation, array $input ): bool { + unset( $input ); // Get HTTP method for the operation. $method = self::get_http_method_for_operation( $operation );
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php
🧬 Code graph analysis (4)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
__construct(34-41)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(326-338)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php (3)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
RestAbilityFactory(20-339)register_controller_abilities(27-39)plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php (1)
WC_REST_Products_Controller(27-2152)plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php (1)
WC_REST_Orders_Controller(26-505)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (6)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1645)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)get_abilities_ids(39-48)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-190)__construct(36-41)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-190)check_permission(49-51)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
167-167: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php
33-33: Avoid unused parameters such as '$output'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
268-268: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
326-326: Avoid unused parameters such as '$input'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (4)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
60-67: TLS gate looks goodRequiring HTTPS by default with a dev override is the right default.
72-86: Harden auth: don’t mutate secrets, avoid credential enumeration, localize, and verify user/permissions
- Don’t use
sanitize_text_field()on credentials; trim only.- Localize all error messages.
- Return a generic “Invalid credentials.” to avoid key/secret enumeration.
- Ensure associated user exists before switching context.
- Enforce allowed key permissions (filterable).
@@ - if ( empty( $api_key ) ) { - return new \WP_Error( - 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', - array( 'status' => 401 ) - ); - } + if ( empty( $api_key ) ) { + return new \WP_Error( + 'missing_api_key', + __( 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } @@ - if ( strpos( $api_key, ':' ) === false ) { - return new \WP_Error( - 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', - array( 'status' => 401 ) - ); - } + if ( strpos( $api_key, ':' ) === false ) { + return new \WP_Error( + 'invalid_api_key', + __( 'X-MCP-API-Key must be in format consumer_key:consumer_secret.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } @@ - $hashed_consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) ); + $hashed_consumer_key = wc_api_hash( trim( (string) $consumer_key ) ); @@ - if ( empty( $user_data ) ) { - return new \WP_Error( - 'invalid_consumer_key', - 'Consumer key is invalid.', - array( 'status' => 401 ) - ); - } + if ( empty( $user_data ) ) { + return new \WP_Error( + 'invalid_credentials', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } @@ - if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { - return new \WP_Error( - 'invalid_consumer_secret', - 'Consumer secret is invalid.', - array( 'status' => 401 ) - ); - } + if ( ! hash_equals( $user_data->consumer_secret, trim( (string) $consumer_secret ) ) ) { + return new \WP_Error( + 'invalid_credentials', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } + + // Enforce minimal allowed permissions for MCP (filterable). + $allowed_permissions = apply_filters( 'woocommerce_mcp_allowed_key_permissions', array( 'read', 'write', 'read_write' ) ); + if ( ! in_array( $user_data->permissions, $allowed_permissions, true ) ) { + return new \WP_Error( + 'insufficient_api_key_permissions', + __( 'The API key does not grant required permissions for MCP.', 'woocommerce' ), + array( 'status' => 403 ) + ); + } + + // Ensure the user still exists before switching context. + $user = get_user_by( 'id', (int) $user_data->user_id ); + if ( ! $user ) { + return new \WP_Error( + 'mcp_user_not_found', + __( 'The user associated with this API key no longer exists.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } @@ - wp_set_current_user( $user_data->user_id ); + wp_set_current_user( $user->ID );Also applies to: 110-139, 141-148
43-51: Require WP_REST_Request param; avoid nullable that can TypeError
validate_request()requires a\WP_REST_Request; passingnullhere can fatally error. Type-hint and drop the default.- * @param WP_REST_Request|null $request The REST request object. + * @param WP_REST_Request $request The REST request object. @@ - public function check_permission( $request = null ) { + public function check_permission( WP_REST_Request $request ) { return $this->validate_request( $request ); }plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
20-36: Bypassing output validation is acceptable for REST abilitiesGiven known schema drift in WC REST responses, skipping output validation here is reasonable and aligns with the intended design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
76-78: Use consistent phrasing and i18n for error messages.The error message "X-MCP-API-Key header required. Format: consumer_key:consumer_secret" should follow the same localization pattern as other error messages in the validation method, and be consistent with the detailed format error message below.
- return new \WP_Error( - 'missing_api_key', - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'missing_api_key', + __( 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret', 'woocommerce' ), + array( 'status' => 401 ) + );
84-86: Use i18n for error message.The error message should be localized for consistency with other error messages in the file.
- return new \WP_Error( - 'invalid_api_key', - 'X-MCP-API-Key must be in format consumer_key:consumer_secret', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'invalid_api_key', + __( 'X-MCP-API-Key must be in format consumer_key:consumer_secret', 'woocommerce' ), + array( 'status' => 401 ) + );
128-130: Use i18n for error message.The error message should be localized for consistency.
- return new \WP_Error( - 'invalid_consumer_key', - 'Consumer key is invalid.', - array( 'status' => 401 ) - ); + return new \WP_Error( + 'invalid_consumer_key', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + );plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
268-299: Unused parameter should be removed from method signature.The static analysis correctly identifies that the
$controllerparameter is not used in this method. Since the method relies onrest_do_request()for proper routing and doesn't need direct controller access, this parameter can be removed.Apply this diff:
- private static function execute_operation( $controller, string $operation, array $input, string $route ) { + private static function execute_operation( string $operation, array $input, string $route ) {And update the callback in Line 63:
- 'execute_callback' => function ( $input ) use ( $controller, $ability_config, $route ) { - return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); + 'execute_callback' => function ( $input ) use ( $ability_config, $route ) { + return self::execute_operation( $ability_config['operation'], $input, $route );
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/Features/FeaturesController.php(1 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- plugins/woocommerce/src/Internal/Features/FeaturesController.php
- plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.098Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.098Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧬 Code graph analysis (2)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(22-191)check_permission(49-51)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
__construct(34-41)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(325-338)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
268-268: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
168-168: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (7)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (4)
168-190: LGTM: Well-implemented permission checking logic.The method correctly maps HTTP methods to API key permissions and only applies MCP-specific logic when in MCP context. The switch statement covers all standard HTTP methods appropriately.
112-112: Critical security issue: sanitize_text_field modifies consumer keys.Consumer keys stored in the database are SHA256 hashed versions, not the original consumer keys shown to users. Using
sanitize_text_field()can modify the actual consumer key value, causing authentication failures since it strips characters and converts to lowercase in some cases.Apply this diff to preserve the exact consumer key value:
- // Hash the consumer key as WooCommerce does. - $hashed_consumer_key = wc_api_hash( sanitize_text_field( $consumer_key ) ); + // Hash the consumer key as WooCommerce does (do not mutate value beyond trimming). + $hashed_consumer_key = wc_api_hash( trim( (string) $consumer_key ) );
134-140: Critical security issue: sanitization not applied to consumer_secret.The consumer secret comparison uses the raw value from the header without trimming, which could lead to authentication failures due to whitespace differences. Also localize the error message.
Apply this diff:
- // Validate consumer secret using hash_equals for timing attack protection. - if ( ! hash_equals( $user_data->consumer_secret, $consumer_secret ) ) { - return new \WP_Error( - 'invalid_consumer_secret', - 'Consumer secret is invalid.', - array( 'status' => 401 ) - ); + // Validate consumer secret using hash_equals for timing attack protection (trim only). + if ( ! hash_equals( $user_data->consumer_secret, trim( (string) $consumer_secret ) ) ) { + return new \WP_Error( + 'invalid_consumer_secret', + __( 'Invalid credentials.', 'woocommerce' ), + array( 'status' => 401 ) + );
145-147: Critical security gap: no validation of user existence.The code sets the current user without verifying the user still exists in the database. If the associated user has been deleted, this could lead to undefined behavior or security issues.
Apply this diff to add user validation:
+ // Ensure the user exists before switching context. + $user = get_user_by( 'id', (int) $user_data->user_id ); + if ( ! $user ) { + return new \WP_Error( + 'mcp_user_not_found', + __( 'The user associated with this API key no longer exists.', 'woocommerce' ), + array( 'status' => 401 ) + ); + } + // Set the current user. - wp_set_current_user( $user_data->user_id ); + wp_set_current_user( $user->ID );plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (3)
325-338: LGTM: Proper permission delegation to MCP transport.The permission checking correctly delegates to the MCP transport layer via the
woocommerce_check_rest_ability_permissions_for_methodfilter, which is handled by theWooCommerceRestTransport::check_ability_permission()method. This ensures consistent permission enforcement across the MCP integration.
85-143: LGTM: Comprehensive schema generation with proper sanitization.The schema generation logic correctly handles different operation types and properly sanitizes WordPress REST arguments to valid JSON Schema format. The fallback schema ensures robustness, and the addition of ID requirements for update operations is appropriate.
157-216: LGTM: Well-implemented JSON Schema sanitization.The method properly converts WordPress REST API arguments to valid JSON Schema by removing callbacks, normalizing field formats (like
readonlytoreadOnly), and correctly handling the required fields conversion from boolean-per-field to array-of-names format.
Regenerated lock file to ensure MCP dependencies are properly resolved.
Fix incorrect version change from "dev-main" to "dev-add/mcp-to-woocommerce" in composer.lock during dependency regeneration. The monorepo-plugin version should remain as "dev-main" for proper dependency resolution. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Regenerated composer.lock and corrected monorepo-plugin version from "dev-add/mcp-to-woocommerce" to "dev-trunk" to match trunk branch. This prevents composer dependency resolution issues. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
…cp_integration feature Add missing 'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE to the mcp_integration feature definition in FeaturesController.php. This resolves PHPUnit test failures complaining about "Unexpected incorrect usage notice for add_feature_definition" and "Assuming positive compatibility by default will be deprecated in the future" warnings. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Refactor MCP feature description logic from inline closure to dedicated get_mcp_integration_description() method for better code organization and maintainability. Use direct method call instead of callback for improved performance and clarity. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Fixes: 'Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space' on line 595. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
* Add comprehensive unit tests for MCP integration This PR adds extensive test coverage for the WooCommerce Model Context Protocol (MCP) integration, establishing testing patterns and validating core functionality. ## Test Coverage Added ### MCPAdapterProviderTest (10 tests) - Feature flag enable/disable logic - Namespace filtering (woocommerce/ abilities by default) - Custom filter support for extending ability inclusion - Initialization state management and double-init prevention - Error handling for edge cases ### WooCommerceRestTransportTest (15 tests) - HTTPS enforcement with proper bypass for testing - API key validation (format, headers, authentication flow) - Database authentication logic with hash_equals validation - Permission system (read/write/read_write for HTTP methods) - User context switching and existence validation - Security edge cases (invalid credentials, missing users) ## Testing Framework Established - **Dependency Bootstrapping**: Manual loading of MCP adapter and WordPress Abilities API - **Feature Flag Testing**: Proper WordPress option-based feature management - **SSL Bypass Pattern**: Using woocommerce_mcp_allow_insecure_transport filter with clear comments - **Database Testing**: Safe API key insertion/cleanup patterns - **Permission Testing**: Reflection-based testing of static properties ## Key Testing Principles Applied - Test business logic, not external dependencies - Use proper WordPress testing patterns - Focus on edge cases and security scenarios - Clear separation between unit and integration concerns - Comprehensive coverage without over-testing **Total: 25 tests, 35+ assertions** All tests pass and validate the security, authentication, and business logic of the MCP integration without requiring actual MCP protocol connections. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Update test to use proper McpTransportContext mock Updates WooCommerceRestTransportTest to match the corrected constructor signature after fixing the interface compatibility issue. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Fix parameter naming and code formatting in MCP tests - Change generic $include parameter to descriptive $should_include in filter callback - Apply WordPress coding standards formatting to test files - Fix array syntax and spacing consistency - Add missing trailing newlines 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
95e376e to
73af99e
Compare
Reset bin/composer/phpcs/composer.lock and bin/composer/phpunit/composer.lock to match trunk versions to avoid unnecessary diff in the branch. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
plugins/woocommerce/bin/composer/phpcs/composer.lockis excluded by!**/*.lockplugins/woocommerce/bin/composer/phpunit/composer.lockis excluded by!**/*.lockplugins/woocommerce/composer.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
docs/features/mcp/README.md(1 hunks)docs/features/mcp/_category_.json(1 hunks)plugins/woocommerce/changelog/add-mcp-to-woocommerce(1 hunks)plugins/woocommerce/composer.json(2 hunks)plugins/woocommerce/includes/class-woocommerce.php(2 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php(1 hunks)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php(1 hunks)plugins/woocommerce/src/Internal/Features/FeaturesController.php(2 hunks)plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php(1 hunks)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php(1 hunks)plugins/woocommerce/tests/php/src/Internal/MCP/WooCommerceRestTransportTest.php(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs/features/mcp/category.json
🚧 Files skipped from review as they are similar to previous changes (7)
- plugins/woocommerce/includes/class-woocommerce.php
- docs/features/mcp/README.md
- plugins/woocommerce/changelog/add-mcp-to-woocommerce
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
- plugins/woocommerce/tests/php/src/Internal/MCP/WooCommerceRestTransportTest.php
- plugins/woocommerce/composer.json
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Features/FeaturesController.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.phpplugins/woocommerce/src/Internal/Features/FeaturesController.phpplugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.phpplugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.phpplugins/woocommerce/src/Internal/Abilities/REST/RestAbility.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
plugins/woocommerce/tests/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/woo-phpunit.mdc)
plugins/woocommerce/tests/**/*.php: Ensure test classes define public setUp() and tearDown() methods (not protected)
All PHPUnit test methods must be public, not protected
Add declare(strict_types=1); at the top of each test file
Test classes should extend WC_Unit_Test_Case
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
plugins/woocommerce/tests/php/src/**/*.php
📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Place PHP unit tests under tests/php/src/
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
plugins/woocommerce/tests/php/**
📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Store PHP test data and fixtures under tests/php/
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
🧠 Learnings (5)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.140Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.140Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.140Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/Features/FeaturesController.phpplugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
📚 Learning: 2025-08-21T07:39:42.952Z
Learnt from: NeosinneR
PR: woocommerce/woocommerce#60520
File: plugins/woocommerce/src/Internal/Admin/EmailPreview/EmailPreview.php:257-259
Timestamp: 2025-08-21T07:39:42.952Z
Learning: In WooCommerce email preview functionality (EmailPreview.php), rel attributes on links should be overwritten rather than preserved for safety reasons, and anchor links (#) can be treated the same as external links since they're not expected in email previews.
Applied to files:
plugins/woocommerce/src/Internal/Features/FeaturesController.php
📚 Learning: 2025-08-18T06:11:48.768Z
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-08-18T06:11:48.768Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : Ensure test classes define public setUp() and tearDown() methods (not protected)
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
📚 Learning: 2025-09-03T11:50:02.208Z
Learnt from: tpaksu
PR: woocommerce/woocommerce#60735
File: plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Fulfillments/class-wc-rest-fulfillments-v4-controller-tests.php:10-10
Timestamp: 2025-09-03T11:50:02.208Z
Learning: For REST API controller tests in plugins/woocommerce/tests/**/*.php, test classes should extend WC_REST_Unit_Test_Case instead of WC_Unit_Test_Case because REST routes need to be registered before tests run.
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
🧬 Code graph analysis (6)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (3)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(23-206)check_permission(50-52)plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
RestAbility(20-37)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)
plugins/woocommerce/src/Internal/Features/FeaturesController.php (1)
plugins/woocommerce/src/Enums/FeaturePluginCompatibility.php (1)
FeaturePluginCompatibility(9-42)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (6)
plugins/woocommerce/includes/class-woocommerce.php (3)
WooCommerce(45-1644)__construct(251-256)instance(160-165)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (3)
AbilitiesRegistry(18-49)__construct(23-25)get_abilities_ids(39-48)plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (2)
WooCommerceRestTransport(23-206)__construct(37-42)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_get_logger(2122-2152)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php (5)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (4)
MCPAdapterProvider(22-230)maybe_initialize(60-74)is_initialized(206-208)disable_mcp_validation(197-199)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (1)
AbilitiesRegistry(18-49)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php (1)
WC_Unit_Test_Case(21-487)plugins/woocommerce/tests/php/src/Internal/MCP/WooCommerceRestTransportTest.php (2)
setUp(27-45)tearDown(50-58)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php (1)
plugins/woocommerce/includes/class-woocommerce.php (1)
WooCommerce(45-1644)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
__construct(48-55)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(329-342)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
272-272: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php
33-33: Avoid unused parameters such as '$output'. (undefined)
(UnusedFormalParameter)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
183-183: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
- GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 9/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (3)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
50-52: Hardencheck_permission()against null requests.
check_permission()forwards whatever it receives straight intovalidate_request(), which has a strictWP_REST_Requesttype hint. If WordPress (or a future refactor) calls this without a request object, you’ll trigger a fatalTypeError. Add a guard that returns aWP_Errorwhen the argument is not aWP_REST_Requestbefore delegating. As per coding guidelines.public function check_permission( $request = null ) { - return $this->validate_request( $request ); + if ( ! ( $request instanceof \WP_REST_Request ) ) { + return new \WP_Error( + 'invalid_request_context', + __( 'Invalid request context for MCP.', 'woocommerce' ), + array( 'status' => 400 ) + ); + } + + return $this->validate_request( $request ); }plugins/woocommerce/src/Internal/Features/FeaturesController.php (1)
595-617: Escape URLs and harden the MCP description links.The new description prints filterable URLs directly into
<a>attributes and omits the usualrelsafeguards. A malicious plugin (or compromised filter) could inject ajavascript:payload into those links. Please wrap both URLs withesc_url(), switch the translated strings to their escaped variants, and addrel="noreferrer noopener"to the external link. As per coding guidelines.- $permalinks_url = admin_url( 'options-permalink.php' ); + $permalinks_url = esc_url( admin_url( 'options-permalink.php' ) ); $permalink_warning = sprintf( - '<br><br><strong>%s:</strong> %s <a href="%s">%s</a>', - __( 'Configuration Required', 'woocommerce' ), - __( 'WordPress permalinks must be set to anything other than "Plain" for MCP to work.', 'woocommerce' ), - $permalinks_url, - __( 'Configure Permalinks', 'woocommerce' ) + '<br><br><strong>%s:</strong> %s <a href="%s">%s</a>', + esc_html__( 'Configuration Required', 'woocommerce' ), + esc_html__( 'WordPress permalinks must be set to anything other than "Plain" for MCP to work.', 'woocommerce' ), + $permalinks_url, + esc_html__( 'Configure Permalinks', 'woocommerce' ) ); // Add documentation link to permalink warning. $documentation_link = sprintf( - ' <a href="%s" target="_blank">%s</a>', - 'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md', - __( 'Learn more', 'woocommerce' ) + ' <a href="%s" target="_blank" rel="noreferrer noopener">%s</a>', + esc_url( 'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md' ), + esc_html__( 'Learn more', 'woocommerce' ) ); @@ - $documentation_link = sprintf( - ' <a href="%s" target="_blank">%s</a>', - 'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md', - __( 'Learn more', 'woocommerce' ) + $documentation_link = sprintf( + ' <a href="%s" target="_blank" rel="noreferrer noopener">%s</a>', + esc_url( 'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md' ), + esc_html__( 'Learn more', 'woocommerce' ) );Also applies to: 613-617
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
221-229: Fix MCP route detection to prevent spoofingThe substring check on the raw
REQUEST_URIflags any REST request that merely contains/woocommerce/mcp, even in query parameters (e.g.GET /wp-json/wc/v3/orders?foo=/woocommerce/mcp). That erroneously treats unrelated REST calls as MCP traffic, so all downstream code that relies onis_mcp_request()(temporary admin context, abilities bridging, etc.) triggers on attacker-controlled requests. Please parse the actual REST route and require an exact/woocommerce/mcp(or sub-route) match before returningtrue.- // Get the request URI. - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; - - // Build the MCP endpoint path dynamically from constants. - $mcp_endpoint = '/' . self::MCP_NAMESPACE . '/' . self::MCP_ROUTE; - - // Check if the request is for the MCP endpoint. - return false !== strpos( $request_uri, $mcp_endpoint ); + $rest_route = ''; + + if ( isset( $_GET['rest_route'] ) ) { + $rest_route = ltrim( sanitize_text_field( wp_unslash( $_GET['rest_route'] ) ), '/' ); + } else { + $path = wp_parse_url( + isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '', + PHP_URL_PATH + ); + + if ( is_string( $path ) ) { + $rest_prefix = '/' . rest_get_url_prefix() . '/'; + + if ( str_starts_with( $path, $rest_prefix ) ) { + $rest_route = substr( $path, strlen( $rest_prefix ) ); + } + } + } + + if ( '' === $rest_route ) { + return false; + } + + $mcp_route = self::MCP_NAMESPACE . '/' . self::MCP_ROUTE; + + return $rest_route === $mcp_route || str_starts_with( $rest_route, $mcp_route . '/' );
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
Show resolved
Hide resolved
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
Show resolved
Hide resolved
Wrap two WP_Error message strings with __() translation function using 'woocommerce' text domain: - 'X-MCP-API-Key header required. Format: consumer_key:consumer_secret' - 'X-MCP-API-Key must be in format consumer_key:consumer_secret' Error codes and status arrays remain unchanged. Messages are now translatable for international WooCommerce users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Add original_abilities_registry property to store original AbilitiesRegistry - Capture original registry before replacing with mock in setUp() - Restore original registry in tearDown() and clear stored property - Ensures subsequent tests see the real AbilitiesRegistry instead of mock This prevents test interference where the mock registry from one test could affect other tests that depend on the real registry behavior. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Replace container->resolve() with container->get() since TestingContainer inherits get() method from RuntimeContainer but doesn't have resolve(). Tests now pass: 25 tests, 37 assertions successful. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (1)
183-205: Address unused parameter warning from static analysis.The
$controllerparameter is flagged as unused by static analysis. Since this method is a filter callback that must match the expected signature, document this requirement./** * Check REST ability permissions for HTTP method. * * @param bool $allowed Whether the operation is allowed. Default false. * @param string $method HTTP method (GET, POST, PUT, DELETE). - * @param object $controller REST controller instance. + * @param object $controller REST controller instance (unused, but required by filter signature). * @return bool Whether permission is granted. */ public function check_ability_permission( $allowed, $method, $controller ) { + // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Required by filter signature. // Only check permissions if we have MCP context.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php(1 hunks)plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧠 Learnings (2)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.140Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-09-17T16:16:48.140Z
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.140Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
Applied to files:
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
🧬 Code graph analysis (1)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (3)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (1)
__construct(48-55)plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php (1)
check_permission(329-342)plugins/woocommerce/includes/wc-core-functions.php (1)
wc_api_hash(1437-1439)
🪛 PHPMD (2.15.0)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php
183-183: Avoid unused parameters such as '$controller'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
- GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 9/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
🔇 Additional comments (7)
plugins/woocommerce/src/Internal/MCP/Transport/WooCommerceRestTransport.php (7)
50-52: Type safety issue with null parameter handling.The
check_permissionmethod accepts$request = nullbut directly passes it tovalidate_request()which expects a\WP_REST_Request. This will cause a TypeError under strict types ifnullis passed.This exact concern was raised in previous reviews. The method needs input validation before delegating to
validate_request().
60-75: LGTM! HTTPS enforcement properly implemented.The HTTPS check follows WordPress REST API best practices by validating security requirements early in the request lifecycle, and the filter allows necessary flexibility for development environments.
78-94: LGTM! Error messages are properly internationalized.The authentication error handling follows WordPress REST API patterns with proper WP_Error responses and internationalized messages.
118-119: LGTM! Secret handling follows security best practices.The code correctly trims the consumer key without mutating it through sanitization, which prevents potential credential corruption while maintaining security.
131-147: LGTM! Authentication follows secure practices.The implementation correctly:
- Uses consistent error messages to prevent credential enumeration
- Employs
hash_equals()for timing attack protection- Handles secrets appropriately without over-sanitization
152-163: LGTM! User context switching is secure.The code properly validates the user exists before switching context and returns the user ID on success, following WordPress security best practices.
183-205: Permission mapping aligns with WooCommerce API
The docblock forget_current_user_permissions()(lines 169–170) explicitly listsread,write, andread_writeas valid return values, and the switch incheck_ability_permission()correctly uses'write'(or'read_write') for all mutating HTTP methods—no changes needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
plugins/woocommerce/tests/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/woo-phpunit.mdc)
plugins/woocommerce/tests/**/*.php: Ensure test classes define public setUp() and tearDown() methods (not protected)
All PHPUnit test methods must be public, not protected
Add declare(strict_types=1); at the top of each test file
Test classes should extend WC_Unit_Test_Case
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
plugins/woocommerce/tests/php/src/**/*.php
📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Place PHP unit tests under tests/php/src/
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
plugins/woocommerce/tests/php/**
📄 CodeRabbit inference engine (plugins/woocommerce/CLAUDE.md)
Store PHP test data and fixtures under tests/php/
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
🧠 Learnings (5)
📓 Common learnings
Learnt from: budzanowski
PR: woocommerce/woocommerce#60901
File: plugins/woocommerce/src/Internal/Abilities/AbilitiesRestBridge.php:27-94
Timestamp: 2025-09-17T16:16:48.140Z
Learning: The WooCommerce MCP integration is intentionally designed for comprehensive store data manipulation, including orders (with PII) and write operations (create/update/delete). This is by design, not a security oversight, and relies on existing WooCommerce API key authentication and permission scoping for security.
📚 Learning: 2025-08-18T06:11:48.768Z
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-08-18T06:11:48.768Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : Ensure test classes define public setUp() and tearDown() methods (not protected)
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
📚 Learning: 2025-09-11T07:18:56.840Z
Learnt from: CR
PR: woocommerce/woocommerce#0
File: plugins/woocommerce/CLAUDE.md:0-0
Timestamp: 2025-09-11T07:18:56.840Z
Learning: Applies to plugins/woocommerce/tests/php/src/Internal/Admin/Suggestions/**/*.php : Place unit tests for payment extension suggestions in tests/php/src/Internal/Admin/Suggestions/
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
📚 Learning: 2025-08-18T06:11:48.768Z
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-08-18T06:11:48.768Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : Test classes should extend WC_Unit_Test_Case
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
📚 Learning: 2025-09-03T11:50:02.208Z
Learnt from: tpaksu
PR: woocommerce/woocommerce#60735
File: plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Fulfillments/class-wc-rest-fulfillments-v4-controller-tests.php:10-10
Timestamp: 2025-09-03T11:50:02.208Z
Learning: For REST API controller tests in plugins/woocommerce/tests/**/*.php, test classes should extend WC_REST_Unit_Test_Case instead of WC_Unit_Test_Case because REST routes need to be registered before tests run.
Applied to files:
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
🧬 Code graph analysis (1)
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php (6)
plugins/woocommerce/src/Internal/MCP/MCPAdapterProvider.php (4)
MCPAdapterProvider(22-230)maybe_initialize(60-74)is_initialized(206-208)disable_mcp_validation(197-199)plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php (1)
AbilitiesRegistry(18-49)plugins/woocommerce/src/Utilities/FeaturesUtil.php (1)
FeaturesUtil(14-102)plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php (1)
WC_Unit_Test_Case(21-487)plugins/woocommerce/tests/php/src/Internal/MCP/WooCommerceRestTransportTest.php (2)
setUp(27-45)tearDown(50-58)plugins/woocommerce/woocommerce.php (1)
wc_get_container(57-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
- GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 9/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 8.4 WP: latest (HPOS:off) [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
- GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.3] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
- GitHub Check: Lint - @woocommerce/plugin-woocommerce
- GitHub Check: build
plugins/woocommerce/tests/php/src/Internal/MCP/MCPAdapterProviderTest.php
Outdated
Show resolved
Hide resolved
Replace wp_actions manipulation with proper remove_action() calls to
prevent cross-test contamination:
- remove_action('rest_api_init', [sut, 'maybe_initialize'], 10)
- remove_action('mcp_adapter_init', [sut, 'initialize_mcp_server'], 10)
This ensures action callbacks are actually removed instead of just
unsetting tracking counters, preventing later tests from hitting
previous test instances when hooks fire.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
|
Hey @budzanowski , not sure the ideal place to ask this, so apologies for spamming an old ticket: Am I correct in assuming that the reason the MCP Adapter canonical plugin was added here as a composer dep is just because it wasn't (and as of writing still isnt) available on dot-org ? (related: WordPress/mcp-adapter#87 ) |
|
@justlevine correct. No worries about spamming. |
Add Model Context Protocol (MCP) integration for WooCommerce
This PR adds native support for the Model Context Protocol (MCP), enabling AI assistants like Claude Desktop to interact directly with WooCommerce stores through a standardized protocol. MCP integration exposes core store operations (products, orders) as discoverable tools that AI clients can use with proper authentication and permissions.
Key changes: Integrates WordPress Abilities API with WooCommerce REST endpoints, adds MCP server with custom WooCommerce transport layer, implements secure API key authentication, and includes feature flag configuration (
mcp_integration). The implementation follows a flexible architecture where abilities can be extended beyond REST bridging.How to test:
Connect using Claude Code:
claude mcp add woocommerce_mcp --env WP_API_URL=https://yoursite.com/wp-json/woocommerce/mcp --env CUSTOM_HEADERS='{"X-MCP-API-Key": "key:secret"}' -- npx -y @automattic/mcp-wordpress-remote@latestOr configure Claude Desktop with the MCP server details ( check the README.md from this PR )
Test product/order operations through the MCP interface, some examples
using woocommerce_mcp check order id 56using woocommerce_mcp add new product with "title", "description", and "price"using woocommerce_mcp remove product id 60Important notes:
Automated tests are in PR Add comprehensive unit tests for MCP integration #61072- merged.Documentation: Complete technical documentation, setup instructions, and security considerations are included in
docs/features/mcp/README.md.