webcake-storefront-mcp 1.11.0 → 1.12.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/dist/api.js +42 -5
- package/dist/changelog.json +14 -14
- package/dist/tools/catalog-write.js +80 -0
- package/dist/tools/site-style.js +16 -37
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -76,11 +76,12 @@ export class WebcakeCmsApi {
|
|
|
76
76
|
createSite(params) {
|
|
77
77
|
return this.request("POST", `/api/v1/dashboard/site/create`, { body: params, timeout: 60000 });
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
/** Create a NEW site from a marketplace TEMPLATE by its theme id — the dedicated
|
|
80
|
+
* "use this template" API. Clones the template's pages, global sections, cart, popups,
|
|
81
|
+
* styles and fonts into a fresh account-owned site. Body: { id: <theme_id>, name, slug }.
|
|
82
|
+
* Returns { data: { site: { id, ... } } }. (403 if the free 4-site quota is reached, prod.) */
|
|
83
|
+
importStoreToTheme(params) {
|
|
84
|
+
return this.request("POST", `/api/v1/dashboard/site/import_store_to_theme`, { body: params, timeout: 120000 });
|
|
84
85
|
}
|
|
85
86
|
getSiteInfo() {
|
|
86
87
|
return this.request("GET", `/api/v1/site/${this.siteId}/`);
|
|
@@ -333,6 +334,27 @@ export class WebcakeCmsApi {
|
|
|
333
334
|
timeout: 60000,
|
|
334
335
|
});
|
|
335
336
|
}
|
|
337
|
+
/** Update a product. Body wraps fields in `product_params` and identifies the product by
|
|
338
|
+
* product_params.product_id. */
|
|
339
|
+
updateProduct(productParams) {
|
|
340
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/products/update`, {
|
|
341
|
+
body: { site_id: this.siteId, product_params: productParams },
|
|
342
|
+
timeout: 60000,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/** Delete products by id. Body: { site_id, ids }. */
|
|
346
|
+
removeProducts(ids) {
|
|
347
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/products/remove`, {
|
|
348
|
+
body: { site_id: this.siteId, ids },
|
|
349
|
+
timeout: 60000,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/** Publish/unpublish products. Body: { site_id, data: [{ product_id, is_published }] }. */
|
|
353
|
+
setProductsPublished(data) {
|
|
354
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/products/published`, {
|
|
355
|
+
body: { site_id: this.siteId, data },
|
|
356
|
+
});
|
|
357
|
+
}
|
|
336
358
|
/** Create a product category. Command-based: pass a `commands` array whose entries each
|
|
337
359
|
* carry a caller-generated `data.id` (the new category id). Response is generic. */
|
|
338
360
|
createProductCategory(commands) {
|
|
@@ -341,6 +363,21 @@ export class WebcakeCmsApi {
|
|
|
341
363
|
timeout: 60000,
|
|
342
364
|
});
|
|
343
365
|
}
|
|
366
|
+
/** Update a product category — command-based (name_category / image_category /
|
|
367
|
+
* multi_description / set_category_visible), each command's data.id = the category id. */
|
|
368
|
+
updateProductCategory(commands) {
|
|
369
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/categories/update`, {
|
|
370
|
+
body: { site_id: this.siteId, commands },
|
|
371
|
+
timeout: 60000,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/** Delete product categories — command bulk_delete_category with data.ids. */
|
|
375
|
+
deleteProductCategory(commands) {
|
|
376
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/categories/delete`, {
|
|
377
|
+
body: { site_id: this.siteId, commands },
|
|
378
|
+
timeout: 60000,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
344
381
|
// ── Orders ──
|
|
345
382
|
listOrders(query) {
|
|
346
383
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/orders/all`, { query });
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.12.0",
|
|
4
|
+
"d": "24/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New update_product tool updates an existing product's name, description, images, category_ids, is_published flag, or variations (price/stock/SKU per…",
|
|
7
|
+
"vi": "Tool mới update_product cập nhật name, description, images, category_ids, cờ is_published hoặc variations (giá/tồn kho/SKU theo từng biến thể) của…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.11.1",
|
|
11
|
+
"d": "24/06/2026",
|
|
12
|
+
"type": "Fixed",
|
|
13
|
+
"en": "create_site_from_template now calls the dedicated import_store_to_theme API instead of the generic site-duplicate endpoint, correctly cloning the…",
|
|
14
|
+
"vi": "create_site_from_template nay gọi đúng API import_store_to_theme thay vì endpoint nhân bản site thông thường, giúp clone đầy đủ các trang, global…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.11.0",
|
|
4
18
|
"d": "24/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "New list_automations tool returns each automation's id, name, status, and trigger info so agents can find the automation_id required by send_mail or…",
|
|
28
42
|
"vi": "Tool mới list_automations trả về id, name, status và thông tin trigger của từng automation, giúp agent tìm được automation_id cần truyền vào…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.7.0",
|
|
32
|
-
"d": "23/06/2026",
|
|
33
|
-
"type": "Changed",
|
|
34
|
-
"en": "The web guide landing page (served via the serve command) is reframed to lead on full-storefront creation: the hero, meta description, \"What you…",
|
|
35
|
-
"vi": "Trang web guide (phục vụ qua lệnh serve) được viết lại để làm nổi bật việc xây dựng toàn bộ cửa hàng: hero, meta description, thư viện \"Bạn dựng…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.6.0",
|
|
39
|
-
"d": "23/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "get_element now returns an attributes field with a curated per-element reference covering the meaningful specials, config, events, and bindings keys…",
|
|
42
|
-
"vi": "get_element nay trả về trường attributes chứa tài liệu tham chiếu theo từng loại element, bao gồm các key có ý nghĩa của specials, config, events và…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -126,4 +126,84 @@ Returns the new category id — pass it to create_product's category_ids. Image
|
|
|
126
126
|
await api.createBlogCategory(commands);
|
|
127
127
|
return { success: true, category_id: id, name };
|
|
128
128
|
}));
|
|
129
|
+
// ── Edits: products & product categories ─────────────────────────────────────
|
|
130
|
+
server.tool("update_product", `Update an existing product. Pass product_id + only the fields to change. To change
|
|
131
|
+
price/stock, pass variations (get_product first to see the existing variation shape).
|
|
132
|
+
Images must be hosted CDN urls (search_images cdn_url / upload_images).`, {
|
|
133
|
+
product_id: z.string().describe("Product id to update"),
|
|
134
|
+
name: z.string().optional().describe("New name"),
|
|
135
|
+
description: z.string().optional().describe("New description (HTML allowed)"),
|
|
136
|
+
images: z.array(z.string()).optional().describe("New hosted image URLs; first becomes the thumbnail"),
|
|
137
|
+
category_ids: z.array(z.string()).optional().describe("Replace the product's categories"),
|
|
138
|
+
is_published: z.boolean().optional().describe("Publish/unpublish the product"),
|
|
139
|
+
variations: z.array(variationSpec).optional().describe("Replace variations (price/stock/SKU per variant). Reuse existing custom_ids from get_product to edit in place."),
|
|
140
|
+
}, ({ product_id, name, description, images, category_ids, is_published, variations }) => handle(async () => {
|
|
141
|
+
const productParams = { product_id };
|
|
142
|
+
if (name != null)
|
|
143
|
+
productParams.name = name;
|
|
144
|
+
if (description != null)
|
|
145
|
+
productParams.description = description;
|
|
146
|
+
if (images)
|
|
147
|
+
productParams.image = images[0];
|
|
148
|
+
if (category_ids) {
|
|
149
|
+
productParams.categories = category_ids;
|
|
150
|
+
productParams.ribbons = [];
|
|
151
|
+
}
|
|
152
|
+
if (is_published != null)
|
|
153
|
+
productParams.is_published = is_published;
|
|
154
|
+
if (variations) {
|
|
155
|
+
productParams.variations = variations.map((v) => ({
|
|
156
|
+
custom_id: v.custom_id || `SKU-${randomUUID().slice(0, 8)}`,
|
|
157
|
+
retail_price: v.retail_price,
|
|
158
|
+
original_price: v.original_price ?? v.retail_price,
|
|
159
|
+
remain_quantity: v.remain_quantity ?? 100,
|
|
160
|
+
images: v.images || (images ? images : []),
|
|
161
|
+
weight: v.weight ?? 0,
|
|
162
|
+
fields: v.fields || [],
|
|
163
|
+
is_hidden: false,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
await api.updateProduct(productParams);
|
|
167
|
+
return { success: true, product_id, updated: Object.keys(productParams).filter((k) => k !== "product_id") };
|
|
168
|
+
}));
|
|
169
|
+
server.tool("set_product_published", "Publish or unpublish one or more products quickly (without a full update).", {
|
|
170
|
+
product_ids: z.array(z.string()).describe("Product ids"),
|
|
171
|
+
is_published: z.boolean().describe("true = publish (visible), false = hide"),
|
|
172
|
+
}, ({ product_ids, is_published }) => handle(async () => {
|
|
173
|
+
await api.setProductsPublished(product_ids.map((product_id) => ({ product_id, is_published })));
|
|
174
|
+
return { success: true, product_ids, is_published };
|
|
175
|
+
}));
|
|
176
|
+
server.tool("delete_product", "Delete one or more products by id.", {
|
|
177
|
+
product_ids: z.array(z.string()).describe("Product ids to delete"),
|
|
178
|
+
}, ({ product_ids }) => handle(async () => {
|
|
179
|
+
await api.removeProducts(product_ids);
|
|
180
|
+
return { success: true, deleted: product_ids };
|
|
181
|
+
}));
|
|
182
|
+
server.tool("update_product_category", "Update a product category (name, image, description, or visibility). Pass id + fields to change.", {
|
|
183
|
+
id: z.string().describe("Category id"),
|
|
184
|
+
name: z.string().optional().describe("New name"),
|
|
185
|
+
image: z.string().optional().describe("New hosted image URL"),
|
|
186
|
+
description: z.string().optional().describe("New description"),
|
|
187
|
+
hidden: z.boolean().optional().describe("Hide (true) or show (false) the category"),
|
|
188
|
+
}, ({ id, name, image, description, hidden }) => handle(async () => {
|
|
189
|
+
const commands = [];
|
|
190
|
+
if (name != null)
|
|
191
|
+
commands.push({ name: "name_category", data: { id, name } });
|
|
192
|
+
if (image != null)
|
|
193
|
+
commands.push({ name: "image_category", data: { id, image } });
|
|
194
|
+
if (description != null)
|
|
195
|
+
commands.push({ name: "multi_description", data: { id, multi_description: [{ id: randomUUID(), title: name || "", description }] } });
|
|
196
|
+
if (hidden != null)
|
|
197
|
+
commands.push({ name: "set_category_visible", data: { id, is_hidden: hidden } });
|
|
198
|
+
if (!commands.length)
|
|
199
|
+
throw new Error("Nothing to update — pass at least one of name/image/description/hidden.");
|
|
200
|
+
await api.updateProductCategory(commands);
|
|
201
|
+
return { success: true, category_id: id, updated: commands.map((c) => c.name) };
|
|
202
|
+
}));
|
|
203
|
+
server.tool("delete_product_category", "Delete one or more product categories by id.", {
|
|
204
|
+
ids: z.array(z.string()).describe("Category ids to delete"),
|
|
205
|
+
}, ({ ids }) => handle(async () => {
|
|
206
|
+
await api.deleteProductCategory([{ name: "bulk_delete_category", data: { ids } }]);
|
|
207
|
+
return { success: true, deleted: ids };
|
|
208
|
+
}));
|
|
129
209
|
}
|
package/dist/tools/site-style.js
CHANGED
|
@@ -52,7 +52,6 @@ async function getThemeCatalog(force = false) {
|
|
|
52
52
|
preview_url: t.preview_url || "",
|
|
53
53
|
thumbnail: t.thumbnail || "",
|
|
54
54
|
categories: (t.categories || []).map((c) => c.name).filter(Boolean),
|
|
55
|
-
site_id: (t.site && t.site.id) || undefined,
|
|
56
55
|
});
|
|
57
56
|
}
|
|
58
57
|
if (themes.length < limit)
|
|
@@ -146,7 +145,6 @@ export function registerSiteStyleTools(server, api, handle) {
|
|
|
146
145
|
const desc = parseThemeDescription(te && te.description);
|
|
147
146
|
return {
|
|
148
147
|
theme_id: themeId,
|
|
149
|
-
template_site_id: info.site_id || null, // pass to create_site_from_template
|
|
150
148
|
score: typeof score === "number" ? Number(score.toFixed(4)) : null,
|
|
151
149
|
name: info.name || null,
|
|
152
150
|
preview_url: info.preview_url || null,
|
|
@@ -163,50 +161,31 @@ export function registerSiteStyleTools(server, api, handle) {
|
|
|
163
161
|
hint: "Pick one and call create_site_from_template with its theme_id (or template_site_id) to clone it into a new editable site.",
|
|
164
162
|
};
|
|
165
163
|
}));
|
|
166
|
-
server.tool("create_site_from_template", `Create a NEW site
|
|
167
|
-
|
|
168
|
-
semantic_search_themes / list_template_themes
|
|
169
|
-
|
|
170
|
-
update_page_element(s), colours/
|
|
164
|
+
server.tool("create_site_from_template", `Create a NEW site from a marketplace TEMPLATE (the dedicated "use this template" API).
|
|
165
|
+
Clones the template's pages, global sections, cart, popups, styles and fonts into a fresh
|
|
166
|
+
site. Pick a template with semantic_search_themes / list_template_themes, then pass its
|
|
167
|
+
theme_id here. Switches to the new site so you can immediately edit layout/content with
|
|
168
|
+
update_page_element(s), colours/fonts via the site-style tools, then publish_site.`, {
|
|
171
169
|
name: z.string().describe("Name for the new site"),
|
|
172
|
-
theme_id: z.string().
|
|
173
|
-
template_site_id: z.string().optional().describe("The template's source site id (alternative to theme_id; semantic_search_themes returns it as template_site_id)"),
|
|
170
|
+
theme_id: z.string().describe("Marketplace theme id (from semantic_search_themes / list_template_themes)"),
|
|
174
171
|
slug: z.string().optional().describe("URL-safe slug for the new site (auto-generated if omitted)"),
|
|
175
|
-
switch_to: z.boolean().default(true).describe("Switch the session to the new site after
|
|
176
|
-
}, ({ name, theme_id,
|
|
177
|
-
|
|
178
|
-
let sourceSiteId = template_site_id;
|
|
179
|
-
if (!sourceSiteId && theme_id) {
|
|
180
|
-
const catalog = await getThemeCatalog().catch(() => new Map());
|
|
181
|
-
sourceSiteId = catalog.get(theme_id)?.site_id;
|
|
182
|
-
}
|
|
183
|
-
if (!sourceSiteId) {
|
|
184
|
-
throw new Error("Could not resolve the template's source site. Pass template_site_id, or a theme_id that exists in the marketplace (list_template_themes / semantic_search_themes).");
|
|
185
|
-
}
|
|
186
|
-
// The duplicate endpoint returns success-only (no id), so snapshot the site list
|
|
187
|
-
// first, clone, then resolve the new site by diff.
|
|
188
|
-
const listIds = async () => {
|
|
189
|
-
const r = await api.listMySites({ page: 1, limit: 100 }).catch(() => null);
|
|
190
|
-
const arr = r?.data?.sites || r?.data || [];
|
|
191
|
-
return Array.isArray(arr) ? arr : [];
|
|
192
|
-
};
|
|
193
|
-
const before = await listIds();
|
|
194
|
-
const beforeIds = new Set(before.map((s) => s.id));
|
|
172
|
+
switch_to: z.boolean().default(true).describe("Switch the session to the new site after creating it (saved for next session)"),
|
|
173
|
+
}, ({ name, theme_id, slug, switch_to }) => handle(async () => {
|
|
174
|
+
let res;
|
|
195
175
|
try {
|
|
196
|
-
await api.
|
|
176
|
+
res = await api.importStoreToTheme({ id: theme_id, name, ...(slug ? { slug } : {}) });
|
|
197
177
|
}
|
|
198
178
|
catch (e) {
|
|
199
179
|
const msg = e instanceof Error ? e.message : String(e);
|
|
200
180
|
if (msg.includes("403"))
|
|
201
181
|
throw new Error("Cannot create site: account site quota reached (free plan allows up to 4 sites).");
|
|
202
|
-
throw new Error(`
|
|
182
|
+
throw new Error(`Creating the site from the template failed: ${msg}. Check the theme_id exists (list_template_themes / semantic_search_themes).`);
|
|
203
183
|
}
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const newSite = fresh.find((s) => s.name === name) || fresh[0];
|
|
184
|
+
const data = res?.data || res;
|
|
185
|
+
const newSite = data?.site || data?.new_site || data;
|
|
207
186
|
const newId = newSite?.id;
|
|
208
187
|
if (!newId)
|
|
209
|
-
throw new Error("
|
|
188
|
+
throw new Error("Site created from template but no id was returned.");
|
|
210
189
|
let switched = false;
|
|
211
190
|
let previewUrl = null;
|
|
212
191
|
const previousSiteId = api.siteId;
|
|
@@ -221,11 +200,11 @@ update_page_element(s), colours/typography with the site-style tools, and republ
|
|
|
221
200
|
success: true,
|
|
222
201
|
site_id: newId,
|
|
223
202
|
name: newSite?.name || name,
|
|
224
|
-
from_template:
|
|
203
|
+
from_template: theme_id,
|
|
225
204
|
switched,
|
|
226
205
|
...(switched ? { current_site_id: api.siteId, previous_site_id: previousSiteId } : {}),
|
|
227
206
|
preview_url: previewUrl,
|
|
228
|
-
next_step: "Site
|
|
207
|
+
next_step: "Site created from the template (pages, sections, popups, styles, fonts). Edit content with search_page_elements + update_page_element(s), colours/fonts via list_themes/site-style, then publish_site.",
|
|
229
208
|
};
|
|
230
209
|
}));
|
|
231
210
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "MCP server for the WebCake/StoreCake storefront builder — page CRUD, page authoring, products, orders, and more",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-storefront-mcp",
|
|
6
6
|
"license": "MIT",
|