@hostinger/mcp 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/headless/SKILL.md +68 -70
- package/skills/headless/connect/SKILL.md +1 -6
- package/skills/headless/steps/01-check-state/SKILL.md +24 -8
- package/skills/headless/steps/02-refine/SKILL.md +91 -24
- package/skills/headless/steps/03-provision/SKILL.md +63 -8
- package/skills/headless/steps/03-provision/ecommerce/SKILL.md +11 -2
- package/skills/headless/steps/03-provision/wordpress/SKILL.md +164 -15
- package/skills/headless/steps/04-content-model/SKILL.md +95 -17
- package/skills/headless/steps/04-content-model/wordpress/SKILL.md +66 -21
- package/skills/headless/steps/04-content-model/wordpress/woocommerce.md +153 -0
- package/skills/headless/steps/05-design/SKILL.md +9 -0
- package/skills/headless/steps/06-frontend/SKILL.md +31 -2
- package/skills/headless/steps/07-validate/SKILL.md +4 -1
- package/skills/headless/steps/08-deploy/SKILL.md +11 -3
- package/skills/headless/steps/09-done/SKILL.md +10 -4
- package/src/core/runtime.js +15 -1
- package/src/core/runtime.ts +15 -1
- package/src/core/skills.js +49 -42
- package/src/core/skills.ts +49 -42
- package/src/core/tools/all.js +5 -1
- package/src/core/tools/all.ts +5 -1
- package/src/core/tools/hosting.js +5 -1
- package/src/core/tools/hosting.ts +5 -1
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
- package/types.d.ts +5 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: headless-step-provision
|
|
3
|
-
description: "Step 3 (provision) of the Hostinger Headless flow. Chooses the backend from brief.md (wordpress | ecommerce | webapp), then routes to the matching subfolder to provision it; writes site.json."
|
|
3
|
+
description: "Step 3 (provision) of the Hostinger Headless flow. Chooses the backend from brief.md (wordpress | ecommerce | webapp), then routes to the matching subfolder to provision it; writes site.json. WordPress + WooCommerce routes to the wordpress subfolder with the WooCommerce store turned on — it is not a separate backend."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# headless-step-provision
|
|
@@ -25,19 +25,74 @@ Read the brief's **Suggested backend** section:
|
|
|
25
25
|
| Suggested backend | Follow |
|
|
26
26
|
|---|---|
|
|
27
27
|
| WordPress | `wordpress/SKILL.md` |
|
|
28
|
+
| WordPress + WooCommerce | `wordpress/SKILL.md` **with the WooCommerce store on** (see below) |
|
|
28
29
|
| Ecommerce | `ecommerce/SKILL.md` |
|
|
29
30
|
| Webapp | `webapp/SKILL.md` |
|
|
30
31
|
|
|
31
32
|
If the section is missing or names something else, go back to **refine** — don't pick one here.
|
|
32
33
|
Tell the user the backend and its one-line reason before provisioning.
|
|
33
34
|
|
|
34
|
-
> **
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
>
|
|
35
|
+
> **WordPress + WooCommerce is the WordPress backend with a store — not a new backend.** Route it to
|
|
36
|
+
> `wordpress/SKILL.md` and follow that recipe's **optional WooCommerce tail** (install + activate the
|
|
37
|
+
> `woocommerce` plugin and verify the store endpoints; the catalog is seeded later through the per-site WP
|
|
38
|
+
> MCP's native `wc-*` abilities — no custom plugin, no consumer key). `site.json → backend` stays
|
|
39
|
+
> `"wordpress"`; the store is recorded under `site.json → woocommerce` (`enabled: true`). Everything else
|
|
40
|
+
> downstream that routes on
|
|
41
|
+
> `wordpress` (content-model push, deploy's `/cms` preservation, done's wp-admin login) then works
|
|
42
|
+
> unchanged.
|
|
43
|
+
|
|
44
|
+
> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce` and `webapp`.**
|
|
45
|
+
> `webapp` is a plain Node.js app host (an empty website — no CMS, no store, no `/cms`) for any app/SPA
|
|
46
|
+
> that brings its own code and data; it never installs WordPress. PocketBase is not available in this
|
|
47
|
+
> version — refine never proposes it, and there is no `pocketbase/` subfolder; if a brief somehow names
|
|
48
|
+
> it, stop and tell the user rather than improvising it. A **WordPress + Ecommerce** brief (two separate
|
|
49
|
+
> backends — a WordPress site **and** a Hostinger Ecommerce store) has no single combined `start-setup`
|
|
50
|
+
> type yet: stop, tell the user it isn't supported as one site, and offer either to continue with just
|
|
51
|
+
> WordPress or just Ecommerce (route to that one subfolder), **or**, if they want content and a store on
|
|
52
|
+
> **one** site, to switch the brief to **WordPress + WooCommerce** — the one-site path, which routes to
|
|
53
|
+
> `wordpress/SKILL.md` with the store on.
|
|
54
|
+
|
|
55
|
+
## Onboarding handoff — swap an existing empty headless shell (do this before creating anything)
|
|
56
|
+
|
|
57
|
+
Onboarding can create the website **before** you ever run, then paste the build prompt into this agent.
|
|
58
|
+
When it does, the domain already has an **empty headless shell**: a website set up with
|
|
59
|
+
`hosting_websites_start-setup { type: "headless_empty" }` — `website_type` is `headless`, but **nothing
|
|
60
|
+
is installed** (no WordPress at `/cms`, no store). It is the hosting slot only, waiting for a backend.
|
|
61
|
+
|
|
62
|
+
So provision has **two entry shapes**, and each subfolder's *detect* step picks between them:
|
|
63
|
+
|
|
64
|
+
- **An empty headless shell already exists for the domain → adopt it and *swap* it to the chosen
|
|
65
|
+
backend. Never call `start-setup` again, never create a second site.** The swap is backend-specific:
|
|
66
|
+
- **WordPress** → install WordPress **into the existing shell's `/cms`** with
|
|
67
|
+
`wordpress_installations_install { type: "headless" }` (it rewrites the root `.htaccess` and marks
|
|
68
|
+
the site headless-WordPress). Do **not** use `start-setup { type: "headless_wordpress" }` — that is
|
|
69
|
+
only for creating a fresh site.
|
|
70
|
+
- **Ecommerce** → the empty headless shell **is** the storefront host already (a `headless_ecommerce`
|
|
71
|
+
website installs no store engine either). Adopt it as the frontend host and go straight to creating
|
|
72
|
+
the store + custom sales channel.
|
|
73
|
+
- **No site exists for the domain at all → create it fresh, exactly as each subfolder defines today**
|
|
74
|
+
(`start-setup` with the backend's own `type`).
|
|
75
|
+
|
|
76
|
+
> **Webapp is not part of this handoff at all.** `webapp` is **not** reached on the usual onboarding
|
|
77
|
+
> flow — it is only triggered by experimental actions — so it **ignores headless instances entirely**:
|
|
78
|
+
> it never detects, adopts or swaps an empty headless shell, and provisions its own **Node.js app host**
|
|
79
|
+
> exactly as `webapp/SKILL.md` defines today. The swap below is **WordPress and Ecommerce only**.
|
|
80
|
+
|
|
81
|
+
**How to tell an empty shell from an already-provisioned backend** (both read `website_type: headless`):
|
|
82
|
+
|
|
83
|
+
1. Exact-match the domain row in `hosting_websites_list` — if there is **no** row, nothing exists → create fresh.
|
|
84
|
+
2. Within 24 h of onboarding, `hosting_websites_list-setups` still shows the setup `type`
|
|
85
|
+
(`headless_empty` = empty shell; `headless_wordpress` = WordPress already; `headless_ecommerce` = an
|
|
86
|
+
ecommerce host). Beyond 24 h the setup drops off — fall back to the backend probe:
|
|
87
|
+
3. `wordpress_installations_list { username, domain, ownership: "all" }` — a row with `directory: "cms"`
|
|
88
|
+
and `is_valid: true` means WordPress is **already** installed (adopt, don't swap); **no** such row on
|
|
89
|
+
a `headless` site means the shell is still empty and WordPress must be swapped in. A local `site.json`
|
|
90
|
+
with a recorded backend means a previous run already provisioned — trust reality over it, but it tells
|
|
91
|
+
you not to expect an empty shell.
|
|
92
|
+
|
|
93
|
+
This only changes *detect*. Everything after the swap (verify, `site.json`, the WooCommerce tail, the
|
|
94
|
+
background fire/join split) is identical to a fresh provision. A swap that installs WordPress is still
|
|
95
|
+
asynchronous — fire it, record `provision.in_progress`, and join at the barrier just like a fresh create.
|
|
41
96
|
|
|
42
97
|
## Step 2 — Fire (don't block)
|
|
43
98
|
|
|
@@ -48,13 +48,22 @@ All website-lifecycle calls go through the `hosting` connector (`execute`); all
|
|
|
48
48
|
Same lifecycle as `../wordpress/SKILL.md` Steps 1–2, but `type: "headless_ecommerce"`:
|
|
49
49
|
|
|
50
50
|
1. Detect: `hosting_websites_list` / `hosting_websites_list-setups` (exact-match the domain). `completed`
|
|
51
|
-
→ adopt. `running` → poll. `failed` → stop, point at hPanel.
|
|
52
|
-
|
|
51
|
+
→ adopt. `running` → poll. `failed` → stop, point at hPanel. If `website_type` is not `headless`
|
|
52
|
+
(plain WordPress, Node.js, …), stop and tell the user — don't overwrite another kind of site.
|
|
53
|
+
2. Create (only if **no** site exists): `hosting_websites_start-setup { order_id, type: "headless_ecommerce", domain }`
|
|
53
54
|
— **omit `domain`** for a temporary subdomain: the setup generates one itself (don't call a separate
|
|
54
55
|
subdomain op first — passing a generated free subdomain back as `domain` 422s). The response carries
|
|
55
56
|
the generated domain; if it differs from the workspace dir, move `~/.hostinger/<old>/` → `<new>/` and
|
|
56
57
|
update `brief.md`. Poll `list-setups` to `completed`. `website_type` will be `headless`.
|
|
57
58
|
|
|
59
|
+
> **Onboarding handoff — an empty headless shell needs no new website.** If the domain already has an
|
|
60
|
+
> empty headless shell (`website_type: headless`, setup `type: headless_empty`, nothing installed —
|
|
61
|
+
> typically created in onboarding), **adopt it as the storefront host and skip the create.** A
|
|
62
|
+
> `headless_ecommerce` website installs no store engine either, so an empty headless shell *is* the host
|
|
63
|
+
> you need — there is no backend to "swap" on the website side. Record its `username`/`order_id` and go
|
|
64
|
+
> straight to Step 2 (store + custom sales channel). Never call `start-setup` on a domain that already
|
|
65
|
+
> has a site.
|
|
66
|
+
|
|
58
67
|
> Don't expect `/cms` or any API on this domain — it serves the storefront you build (Step 6) and
|
|
59
68
|
> deploy (Step 8).
|
|
60
69
|
|
|
@@ -9,27 +9,43 @@ Create the headless WordPress backend for `<domain>` and prove it is ready. One
|
|
|
9
9
|
every headless site out identically: WordPress in `public_html/cms`, Hostinger AI Assistant + MCP
|
|
10
10
|
adapter, site marked `headless`. **Out:** `site.json`.
|
|
11
11
|
|
|
12
|
+
> **WordPress + WooCommerce?** This same recipe serves it. When the brief's **Suggested backend** is
|
|
13
|
+
> `**WordPress + WooCommerce**`, provision the WordPress site exactly as below, then run the **optional
|
|
14
|
+
> WooCommerce tail in Step 5** (install + activate the `woocommerce` plugin and verify the store
|
|
15
|
+
> endpoints; the catalog is seeded in content-model via the per-site WP MCP).
|
|
16
|
+
> WooCommerce is a plugin on this WordPress install, **not** a separate backend: `site.json → backend`
|
|
17
|
+
> stays `"wordpress"` and the store is recorded under `site.json → woocommerce`. For a plain WordPress
|
|
18
|
+
> brief, skip Step 5 entirely.
|
|
19
|
+
|
|
12
20
|
All operations below go through the Hostinger connector (`execute`).
|
|
13
21
|
|
|
14
|
-
> **Fire vs join (background provision).**
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
> the
|
|
19
|
-
>
|
|
22
|
+
> **Fire vs join (background provision).** Step 1, plus the queuing call — `hosting_websites_start-setup`
|
|
23
|
+
> for a fresh site (Step 2) **or** `wordpress_installations_install` to swap an empty headless shell
|
|
24
|
+
> (Step 2b) — are the **fire** half: once the job is queued, record `provision.in_progress` and return
|
|
25
|
+
> to do prepare work (see the router `../SKILL.md`). The readiness **poll** at the end of Step 2 / 2b,
|
|
26
|
+
> plus Steps 3–6, are the **join** half — run them when an instance-barrier step needs the backend (or
|
|
27
|
+
> immediately, if the agent can't interleave). An already-`completed` site with WordPress at `/cms`
|
|
28
|
+
> detected in Step 1 means provision is already done: record `provision.completed` and adopt it.
|
|
20
29
|
|
|
21
30
|
## Step 1 — Detect before you create (never provision twice)
|
|
22
31
|
|
|
23
32
|
1. `hosting_websites_list { "domain": "<domain>" }` — substring filter, so **exact-match** the row's
|
|
24
33
|
`domain`. If a row exists, note `username`, `order_id`, `website_type`.
|
|
25
|
-
2.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
2. **No exact row** → nothing exists on this domain → **create fresh** (Step 2).
|
|
35
|
+
3. **A row exists.** If `website_type` is **not** `headless`, stop and tell the user — this domain
|
|
36
|
+
already hosts a different kind of site (plain WordPress, Node.js, builder, …); never overwrite it.
|
|
37
|
+
If it **is** `headless`, work out whether WordPress is already installed or the shell is still empty
|
|
38
|
+
(see the router's *Onboarding handoff*): check `hosting_websites_list-setups { "domain": "<domain>" }`
|
|
39
|
+
(exact row) and `wordpress_installations_list { username, domain, ownership: "all" }` (exact row):
|
|
40
|
+
- Setup `running` → provisioning is underway → skip to Step 2's poll.
|
|
41
|
+
- Setup `failed` → tell the user and point them at hPanel; do **not** call start-setup again yourself.
|
|
42
|
+
- A WordPress install with `directory: "cms"` already present (or setup `type: headless_wordpress`)
|
|
43
|
+
→ **already provisioned** → skip to Step 3 (adopt it).
|
|
44
|
+
- A `headless` site with **no** `/cms` install (an **empty headless shell** — setup
|
|
45
|
+
`type: headless_empty`, typically from onboarding) → **swap it to WordPress** (Step 2b). Do **not**
|
|
46
|
+
call `start-setup` again — the website already exists; you only install WordPress into it.
|
|
47
|
+
|
|
48
|
+
## Step 2 — Create a fresh headless WordPress site (only when no site exists)
|
|
33
49
|
|
|
34
50
|
1. Pick the plan: `hosting_orders_list { "statuses": ["active"] }`. One active order → use it; several
|
|
35
51
|
→ ask the user which.
|
|
@@ -50,6 +66,38 @@ All operations below go through the Hostinger connector (`execute`).
|
|
|
50
66
|
3. Poll `hosting_websites_list-setups { "domain": "<domain>" }` every ~15 s until `completed`
|
|
51
67
|
(typically under a minute; allow a few minutes). `failed` → stop, point at hPanel.
|
|
52
68
|
|
|
69
|
+
## Step 2b — Swap an empty headless shell to WordPress (only when onboarding already made the shell)
|
|
70
|
+
|
|
71
|
+
The website already exists as an empty `headless` shell, so there is **no `start-setup`** here — you
|
|
72
|
+
install WordPress straight into its `/cms` directory. This is the onboarding handoff path.
|
|
73
|
+
|
|
74
|
+
1. Use the shell's `username` and `order_id` from Step 1's row — no new order pick, no new website.
|
|
75
|
+
2. Install WordPress into `/cms`, flagged headless:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "operation": "wordpress_installations_install",
|
|
79
|
+
"params": { "username": "<username>", "domain": "<domain>", "type": "headless",
|
|
80
|
+
"site_title": "<site title from brief.md>",
|
|
81
|
+
"credentials": { "login": "<admin login>", "email": "<account email>",
|
|
82
|
+
"password": "<strong generated password>" } } }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `type: "headless"` installs into `/cms` with the Hostinger headless MU plugin and marks the site
|
|
86
|
+
headless-WordPress; **`directory` is not allowed with it** (it is always `cms`). The target must
|
|
87
|
+
already be `headless` — the headless install rewrites the root `.htaccess`, so the API rejects it
|
|
88
|
+
with `422` on any non-headless site (which is why Step 1 confirms `website_type: headless` first).
|
|
89
|
+
- **You generate the admin password** (strong, 8–50 chars, mixed case + a digit) — never ask the user
|
|
90
|
+
for one and never print it. The user signs in later through a one-click login link (Step 6), so the
|
|
91
|
+
password only has to exist, not be remembered.
|
|
92
|
+
- A new database is created for the install unless you pass `database.name` of an existing one; the
|
|
93
|
+
generated database counts against the plan's limit.
|
|
94
|
+
3. The call is **asynchronous** — a success response only means the install job was **queued**. This is
|
|
95
|
+
the *fire*: record `provision.in_progress` and return to prepare work. Poll
|
|
96
|
+
`wordpress_installations_list { username, domain, ownership: "all" }` (exact row) at the join; when a
|
|
97
|
+
row with `directory: "cms"` and `is_valid: true` appears, WordPress is ready. Allow 1–2 minutes;
|
|
98
|
+
`wordpress_installations_list` can lag a few seconds behind the job finishing and return `[]` — retry
|
|
99
|
+
briefly before failing.
|
|
100
|
+
|
|
53
101
|
## Step 3 — Verify (all must pass)
|
|
54
102
|
|
|
55
103
|
| Check | How | Expect |
|
|
@@ -71,7 +119,78 @@ Notes:
|
|
|
71
119
|
|
|
72
120
|
If a check fails: retry once after ~30 s; if it still fails, stop and point the user at hPanel.
|
|
73
121
|
|
|
74
|
-
## Step 4 —
|
|
122
|
+
## Step 4 — SEO plugin (Yoast) *(every WordPress site — on by default)*
|
|
123
|
+
|
|
124
|
+
Every headless WordPress site gets **Yoast SEO** (`wordpress-seo`) installed and activated by default,
|
|
125
|
+
so per-page title/description/Open Graph/JSON-LD come from a maintained plugin's `yoast_head_json`
|
|
126
|
+
rather than being hand-generated from each item's content by the frontend. This is **join-half** work
|
|
127
|
+
(needs the live `/cms` install), so run it once `provision.status == "completed"`, the same way as the
|
|
128
|
+
WooCommerce tail. Tell the user in one line that SEO (Yoast) is being set up; if they explicitly don't
|
|
129
|
+
want it, skip this step and record `seo.enabled: false` — but the default is to install it.
|
|
130
|
+
|
|
131
|
+
1. **Install + activate Yoast** (management MCP, async — poll, never blind-retry):
|
|
132
|
+
- `wordpress_plugins_install { username, software: <install_id>, plugins: ["wordpress-seo"] }`
|
|
133
|
+
- Poll `wordpress_plugins_list-installed { username, software }` until `wordpress-seo` shows
|
|
134
|
+
`active` (install + activate can complete together), or activate it with
|
|
135
|
+
`wordpress_plugins_activate { username, software, plugin: "wordpress-seo" }`.
|
|
136
|
+
- **Known quirk:** the `hostinger-wordpress` MCP sometimes returns **HTTP 500 even though the job
|
|
137
|
+
succeeded**. Treat a 500 as "verify, don't assume": poll `list-installed` before retrying, and
|
|
138
|
+
never fire a second install blindly.
|
|
139
|
+
2. **Verify** `GET https://<domain>/cms/wp-json/` → `namespaces` once more, and that a sample post/page
|
|
140
|
+
response carries `yoast_head_json`. The per-item Yoast meta (focus title/description per post) is set
|
|
141
|
+
later in the **content-model push** — provision only installs + activates the plugin here. Don't fail
|
|
142
|
+
provision over SEO: if the install can't be confirmed after one retry, record `seo.enabled: false`,
|
|
143
|
+
tell the user, and let the frontend fall back to generating tags from content.
|
|
144
|
+
|
|
145
|
+
## Step 5 — WooCommerce store *(only for a `WordPress + WooCommerce` brief — skip otherwise)*
|
|
146
|
+
|
|
147
|
+
Run this only when the brief's **Suggested backend** is `**WordPress + WooCommerce**`. It turns the
|
|
148
|
+
headless WordPress site into a headless **store**: WooCommerce is the `woocommerce` plugin on this same
|
|
149
|
+
`/cms` install — **not a separate backend**. This is **join-half** work (needs the live instance), so run
|
|
150
|
+
it once `provision.status == "completed"`. The authoritative data-layer reference is
|
|
151
|
+
[`../../04-content-model/wordpress/woocommerce.md`](../../04-content-model/wordpress/woocommerce.md).
|
|
152
|
+
|
|
153
|
+
> **No custom plugin — the catalog is driven through the per-site WP MCP.** A custom plugin can't be
|
|
154
|
+
> installed into a headless `/cms` from a coding agent, so there is **no seed/config plugin and no REST
|
|
155
|
+
> consumer key in this flow.** Instead, the Hostinger AI Assistant (the per-site WP MCP, same JWT session
|
|
156
|
+
> content-model uses) exposes **native WooCommerce abilities** — `wc-products-*`, `wc-product-categories-*`,
|
|
157
|
+
> `wc-coupons-*`, `wc-orders-*`, `media-upload-from-url`, `wp-settings-*`. Provision just **installs
|
|
158
|
+
> WooCommerce and verifies the store endpoints**; the catalog + coupons are seeded in the content-model
|
|
159
|
+
> push through those abilities (see the cookbook).
|
|
160
|
+
|
|
161
|
+
1. **Install + activate WooCommerce** (management MCP, async — poll, never blind-retry):
|
|
162
|
+
- `wordpress_plugins_install { username, software: <install_id>, plugins: ["woocommerce"] }`
|
|
163
|
+
- Poll `wordpress_plugins_list-installed { username, software }` until `woocommerce` shows `active`
|
|
164
|
+
(install + activate can complete together), or activate it with
|
|
165
|
+
`wordpress_plugins_activate { username, software, plugin: "woocommerce" }`.
|
|
166
|
+
- Confirm with `wordpress_plugins_check-if-woo-commerce-is-installed { domain: "<domain>" }`. Allow a
|
|
167
|
+
moment for WooCommerce's installer to create its tables and the Shop/Cart/Checkout pages.
|
|
168
|
+
- **Known quirk:** the `hostinger-wordpress` MCP sometimes returns **HTTP 500 even though the job
|
|
169
|
+
succeeded** (seen on both `plugins_install` and `jwt-token`). Treat a 500 as "verify, don't assume":
|
|
170
|
+
poll `list-installed` / re-check before retrying, and never fire a second install blindly.
|
|
171
|
+
2. **Verify the store endpoints (public, no auth):**
|
|
172
|
+
|
|
173
|
+
| Check | How | Expect |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| WooCommerce active | `wordpress_plugins_check-if-woo-commerce-is-installed { domain }` | installed + active |
|
|
176
|
+
| Store API answers | `GET https://<domain>/cms/wp-json/wc/store/v1/products?per_page=1` | 200 (empty until content-model seeds — fine) |
|
|
177
|
+
| Both namespaces loaded | `GET https://<domain>/cms/wp-json/` → `namespaces` | contains `wc/store/v1` **and** `wc/v3` |
|
|
178
|
+
|
|
179
|
+
Pretty permalinks are on by default on a headless install, so the Store API resolves immediately; if a
|
|
180
|
+
store route ever 404s, the permalink structure is the thing to check.
|
|
181
|
+
|
|
182
|
+
> **Checkout needs owner setup — treat it like the Ecommerce payment gate.** A fresh WooCommerce install
|
|
183
|
+
> has **no payment gateway and no shipping zone enabled**, and that configuration is **not reachable from
|
|
184
|
+
> the headless tooling** (there is no payment/shipping MCP ability, and `wp-settings-update` only writes
|
|
185
|
+
> standard WP settings). So **checkout is unavailable until the owner enables a payment method + shipping
|
|
186
|
+
> in wp-admin** (hand them the one-click login in Step 6). Record `woocommerce.payment_enabled: false`
|
|
187
|
+
> accordingly. This is **not a build failure**: the catalog, product pages, client-side cart and coupon
|
|
188
|
+
> application all work headlessly — the downstream frontend renders a graceful disabled-checkout state and
|
|
189
|
+
> never calls `/checkout` while `payment_enabled` is `false` (see content-model + frontend). Never ask the
|
|
190
|
+
> user for payment credentials. Store currency and address (`woocommerce_*` options) likewise aren't
|
|
191
|
+
> settable headlessly — they follow the install default and the owner can change them in wp-admin.
|
|
192
|
+
|
|
193
|
+
## Step 6 — Record `site.json`
|
|
75
194
|
|
|
76
195
|
```json
|
|
77
196
|
{
|
|
@@ -84,6 +203,10 @@ If a check fails: retry once after ~30 s; if it still fails, stop and point the
|
|
|
84
203
|
"directory": "cms",
|
|
85
204
|
"admin_login": "…"
|
|
86
205
|
},
|
|
206
|
+
"seo": {
|
|
207
|
+
"plugin": "wordpress-seo",
|
|
208
|
+
"enabled": true
|
|
209
|
+
},
|
|
87
210
|
"urls": {
|
|
88
211
|
"site": "https://<domain>",
|
|
89
212
|
"cms": "https://<domain>/cms",
|
|
@@ -98,3 +221,29 @@ If a check fails: retry once after ~30 s; if it still fails, stop and point the
|
|
|
98
221
|
|
|
99
222
|
No passwords or tokens in `site.json`. When the user needs wp-admin, mint a one-click link with
|
|
100
223
|
`wordpress_login_create-links { username, software: <install_id> }` — never hand over a password.
|
|
224
|
+
|
|
225
|
+
**For a `WordPress + WooCommerce` brief, also add a `woocommerce` block and the store URLs** (the
|
|
226
|
+
`backend` field still stays `"wordpress"`):
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"woocommerce": {
|
|
231
|
+
"enabled": true,
|
|
232
|
+
"seeded_via": "per-site-wp-mcp",
|
|
233
|
+
"currency": "<currency_code from GET {store_api}/products or the install default>",
|
|
234
|
+
"payment_enabled": false,
|
|
235
|
+
"checkout_setup": "owner enables a payment method + shipping in wp-admin"
|
|
236
|
+
},
|
|
237
|
+
"urls": {
|
|
238
|
+
"store_api": "https://<domain>/cms/wp-json/wc/store/v1",
|
|
239
|
+
"wc_rest_api": "https://<domain>/cms/wp-json/wc/v3"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`rest_api` stays `wp/v2` (the WordPress content contract, for the blog/pages on the same site);
|
|
245
|
+
`store_api` is the public storefront surface (browse/cart/coupons/checkout, no auth). `wc_rest_api`
|
|
246
|
+
(`wc/v3`) is recorded only as the surface the **owner's own** integrations use with a consumer key they
|
|
247
|
+
generate in wp-admin — **this flow never creates or stores a key** (the catalog is seeded through the
|
|
248
|
+
per-site WP MCP's `wc-*` abilities; the storefront uses the public Store API). `payment_enabled` starts
|
|
249
|
+
`false` and flips to `true` only once a gateway + shipping are configured — no secrets in `site.json`.
|
|
@@ -53,15 +53,18 @@ and tone:
|
|
|
53
53
|
## Articles (optional)
|
|
54
54
|
> Generate <N> articles across the brief's categories (<list them>). For each: title, slug,
|
|
55
55
|
> body in the brief's tone, category, any relationship (e.g. destination), published date, SEO,
|
|
56
|
-
> and a featured image (
|
|
56
|
+
> and a featured image (see *Images* below — Hostinger AI generation or Unsplash stock) with
|
|
57
57
|
> alt text. Omit if the brief has no article-like content.
|
|
58
58
|
|
|
59
59
|
## Products (optional — required for a commerce brief)
|
|
60
60
|
> Skip for a non-commerce brief. For a commerce brief, generate the catalog content here (prepare
|
|
61
61
|
> work, no instance): for each product — name, slug, SKU, short + long description in the brief's
|
|
62
|
-
> tone, price, the collection it belongs to, variants if any, and a product image (
|
|
63
|
-
>
|
|
64
|
-
> the brief implies
|
|
62
|
+
> tone, price, the collection it belongs to, variants if any, and a product image (see *Images* —
|
|
63
|
+
> Hostinger AI generation or Unsplash stock) with alt text. Group products into the collections
|
|
64
|
+
> the brief implies, and generate any **coupons** the brief calls for (code, type, amount). The
|
|
65
|
+
> `ecommerce` push phase creates these on the store's sales channel; a **WordPress + WooCommerce** brief
|
|
66
|
+
> seeds them into WooCommerce instead (see `wordpress/SKILL.md` + `wordpress/woocommerce.md`) — there
|
|
67
|
+
> the **SKU is the idempotency key** and coupons + product categories are part of the catalog.
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
Guidance:
|
|
@@ -69,10 +72,12 @@ Guidance:
|
|
|
69
72
|
- **Fixed vs. prompt.** Title and Description are concrete values. Navigation, Pages and Articles are
|
|
70
73
|
prompts because their volume/wording is generated — keep them as prompts so the push phase (or a
|
|
71
74
|
rerun) can regenerate consistently.
|
|
72
|
-
- **Run the prompts now.** Generate the actual page copy
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
- **Run the prompts now.** Generate the actual page copy and article drafts during the wait — this is
|
|
76
|
+
the slowest work and the whole reason it runs in parallel. Images depend on the chosen source (see
|
|
77
|
+
*Images* below): Unsplash photos and your own generated files can be produced now; Hostinger AI
|
|
78
|
+
images only generate once the instance exists, so write their brief now and generate at the barrier.
|
|
79
|
+
Put any generated/own media in `~/.hostinger/<domain>/media/` and reference it by filename. Record
|
|
80
|
+
alt text for every image.
|
|
76
81
|
- **Optional sections.** Drop **Articles** if the brief has no article-like content. Drop **Products**
|
|
77
82
|
for a non-commerce brief; for a commerce brief, generate the product catalog here (names, prices,
|
|
78
83
|
descriptions, collections, images) — that's prepare work, and the `ecommerce` push phase then creates
|
|
@@ -81,12 +86,82 @@ Guidance:
|
|
|
81
86
|
provision wait, so never wait for it here. If `design.md` already exists when you source images,
|
|
82
87
|
follow its **Imagery** section; otherwise generate neutral, on-topic imagery from the brief.
|
|
83
88
|
|
|
84
|
-
### Images —
|
|
89
|
+
### Images — generate with Hostinger AI, or source stock from Unsplash
|
|
90
|
+
|
|
91
|
+
Every article (and every product) needs an image. There are two sources, and **you MUST offer the user
|
|
92
|
+
both and let them choose — never pick silently.** State the default for this backend in one line, name
|
|
93
|
+
the other option, and proceed on their answer (a skipped answer / "you decide" = use the default):
|
|
94
|
+
|
|
95
|
+
| Option | Tool | Backends | Needs the live instance? |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| **A — Hostinger AI image generation** *(default for WordPress / WooCommerce)* | `wordpress_ai-content_generate-images` | WordPress, WordPress + WooCommerce | **yes** — runs at the instance barrier |
|
|
98
|
+
| **B — Unsplash stock photos** *(default for Ecommerce / Webapp; the fallback everywhere)* | Unsplash API / web search | any | no — pure prepare work |
|
|
99
|
+
|
|
100
|
+
> **Do not silently fall back to stock on a WordPress site.** On WordPress / WooCommerce the default is
|
|
101
|
+
> **Hostinger AI (Option A)** — on-brand, generated from `design.md`. Using Unsplash on a WordPress build
|
|
102
|
+
> is only correct when **the user chose it**, or when **AI generation actually failed** (and then you say
|
|
103
|
+
> so). Taking stock because it is faster, synchronous, or avoids the async poll is a **defect**, not a
|
|
104
|
+
> shortcut: the async start → poll → upload-before-expiry flow (Option A below) is the expected work, not
|
|
105
|
+
> an optional extra. If you are tempted to skip it "for speed", stop and offer the user the choice instead.
|
|
106
|
+
|
|
107
|
+
If you can generate images yourself (some agents can), that's a third path: put the files in `media/`
|
|
108
|
+
and record alt text — no tool needed. **Record alt text for every image whichever source you use.**
|
|
109
|
+
|
|
110
|
+
**During the prepare wait, write an image brief for each image** into `content-model.md` no matter the
|
|
111
|
+
option (subject, `image_type`, aspect, and the alt text you expect). Option B can also *fetch* the
|
|
112
|
+
photo now (below); Option A only generates once the instance exists, so its briefs wait for the barrier.
|
|
113
|
+
|
|
114
|
+
#### Option A — Hostinger AI image generation (WordPress backends)
|
|
115
|
+
|
|
116
|
+
`wordpress_ai-content_generate-images` generates on-brand images for a live WordPress install, so it
|
|
117
|
+
runs **at the instance barrier** (after `provision.status == "completed"`), using the install from
|
|
118
|
+
`site.json` (`username`, and `wordpress.install_id` → the `software` path param). Follow `design.md`'s
|
|
119
|
+
**Imagery** section for look and palette. It is **asynchronous**:
|
|
120
|
+
|
|
121
|
+
1. **Start** one generation per image:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "operation": "wordpress_ai-content_generate-images",
|
|
125
|
+
"params": {
|
|
126
|
+
"username": "<site.json username>",
|
|
127
|
+
"software": "<site.json wordpress.install_id>",
|
|
128
|
+
"prompt": "<what the image shows; for a logo, the wordmark + mark to render>",
|
|
129
|
+
"image_type": "hero|background|inline|gallery|product|logo",
|
|
130
|
+
"num_of_images": 1,
|
|
131
|
+
"style": "<photographic style tokens from design.md; ignored for logo>",
|
|
132
|
+
"color_guidance": "<logo only — colour direction, not rendered as text>" } }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`prompt` (3–2500 chars) and `image_type` are required; `width`/`height` (128–2048) default per type
|
|
136
|
+
and are ignored for `logo`. Returns `202` with a `generation_id` and `status: in-progress`.
|
|
137
|
+
**Only one generation runs per install at a time** — a `409` means one is already running (and it
|
|
138
|
+
returns no id), so wait for it to finish before starting the next. The endpoint allows 10
|
|
139
|
+
requests/minute.
|
|
140
|
+
|
|
141
|
+
2. **Poll** `wordpress_ai-content_generation-result` with `{ username, software, generation_id }`
|
|
142
|
+
every few seconds until `status` is `completed` (or `failed`). A completed result carries
|
|
143
|
+
`result.images[]` — each with a **temporary** `url`, plus `width`, `height`, `content_type` — and
|
|
144
|
+
`result.expires_at`.
|
|
145
|
+
|
|
146
|
+
3. **Use the images before they expire.** The URLs are short-lived, so in the push phase hand each
|
|
147
|
+
`url` straight to WordPress with `media-upload-from-url`, which downloads and stores WP's own copy
|
|
148
|
+
(see the wordpress push `SKILL.md`). Don't persist the temporary URL as the live image. There is no
|
|
149
|
+
photographer to credit — leave `caption` empty or a short on-brand one. Record under the
|
|
150
|
+
article/product in `content-model.md`:
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
Image: Hostinger AI (image_type: <type>, generation_id: <uuid>)
|
|
154
|
+
Image alt: <what the image shows>
|
|
155
|
+
Image file: <site>-<subject>.jpg
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
On `failed`, retry once with a tightened prompt; if it fails again, fall back to Option B and note
|
|
159
|
+
the gap. Never block the whole push on a single image.
|
|
160
|
+
|
|
161
|
+
#### Option B — stock photos from Unsplash (any backend, no instance)
|
|
85
162
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
needs no live backend: you only pick the photos and record their URLs here. The push phase uploads
|
|
89
|
-
them.
|
|
163
|
+
This is still prepare work and needs no live backend: you pick the photos and record their URLs here,
|
|
164
|
+
and the push phase uploads them.
|
|
90
165
|
|
|
91
166
|
1. **Find candidates.** Use one photo per article, matching its subject and destination.
|
|
92
167
|
- With an `UNSPLASH_ACCESS_KEY` in the environment, use the official API:
|
|
@@ -134,13 +209,16 @@ token) to create the menu, pages, posts, categories and media:
|
|
|
134
209
|
| Suggested backend | Follow |
|
|
135
210
|
|---|---|
|
|
136
211
|
| WordPress | `wordpress/SKILL.md` |
|
|
212
|
+
| WordPress + WooCommerce | `wordpress/SKILL.md` — pushes WP content **and** seeds the WooCommerce catalog (its WooCommerce section + `wordpress/woocommerce.md`) |
|
|
137
213
|
| Ecommerce | `ecommerce/SKILL.md` |
|
|
138
214
|
| Webapp | — (no push; skip to frontend) |
|
|
139
215
|
|
|
140
|
-
> **Supported backends: `wordpress
|
|
141
|
-
> **skipping this whole stage** (it has no content backend; see the note
|
|
142
|
-
>
|
|
143
|
-
>
|
|
216
|
+
> **Supported backends: `wordpress` (optionally with a WooCommerce store), `ecommerce`, and `webapp`** —
|
|
217
|
+
> where `webapp` is "supported" by **skipping this whole stage** (it has no content backend; see the note
|
|
218
|
+
> below). A **WordPress + WooCommerce** brief routes to `wordpress/SKILL.md`, which pushes the WP content
|
|
219
|
+
> **and** seeds the WooCommerce catalog in the same push. PocketBase is not available in this version.
|
|
220
|
+
> Push to whichever single backend provision set up (see the provision router) — don't try to push to two
|
|
221
|
+
> backends here.
|
|
144
222
|
>
|
|
145
223
|
> **Webapp has no content backend.** There is no CMS or store to seed, so a `webapp` brief **skips this
|
|
146
224
|
> whole stage** — both prepare and push. The app owns its own data (its own API or database). Leave
|
|
@@ -29,9 +29,17 @@ authenticated; the admin user context (`wp_set_current_user`) exists only *insid
|
|
|
29
29
|
| **Per-site WP MCP** | `urls.wp_mcp` (`mcp_url`) | **all writes** — create content through its ability tools |
|
|
30
30
|
| **WP REST API** `wp/v2` | `urls.rest_api` | **public reads only** (no auth needed) — what the Step 6 frontend consumes; do not write to it with the JWT |
|
|
31
31
|
|
|
32
|
-
> **The WordPress content model is posts, pages and categories** (plus media). For **SEO**,
|
|
33
|
-
>
|
|
34
|
-
> item's `yoast_head_json
|
|
32
|
+
> **The WordPress content model is posts, pages and categories** (plus media). For **SEO**, **Yoast**
|
|
33
|
+
> is already installed + activated at provision (every headless WordPress site — see provision Step 4),
|
|
34
|
+
> so meta + schema come from a maintained plugin and the frontend reads each item's `yoast_head_json`.
|
|
35
|
+
> This push only sets per-item Yoast meta (Step 4.7 below).
|
|
36
|
+
|
|
37
|
+
> **A store on this site? See [`woocommerce.md`](woocommerce.md).** When the brief's **Suggested
|
|
38
|
+
> backend** is `**WordPress + WooCommerce**` (`site.json → woocommerce.enabled: true`), this push also
|
|
39
|
+
> seeds the WooCommerce catalog — products, categories and coupons — through the **same per-site WP MCP
|
|
40
|
+
> session**, which exposes native `wc-products-*`, `wc-product-categories-*` and `wc-coupons-*` abilities
|
|
41
|
+
> (no custom plugin, no consumer key). Do the WordPress content below as normal, then run the **WooCommerce
|
|
42
|
+
> catalog** section before you hand off.
|
|
35
43
|
|
|
36
44
|
## Step 1 — Open the authenticated MCP session
|
|
37
45
|
|
|
@@ -76,7 +84,10 @@ Seen on a live install (Hostinger AI Assistant, discovery mode): the meta-tools
|
|
|
76
84
|
`hostinger-ai-execute-ability { name, parameters }`. The abilities are named
|
|
77
85
|
`hostinger-ai/<thing>-<action>`, e.g. `posts-create`, `posts-update`, `media-upload-from-url`,
|
|
78
86
|
`media-delete`. Call `get-ability-info` for each schema before using it. Results come back as
|
|
79
|
-
`{ ability, result: { data, … } }` for lists and `{ ability, result: {…} }` for single objects
|
|
87
|
+
`{ ability, result: { data, … } }` for lists and `{ ability, result: {…} }` for single objects — so a
|
|
88
|
+
create answers **`200`** (not `201`) and the new object's `id` is **inside** `result` (`result.id`, or
|
|
89
|
+
`result.data.id`), never at the top level. A missing top-level `id` is not a failure; read it from
|
|
90
|
+
`result`.
|
|
80
91
|
|
|
81
92
|
## Step 3 — Idempotency rule (makes resume safe)
|
|
82
93
|
|
|
@@ -96,29 +107,41 @@ reuse its `id`; empty → create. Capture each returned `id` and record the id m
|
|
|
96
107
|
1. **Site Title & Description** (the helper's global part) → the Settings ability.
|
|
97
108
|
2. **Categories / tags** — one per term in the helper → Categories / Tags abilities. Keep `name → id`.
|
|
98
109
|
3. **Media.** Upload every image in the helper, set `alt_text`, and keep `filename → id`. See
|
|
99
|
-
*Media upload* below for
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
*Media upload* below for all sources: a Hostinger-AI image URL, an Unsplash URL, or a generated
|
|
111
|
+
file in `media/`. For a Hostinger-AI image, generate it now (its URL is temporary — see the
|
|
112
|
+
content-model `SKILL.md`, *Images* Option A) and upload straight away, before `expires_at`.
|
|
113
|
+
4. **Pages** — each static/index page → Pages ability. Pass `title`, `content` and `excerpt` as
|
|
114
|
+
**`{ "raw": "…" }` objects** (see the note below), plus `slug`, `status: publish`. Keep `slug → id`.
|
|
115
|
+
5. **Posts (articles)** — each → Posts ability: `title`, `content` and `excerpt` as **`{ "raw": "…" }`**
|
|
116
|
+
(note below), plus `slug`, `status: publish`, `categories: [id]`, `tags: [id…]`,
|
|
117
|
+
`featured_media: <id>`, `date`. (The plugin block-converts post content on insert via
|
|
118
|
+
`rest_pre_insert_post`, so HTML bodies in `content.raw` are fine.)
|
|
119
|
+
|
|
120
|
+
> **Create payload gotcha (`pages-create` / `posts-create`).** These abilities take `title`,
|
|
121
|
+
> `content` and `excerpt` as WordPress's core **`{ "raw": "…" }` objects**, not bare strings. Pass a
|
|
122
|
+
> bare string and the field is silently dropped — the create still answers `200`, so you get a
|
|
123
|
+
> *published-but-empty* page or post with nothing to key off. Always send `{ "raw": "…" }` and
|
|
124
|
+
> read the new `id` from `result` (see Step 2). Confirm the exact field shape with
|
|
125
|
+
> `get-ability-info` if a schema differs.
|
|
105
126
|
6. **Navigation** — Menus / Navigation abilities: create the menu, then an item per nav entry
|
|
106
127
|
referencing a page `id` (or a custom URL for listing routes). **Headless note:** the Step 6
|
|
107
128
|
frontend renders nav from `content-model.md`, so the WP menu is for owners editing in wp-admin, not
|
|
108
129
|
what the live site reads — create it, but it isn't the source of truth.
|
|
109
|
-
7. **SEO** —
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
7. **SEO** — **Yoast SEO** (`wordpress-seo`) is already installed + activated at provision (Step 4 of
|
|
131
|
+
the WordPress provision recipe), so it generates meta + schema and the frontend reads
|
|
132
|
+
`yoast_head_json` per post/page. Here, just set each post's Yoast title/description if an ability
|
|
133
|
+
exposes it; otherwise the excerpt + title are a fine fallback. Don't fail the push over SEO fields.
|
|
134
|
+
If `site.json → seo.enabled` is `false` (the owner declined Yoast, or provision couldn't confirm the
|
|
135
|
+
install), skip this and let the frontend generate tags from content — don't try to install the
|
|
136
|
+
plugin from the content stage.
|
|
116
137
|
|
|
117
138
|
### Media upload
|
|
118
139
|
|
|
119
|
-
**From a URL (Unsplash or any direct file URL).** Use
|
|
120
|
-
WordPress downloads the file and stores its own copy, so the
|
|
121
|
-
staying up
|
|
140
|
+
**From a URL (a Hostinger-AI image, Unsplash, or any direct file URL).** Use
|
|
141
|
+
`hostinger-ai/media-upload-from-url`. WordPress downloads the file and stores its own copy, so the
|
|
142
|
+
site never depends on the external URL staying up — which is exactly why a **Hostinger-AI** image
|
|
143
|
+
(whose `url` from `wordpress_ai-content_generation-result` is temporary) must be uploaded here, while
|
|
144
|
+
it is still valid; drop the Unsplash-only `caption`/`description` for AI images:
|
|
122
145
|
|
|
123
146
|
```json
|
|
124
147
|
{ "name": "hostinger-ai/media-upload-from-url",
|
|
@@ -171,8 +194,30 @@ duplicates with `media-delete { id, force: true }`.
|
|
|
171
194
|
→ id) so the frontend data phase and any re-run resolve references directly. Include one row per
|
|
172
195
|
media item: filename, credit and source page, the post it's featured on, and its id.
|
|
173
196
|
|
|
197
|
+
## WooCommerce catalog *(only for a `WordPress + WooCommerce` brief — skip otherwise)*
|
|
198
|
+
|
|
199
|
+
When `site.json → woocommerce.enabled` is `true`, also seed the store **in this same authenticated MCP
|
|
200
|
+
session**, using WooCommerce abilities the Assistant exposes alongside the content ones. Full recipe (incl.
|
|
201
|
+
the storefront Store API and the checkout gate): [`woocommerce.md`](woocommerce.md). In short:
|
|
202
|
+
|
|
203
|
+
1. **Product categories** — `wc-product-categories-list` (reuse by slug/name) else
|
|
204
|
+
`wc-product-categories-create { name, slug }`. Keep `name → id`.
|
|
205
|
+
2. **Products** — **idempotent by SKU**: `wc-products-search { sku }` → reuse, else
|
|
206
|
+
`wc-products-create { name, type:"simple", sku, regular_price:"<decimal>", description, status:"publish",
|
|
207
|
+
categories:[{id}] }`. For images, `media-upload-from-url` then `wc-products-update { id, images:[{id}] }`.
|
|
208
|
+
3. **Coupons** — **idempotent by code**: `wc-coupons-search { code }` → reuse, else
|
|
209
|
+
`wc-coupons-create { code, discount_type, amount }`.
|
|
210
|
+
4. **Verify over the public Store API** (no auth): `GET {urls.store_api}/products?per_page=100` count +
|
|
211
|
+
`prices` (minor units), `GET {urls.store_api}/products/categories`. Record the SKU→id map, category ids
|
|
212
|
+
and coupon codes into `content-model.md` under `## Created in WooCommerce`.
|
|
213
|
+
|
|
214
|
+
> **Don't seed or test checkout here.** A fresh WooCommerce install has no payment gateway/shipping and
|
|
215
|
+
> that config isn't reachable headlessly (`site.json → woocommerce.payment_enabled` is `false` — the owner
|
|
216
|
+
> finishes it in wp-admin). Seeding + verifying the catalog over the Store API is the whole job; never call
|
|
217
|
+
> `/checkout` or block on it. See the cookbook §4.
|
|
218
|
+
|
|
174
219
|
## Step 6 — Hand off
|
|
175
220
|
|
|
176
221
|
Update `state.json` (`provision` stays `completed`; advance `stage` toward the frontend **data**
|
|
177
222
|
phase) with a fresh `updated_at`. The backend now holds the content; Step 6's data wiring points the
|
|
178
|
-
frontend at `urls.rest_api
|
|
223
|
+
frontend at `urls.rest_api` (and, for a store, `urls.store_api`).
|