Skip to content

Commit ca18653

Browse files
authored
Merge pull request #18 from woocommerce/feat/migrate-to-wordpress-mcp
Migrate to plugin-owned /wp-json/hey-woo/mcp endpoint
2 parents 9d9e702 + 7fe5924 commit ca18653

17 files changed

Lines changed: 1116 additions & 383 deletions

AGENTS.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Hey Woo
22

3-
The intelligence layer on top of WooCommerce core's MCP server: analytics skills, knowledge resources, prompts, and an AI-readiness scoring engine, registered as WordPress Abilities and exposed through the single MCP endpoint at `/wp-json/woocommerce/mcp`.
3+
The intelligence layer for an AI-ready WooCommerce store: analytics skills, knowledge resources, prompts, and an AI-readiness scoring engine, registered as WordPress Abilities and exposed through the plugin's own MCP endpoint at `/wp-json/hey-woo/mcp`.
44

55
WordPress plugin, PHP 7.4+, GPL-3.0-or-later. Public repo — never commit secrets, internal-only URLs, or anything not intended for public distribution.
66

@@ -27,7 +27,7 @@ WordPress plugin, PHP 7.4+, GPL-3.0-or-later. Public repo — never commit secre
2727
hey-woo/
2828
├── hey-woo.php # Plugin bootstrap (HPOS declaration, requirements, options migration)
2929
├── includes/
30-
│ ├── class-plugin.php # Singleton — wires hooks, MCP filter, mcp_adapter_init injection
30+
│ ├── class-plugin.php # Singleton — wires hooks, boots WP MCP adapter, registers own MCP server
3131
│ ├── abilities/ # One file per skill. wc-analytics/* (analytics tools),
3232
│ │ # hey-woo/* (store/readiness/search tools), wc-knowledge/*
3333
│ │ # (resources), wc-prompts/* (prompts), hey-woo-integrations/*
@@ -56,8 +56,8 @@ hey-woo/
5656

5757
```bash
5858
npx @wordpress/env start # boots WP 6.9 + WC + this plugin on http://localhost:8888
59-
# afterStart enables the MCP feature flag, activates WC + Hey Woo,
60-
# installs WC pages, sets a UK store address (London / GBP)
59+
# afterStart activates WC + Hey Woo, installs WC pages,
60+
# sets a UK store address (London / GBP)
6161
./bin/check # full pre-push gate (PHPCS, composer audit, PHPUnit, DCC)
6262
```
6363

@@ -72,9 +72,9 @@ npx @wordpress/env run cli -- wp eval-file /tmp/seed.php
7272

7373
These are validated decisions. **MUST NOT** relitigate without strong new signal.
7474

