You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three fixes from the third Codex review:
P1: PHPCS suppression on the new auth query
The new permissions-aware lookup in `authenticate_mcp_request` only
suppressed `WordPress.DB.DirectDatabaseQuery`. Every comparable query
in the codebase (RestApiKey's three lookup helpers, the analytics
controller, the customer-value test fixture) suppresses
`WordPress.DB.PreparedSQL.InterpolatedNotPrepared` alongside it,
since `{$wpdb->prefix}` interpolation triggers that sniff. Local
PHPCS happened to pass without it but the convention exists for a
reason — CI may be stricter or the rule may light up under future
upstream tightening. Match the convention.
P2: legacy X-MCP-API-Key bundles re-scoped
The previous commit dropped X-MCP-API-Key reading from
`extract_request_credential` "for consistency" with the auth
callback. Wrong move: the credential extractor's job is defensive
recognition, not authentication, and pre-migration `.mcpb` bundles
distributed against earlier plugin versions still send the header
to /wp-json/woocommerce/mcp (the deprecated WC core MCP endpoint,
which still authenticates the same WC API key when its feature flag
is on). Without the extractor reading the header,
`enforce_setup_key_route_scope` treats those legacy requests as
unrelated and lets them through — silently keeping the credential
usable on a route the merchant didn't intend.
Restore the X-MCP-API-Key branch in the extractor (defense-only,
not a supported auth path on the new endpoint) and pin the deny
behaviour with a regression test against /wp-json/woocommerce/mcp.
P3: README documents the new auth shape
The architecture paragraph still claimed "authenticates via an
X-MCP-API-Key header" after the Basic-auth migration. The setup
snippets, MCPB manifest, and `authenticate_mcp_request` all use
`WP_API_USERNAME` / `WP_API_PASSWORD` Basic auth now. Native HTTP
clients following the old paragraph would send the wrong shape and
get rejected. Updated to describe the actual contract (ck:cs Basic
auth from a WC key with read or read_write scope).
297 PHPUnit tests pass (was 296). PHPCS clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Everything ships through the single endpoint at `/wp-json/hey-woo/mcp`. There is no separate MCP server process to run — the plugin registers its abilities and stands up its own MCP server on `mcp_adapter_init` using the WordPress MCP adapter (vendored inside WooCommerce). The server bundles tools, resources, and prompts directly, and authenticates via an `X-MCP-API-Key` header backed by a standard WooCommerce REST API key.
185
+
Everything ships through the single endpoint at `/wp-json/hey-woo/mcp`. There is no separate MCP server process to run — the plugin registers its abilities and stands up its own MCP server on `mcp_adapter_init` using the WordPress MCP adapter (vendored inside WooCommerce). The server bundles tools, resources, and prompts directly, and authenticates via standard HTTP Basic auth — `ck_xxx` as the username, `cs_xxx` as the password, sourced from a WooCommerce REST API key with `read` or `read_write` scope.
Copy file name to clipboardExpand all lines: includes/class-plugin.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -514,7 +514,7 @@ public function authenticate_mcp_request( $request ) {
514
514
}
515
515
516
516
global$wpdb;
517
-
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- direct lookup against woocommerce_api_keys; no caching surface for auth.
517
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- direct lookup against woocommerce_api_keys; table prefix is trusted; no caching surface for auth.
518
518
$row = $wpdb->get_row(
519
519
$wpdb->prepare(
520
520
"SELECT key_id, user_id, consumer_secret, permissions FROM {$wpdb->prefix}woocommerce_api_keys WHERE consumer_key = %s",
* form via PHP_AUTH_USER/PW; raw HTTP_AUTHORIZATION; query-string
527
+
* `consumer_key` / `consumer_secret`) plus the legacy
528
+
* `X-MCP-API-Key` header.
529
+
*
530
+
* The legacy header is no longer accepted by our MCP auth callback,
531
+
* but pre-migration `.mcpb` bundles distributed against earlier
532
+
* plugin versions still send it — typically targeting the
533
+
* deprecated WC core MCP endpoint at `/wp-json/woocommerce/mcp`.
534
+
* Reading it here means `enforce_setup_key_route_scope()` can
535
+
* still recognise our credential on those legacy requests and
536
+
* deny them on every route except `/wp-json/hey-woo/mcp`. Drop
537
+
* the header here and a leaked legacy bundle silently keeps
538
+
* authenticating against the WC core endpoint.
528
539
*
529
540
* Returns '' if no credential is present (the request will be
530
541
* unauthenticated or rely on cookies/nonces instead, neither of
@@ -536,7 +547,16 @@ private static function extract_request_credential() {
536
547
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only inspection of an in-flight REST request.
537
548
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- credentials are byte-compared, not interpolated; sanitisation would corrupt them.
538
549
539
-
// 1a. PHP_AUTH_USER + PHP_AUTH_PW — the split form Apache/mod_php
550
+
// 1. Legacy X-MCP-API-Key header — kept for defense-in-depth so
551
+
// pre-migration bundles still hit the route-scope deny path on
552
+
// non-allowed routes. Not a supported auth path for the new
0 commit comments