75-
- **No separate MCP server process.** Everything ships through WC core's MCP at `/wp-json/woocommerce/mcp`. This plugin registers WordPress Abilities (`wp_register_ability`) and opts them in via `woocommerce_mcp_include_ability` for tools and `mcp_adapter_init` (priority 20, after WC's 10) for resources/prompts. WC core's component registry exposes `register_resources()` / `register_prompts()` — that's the injection seam.
75+
- **Plugin-owned MCP server.** Hey Woo registers its own MCP server at `/wp-json/hey-woo/mcp` via the WordPress MCP adapter (vendored inside WooCommerce as `vendor/wordpress/mcp-adapter`). The plugin boots the adapter on `plugins_loaded` so the endpoint works regardless of WC's `mcp_integration` feature flag, then calls `$adapter->create_server('hey-woo', 'hey-woo', 'mcp', ...)` on `mcp_adapter_init` with a curated list of tools, resources, and prompts. Auth uses an `X-MCP-API-Key: ck:cs` header backed by a standard WC REST API key. The earlier "ride on WC's `woocommerce-mcp` server via `woocommerce_mcp_include_ability`" approach is gone — that endpoint is being deprecated upstream.
7676
- **Single Abilities API namespace.** Every analytics skill is at `wp-abilities/v1/abilities/wc-analytics/{skill}/run`. Earlier custom `hey-woo/v1/analytics/*` REST routes were folded into Abilities so MCP and direct callers hit one surface.
77-
- **Three plugin-owned ability prefixes**, declared in `Plugin::OWNED_ABILITY_NAMESPACES`: `wc-analytics/`, `hey-woo/`, `hey-woo-integrations/`. The WC auth scope filter trusts only routes under these prefixes — a Hey Woo consumer key cannot be replayed against abilities registered by other plugins. Adding a fourth prefix is a single-edit operation; the `include_wc_analytics_in_mcp` filter must be updated in lockstep.
77+
- **Three plugin-owned ability prefixes**, declared in `Plugin::OWNED_ABILITY_NAMESPACES`: `wc-analytics/`, `hey-woo/`, `hey-woo-integrations/`. The WC auth scope filter trusts only routes under these prefixes — a Hey Woo consumer key cannot be replayed against abilities registered by other plugins. Adding a fourth prefix is a single-edit operation; the `Plugin::mcp_tool_ability_ids()` curated list must be updated in lockstep.
7878
- **Aggregated-only privacy by default.** PII gate (`hey_woo_allow_customer_pii`) is off; merchants opt in only when chaining with email/CRM MCPs that need real addresses. `wc_string_to_bool` reads the option (not `(bool)``'no'` would otherwise be truthy).
7979
- **HPOS-compatible.** Declared via `FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true)`. SQL must read from HPOS tables (`wp_wc_orders_meta` for attribution) when available, falling back to `wp_postmeta` only when HPOS isn't enabled. The runtime branch lives in `AnalyticsController::get_order_meta_source()`.
8080
- **No background work.** No cron, no polling, no sync jobs. The plugin runs only when an MCP/REST request arrives. This is the contract that lets the perf doc say "lighter than loading WC Analytics a few times an hour."
@@ -103,7 +103,7 @@ The DCC step (`bin/check-dcc`) is gated — it auto-skips when `vendor-plugins/w
103103

104104
### MCP requires HTTPS by default
105105

106-
Local wp-env runs on plain HTTP. The mu-plugin at `tools/mu-plugins/dev-allow-insecure-transport.php` sets `woocommerce_mcp_allow_insecure_transport` for the dev environment only. Don't move that filter into the plugin proper — it's dev-only, deliberately.
106+
Local wp-env runs on plain HTTP. The Hey Woo MCP transport (`WP\MCP\Transport\HttpTransport`) does not enforce HTTPS, so curl-style local testing against `/wp-json/hey-woo/mcp` works without a TLS cert. Production stores should still front the endpoint with HTTPS.
107107

108108
### Two-step skill addition
109109

CONTRIBUTING.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ If you're just looking to install / use the plugin, the [README](./README.md) is
1919
## Architecture
2020

2121
```
22-
Plugin (PHP) WC core MCP server
22+
Plugin (PHP) Hey Woo MCP server
2323
───────────────────── ──────────────────────
24-
Abilities (wp-abilities/v1) ─────▶ /wp-json/woocommerce/mcp
25-
- wc-analytics/* (tools) tools (via woocommerce_mcp_include_ability filter)
26-
- hey-woo/* (tools)
27-
- wc-knowledge/* (resources) ─────▶ resources (via mcp_adapter_init injection)
28-
- wc-prompts/* (prompts) ─────▶ prompts (via mcp_adapter_init injection)
24+
Abilities (wp-abilities/v1) ─────▶ /wp-json/hey-woo/mcp
25+
- wc-analytics/* (tools) tools (curated via Plugin::mcp_tool_ability_ids())
26+
- hey-woo/* (tools) resources (passed in to create_server())
27+
- wc-knowledge/* (resources) prompts (passed in to create_server())
28+
- wc-prompts/* (prompts)
2929
Knowledge providers
3030
Scoring engine
3131
```
3232

33-
There is **no separate MCP server process**abilities register into the WC core MCP server, which is the single endpoint at `/wp-json/woocommerce/mcp`.
33+
There is **no separate MCP server process**Hey Woo registers its own MCP server via the WordPress MCP adapter (vendored inside WooCommerce as `vendor/wordpress/mcp-adapter`) and owns the single endpoint at `/wp-json/hey-woo/mcp`. The plugin boots the adapter on `plugins_loaded` so the endpoint works regardless of WC's `mcp_integration` feature flag, then calls `$adapter->create_server('hey-woo', 'hey-woo', 'mcp', ...)` on `mcp_adapter_init` with a curated list of tools, resources, and prompts.
3434

3535
**Analytics Skills** all use the WordPress Abilities API (`wp_register_ability()`, auto-exposed under `wp-abilities/v1/abilities/wc-analytics/{skill}/run`). This is the standard path for anything that exposes actions or tools to AI systems.
3636

@@ -43,7 +43,7 @@ There is **no separate MCP server process** — abilities register into the WC c
4343
| `includes/api/` | REST controllers for store, catalog, products, readiness. The non-analytics tool abilities delegate into these. |
4444
| `includes/knowledge/providers/` | Knowledge providers (store profile, catalog, products, policies) |
4545
| `includes/scoring/` | Scoring engine + 4 factors (product completeness, schema coverage, content quality, policy completeness) |
46-
| `includes/class-plugin.php` | Singleton. Registers the include filter + the `mcp_adapter_init` hook that injects resources/prompts into the WC MCP server's component registry. |
46+
| `includes/class-plugin.php` | Singleton. Boots the WP MCP adapter on `plugins_loaded` and registers the Hey Woo MCP server (with its tools, resources, prompts, and a Basic-auth callback that authenticates `ck_xxx:cs_xxx` against `wp_woocommerce_api_keys`) on `mcp_adapter_init`. |
4747
| `skills/` | Reference Claude Code / Codex skills (catalog-audit, product-content-generator, store-health-monitor) |
4848

4949
## Privacy rule
@@ -118,7 +118,7 @@ The high-level shape every skill follows:
118118
- Add the new class to `AbilitiesBootstrap::register_abilities()`
119119
- `require_once` it from `class-plugin.php`
120120

121-
3. **The `woocommerce_mcp_include_ability` filter** already whitelists the `wc-analytics/` prefix, so the ability auto-exposes at `/wp-json/woocommerce/mcp` as `wc-analytics-get-skill-name` — no extra wiring per skill.
121+
3. **Add the new ability ID to `Plugin::mcp_tool_ability_ids()`** in `class-plugin.php` so it's exposed as a tool on `/wp-json/hey-woo/mcp` — only top-level routing tools (`get-data`, `describe`, `confirm-large-range`) and curated `hey-woo/*` tools are exposed; analytics sub-skills stay registered in the Abilities API but hidden from the MCP tool list, since `wc-analytics/describe` reads their docs.
122122

123123
4. **Verify** — compare the endpoint output against direct SQL (or the WC Analytics REST API) for the same date range.
124124

README.md

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,23 @@ Same setup page, **Manual Setup** card. Pick your client from the dropdown — H
4040

4141
### Manual / scripted setup
4242

43-
If you'd rather configure everything yourself — for example to drop the admin page from your workflow, ship via WP-CLI, or wire a CI deploy — the manual flow is unchanged:
43+
If you'd rather configure everything yourself — for example to drop the admin page from your workflow, ship via WP-CLI, or wire a CI deploy — the manual flow is:
4444

45-
#### 1. Enable WooCommerce core MCP
45+
#### 1. Create an API key
4646

47-
```bash
48-
wp option update woocommerce_feature_mcp_integration_enabled yes
49-
```
50-
51-
(Or via the admin: **WooCommerce > Settings > Advanced > Features > MCP integration**.)
52-
53-
#### 2. Create an API key
54-
55-
In **WooCommerce > Settings > Advanced > REST API**, create a new key with Read or Read/Write permissions. Save the consumer key (`ck_...`) and consumer secret (`cs_...`). The MCP endpoint authenticates via an `X-MCP-API-Key: ck_...:cs_...` header; HTTPS is required by default. For local HTTP dev see the mu-plugin snippet in the [Local development](#local-development) section.
47+
In **WooCommerce > Settings > Advanced > REST API**, create a new key with Read or Read/Write permissions. Save the consumer key (`ck_...`) and consumer secret (`cs_...`). The MCP endpoint authenticates with the standard WooCommerce REST API key flow — `ck_...` as the username and `cs_...` as the password over HTTP Basic auth. HTTPS is required for production; HTTP works for local dev.
5648

57-
#### 3. Point your MCP client at the endpoint
49+
#### 2. Point your MCP client at the endpoint
5850

59-
The WooCommerce MCP docs recommend connecting through [`@automattic/mcp-wordpress-remote`](https://github.com/Automattic/mcp-wordpress-remote) — a lightweight local proxy that translates stdio-based MCP (what most clients speak) into HTTP requests to WordPress. This works across Claude Desktop, Claude Code, and any other MCP client.
51+
The recommended path is to connect through [`@automattic/mcp-wordpress-remote`](https://github.com/Automattic/mcp-wordpress-remote) — a lightweight local proxy that translates stdio-based MCP (what most clients speak) into HTTP requests to WordPress. This works across Claude Desktop, Claude Code, and any other MCP client.
6052

6153
**Claude Code** — one command:
6254

6355
```bash
6456
claude mcp add hey-woo \
65-
--env WP_API_URL=https://yourstore.com/wp-json/woocommerce/mcp \
66-
--env CUSTOM_HEADERS='{"X-MCP-API-Key": "ck_xxx:cs_xxx"}' \
57+
--env WP_API_URL=https://yourstore.com/wp-json/hey-woo/mcp \
58+
--env WP_API_USERNAME=ck_xxx \
59+
--env WP_API_PASSWORD=cs_xxx \
6760
-- npx -y @automattic/mcp-wordpress-remote@0.3.0
6861
```
6962

@@ -76,8 +69,9 @@ claude mcp add hey-woo \
7669
"command": "npx",
7770
"args": ["-y", "@automattic/mcp-wordpress-remote@0.3.0"],
7871
"env": {
79-
"WP_API_URL": "https://yourstore.com/wp-json/woocommerce/mcp",
80-
"CUSTOM_HEADERS": "{\"X-MCP-API-Key\": \"ck_xxx:cs_xxx\"}"
72+
"WP_API_URL": "https://yourstore.com/wp-json/hey-woo/mcp",
73+
"WP_API_USERNAME": "ck_xxx",
74+
"WP_API_PASSWORD": "cs_xxx"
8175
}
8276
}
8377
}
@@ -86,21 +80,7 @@ claude mcp add hey-woo \
8680

8781
Having trouble? See the [mcp-wordpress-remote troubleshooting guide](https://github.com/Automattic/mcp-wordpress-remote/blob/trunk/Docs/troubleshooting.md).
8882

89-
If your MCP client supports HTTP transport natively (some do, many don't), you can point it straight at the endpoint without the proxy:
90-
91-
```json
92-
{
93-
"mcpServers": {
94-
"hey-woo": {
95-
"type": "http",
96-
"url": "https://yourstore.com/wp-json/woocommerce/mcp",
97-
"headers": {
98-
"X-MCP-API-Key": "ck_xxx:cs_xxx"
99-
}
100-
}
101-
}
102-
}
103-
```
83+
If your MCP client supports HTTP transport natively (some do, many don't), you can point it straight at the endpoint without the proxy. Use HTTP Basic auth with the consumer key as the username and consumer secret as the password.
10484

10585
### Restart your client and talk to your store
10686

@@ -202,7 +182,7 @@ WooCommerce core already exposes basic product and order CRUD via MCP. This proj
202182
| **WooCommerce core MCP** | HTTP transport, auth, product/order CRUD tools | WooCommerce core team |
203183
| **Hey Woo plugin** | Analytics skills, knowledge resources, prompts, readiness scoring | This project |
204184

205-
Everything ships through the single endpoint at `/wp-json/woocommerce/mcp`. There is no separate MCP server process to run — the plugin registers its abilities, a filter widens the core server's inclusion rules to cover the `wc-analytics/*` and `hey-woo/*` namespaces, and resources/prompts are injected via the `mcp_adapter_init` action.
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.
206186

207187
---
208188

@@ -253,22 +233,14 @@ add_filter( 'hey_woo_enriched_product', function( $data, $product ) {
253233
# Start the WordPress + WooCommerce environment:
254234
npx @wordpress/env start
255235

256-
# Enable Woo core MCP:
257-
npx @wordpress/env run cli -- wp option update woocommerce_feature_mcp_integration_enabled yes
258-
259236
# Test the plugin's REST endpoints (still available for direct access):
260237
curl -u ck_xxx:cs_xxx http://localhost:8888/wp-json/hey-woo/v1/store/profile
261238
curl -u ck_xxx:cs_xxx http://localhost:8888/wp-json/hey-woo/v1/readiness/score
262239
curl -u ck_xxx:cs_xxx http://localhost:8888/wp-json/hey-woo/v1/products
263240

264-
# MCP requires HTTPS by default. For local HTTP, add a mu-plugin:
265-
echo '<?php add_filter( "woocommerce_mcp_allow_insecure_transport", "__return_true" );' \
266-
| npx @wordpress/env run cli -- bash -c "cat > /var/www/html/wp-content/mu-plugins/allow-http-mcp.php"
267-
268-
# Then test the endpoint:
269-
curl -X POST http://localhost:8888/wp-json/woocommerce/mcp \
241+
# Test the MCP endpoint (HTTP works for local dev — HTTPS only matters for production):
242+
curl -X POST -u ck_xxx:cs_xxx http://localhost:8888/wp-json/hey-woo/mcp \
270243
-H 'Content-Type: application/json' \
271-
-H 'X-MCP-API-Key: ck_xxx:cs_xxx' \
272244
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
273245
```
274246

docs/performance-and-hosting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Where it can get noisier: very large catalogues (10k+ SKUs), very large order hi
1212

1313
## How load is generated
1414

15-
Each question to Claude can fire one or more tool calls against `/wp-json/woocommerce/mcp`. Per uncached call:
15+
Each question to Claude can fire one or more tool calls against `/wp-json/hey-woo/mcp`. Per uncached call:
1616

1717
1. The plugin runs a direct SQL query against WooCommerce's analytics lookup tables (`wc_order_stats`, `wc_order_product_lookup`, `wc_customer_lookup`, etc.).
1818
2. The result is stored in a WordPress transient with a 1-hour TTL.

includes/abilities/class-abilities-bootstrap.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,17 @@ public static function register_abilities() {
8080
}
8181

8282
// Non-analytics tools (hey-woo/*) — store knowledge, readiness,
83-
// and suggestion helpers. Picked up by the woocommerce_mcp_include_ability
84-
// filter alongside wc-analytics/*.
83+
// and suggestion helpers. Exposed as MCP tools on
84+
// /wp-json/hey-woo/mcp via Plugin::mcp_tool_ability_ids().
8585
GetStoreProfileAbility::register();
8686
SearchProductsAbility::register();
8787
GetProductDetailsAbility::register();
8888
GetReadinessScoreAbility::register();
8989
GetRecommendationsAbility::register();
9090
SuggestImprovementsAbility::register();
9191

92-
// Resources (wc-knowledge/*) and prompts (wc-prompts/*) — wired into
93-
// the Woo core MCP server by Plugin::inject_mcp_components, not by
94-
// the tools include filter.
92+
// Resources (wc-knowledge/*) and prompts (wc-prompts/*) — passed
93+
// directly into create_server() by Plugin::register_mcp_server().
9594
StoreProfileAbility::register();
9695
CatalogSchemaAbility::register();
9796
StorePoliciesAbility::register();

includes/abilities/class-google-analytics-channels-ability.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525

2626
/**
2727
* Registers the GA4-channels prototype ability under the plugin-owned
28-
* `hey-woo-integrations/` namespace. The namespace is opted into Woo
29-
* core MCP via the `woocommerce_mcp_include_ability` filter in
30-
* Plugin::include_wc_analytics_in_mcp — using a plugin-owned prefix
31-
* (rather than the broader `integrations/`) so unrelated abilities
32-
* registered by other plugins don't get pulled into the Woo MCP tool list.
28+
* `hey-woo-integrations/` namespace. The ability is exposed on the Hey
29+
* Woo MCP server (`/wp-json/hey-woo/mcp`) by listing it in
30+
* Plugin::mcp_tool_ability_ids(). Using a plugin-owned prefix (rather
31+
* than the broader `integrations/`) keeps the curated tool list scoped
32+
* to abilities we own.
3333
*/
3434
class GoogleAnalyticsChannelsAbility {
3535

0 commit comments

Comments
 (0)