webcake-storefront-mcp 1.4.0 → 1.5.1
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 +56 -3
- package/dist/changelog.json +14 -14
- package/dist/server.js +4 -0
- package/dist/tools/articles.js +30 -9
- package/dist/tools/builder.js +11 -6
- package/dist/tools/catalog-write.js +129 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -139,8 +139,16 @@ export class WebcakeCmsApi {
|
|
|
139
139
|
listPages() {
|
|
140
140
|
return this.request("GET", `/api/v1/site/${this.siteId}/pages`);
|
|
141
141
|
}
|
|
142
|
+
/** Create a page. The backend creates the page AND its source in one call, so `source`
|
|
143
|
+
* is REQUIRED and must be a JSON string (stringified here if an object is passed).
|
|
144
|
+
* `slug`/`is_homepage` are NOT applied at create — set them afterwards via updatePage. */
|
|
142
145
|
createPage(params) {
|
|
143
|
-
|
|
146
|
+
const body = { ...params };
|
|
147
|
+
if (body.source != null && typeof body.source !== "string")
|
|
148
|
+
body.source = JSON.stringify(body.source);
|
|
149
|
+
if (body.source == null)
|
|
150
|
+
body.source = JSON.stringify({ sections: [] });
|
|
151
|
+
return this.request("POST", `/api/v1/site/${this.siteId}/page`, { body });
|
|
144
152
|
}
|
|
145
153
|
updatePage(pageId, params) {
|
|
146
154
|
return this.request("POST", `/api/v1/site/${this.siteId}/${pageId}/update_page`, { body: params });
|
|
@@ -169,8 +177,21 @@ export class WebcakeCmsApi {
|
|
|
169
177
|
saveSite(params = {}) {
|
|
170
178
|
return this.request("POST", `/api/v1/site/${this.siteId}/save`, { body: params, timeout: 60000 });
|
|
171
179
|
}
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
/** Publish the site live. /publish runs the full "save" pipeline, which OVERWRITES
|
|
181
|
+
* site.settings with the body's `settings` — so we send the CURRENT settings (else
|
|
182
|
+
* they'd be nulled, disabling use_store/use_blog/etc.). Other collections default to []. */
|
|
183
|
+
async publishSite(params = {}) {
|
|
184
|
+
let settings = params.settings;
|
|
185
|
+
if (settings === undefined) {
|
|
186
|
+
settings = await this.getSiteSettings().catch(() => ({}));
|
|
187
|
+
}
|
|
188
|
+
// The save pipeline stores site.settings as a JSON STRING — an object body is
|
|
189
|
+
// rejected (422). Stringify unless the caller already passed a string.
|
|
190
|
+
const settingsStr = typeof settings === "string" ? settings : JSON.stringify(settings || {});
|
|
191
|
+
return this.request("POST", `/api/v1/site/${this.siteId}/publish`, {
|
|
192
|
+
body: { global_sources: [], global_sections: [], page_contents: [], ...params, settings: settingsStr },
|
|
193
|
+
timeout: 60000,
|
|
194
|
+
});
|
|
174
195
|
}
|
|
175
196
|
uploadImageBase64({ base64, content_type } = {}) {
|
|
176
197
|
return this.request("POST", `/api/v1/site/${this.siteId}/media/content/b64`, {
|
|
@@ -266,6 +287,22 @@ export class WebcakeCmsApi {
|
|
|
266
287
|
deleteArticle(id) {
|
|
267
288
|
return this.request("DELETE", `/api/v1/cms_function/${this.siteId}/blog/article/${id}`);
|
|
268
289
|
}
|
|
290
|
+
/** Create a blog/article category. Command-based: pass a `commands` array whose entries
|
|
291
|
+
* each carry a caller-generated `data.id` (the new category id). Response is generic. */
|
|
292
|
+
createBlogCategory(commands) {
|
|
293
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/blog/categories/create`, {
|
|
294
|
+
body: { site_id: this.siteId, commands },
|
|
295
|
+
timeout: 60000,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/** Create a blog article via the dashboard command pipeline (supports category linkage,
|
|
299
|
+
* images, summary, content). Caller generates the article id in each command's data.id. */
|
|
300
|
+
createBlogArticle(commands) {
|
|
301
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/blog/articles/create`, {
|
|
302
|
+
body: { site_id: this.siteId, commands },
|
|
303
|
+
timeout: 60000,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
269
306
|
// ── Products ──
|
|
270
307
|
listProducts(query) {
|
|
271
308
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/products/all`, { query });
|
|
@@ -282,6 +319,22 @@ export class WebcakeCmsApi {
|
|
|
282
319
|
getProductsByCategory(categoryId) {
|
|
283
320
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/categories/products`, { query: { category_id: categoryId } });
|
|
284
321
|
}
|
|
322
|
+
/** Create a product. Body wraps fields in `product_params`; the backend generates the
|
|
323
|
+
* id/slug and sets is_published. Pass variations (price/stock per SKU) + categories. */
|
|
324
|
+
createProduct(productParams) {
|
|
325
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/products/create`, {
|
|
326
|
+
body: { site_id: this.siteId, product_params: productParams },
|
|
327
|
+
timeout: 60000,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/** Create a product category. Command-based: pass a `commands` array whose entries each
|
|
331
|
+
* carry a caller-generated `data.id` (the new category id). Response is generic. */
|
|
332
|
+
createProductCategory(commands) {
|
|
333
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/categories/create`, {
|
|
334
|
+
body: { site_id: this.siteId, commands },
|
|
335
|
+
timeout: 60000,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
285
338
|
// ── Orders ──
|
|
286
339
|
listOrders(query) {
|
|
287
340
|
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.5.1",
|
|
4
|
+
"d": "23/06/2026",
|
|
5
|
+
"type": "Fixed",
|
|
6
|
+
"en": "build_page now passes the finalized page source to create_page in the initial create call (required by the backend), then sets slug and is_homepage…",
|
|
7
|
+
"vi": "build_page nay truyền source trang đã hoàn thiện vào lời gọi create_page ban đầu (bắt buộc bởi backend), sau đó đặt slug và is_homepage qua một lời…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.5.0",
|
|
11
|
+
"d": "23/06/2026",
|
|
12
|
+
"type": "Added",
|
|
13
|
+
"en": "New create_product tool creates a storefront product (simple name + price, or advanced with named attributes and per-SKU variations); accepts hosted…",
|
|
14
|
+
"vi": "Tool mới create_product tạo sản phẩm cho storefront (đơn giản với tên + giá, hoặc nâng cao với attributes và variations theo từng SKU); nhận URL ảnh…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.4.0",
|
|
4
18
|
"d": "23/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "The remote landing page served by the serve command now includes a \"What's new\" section that renders a version timeline loaded from a build-time…",
|
|
28
42
|
"vi": "Trang landing của lệnh serve nay có thêm mục \"Có gì mới\" hiển thị timeline lịch sử phiên bản được tải từ file changelog.json sinh ra lúc build (từ…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.1.3",
|
|
32
|
-
"d": "23/06/2026",
|
|
33
|
-
"type": "Changed",
|
|
34
|
-
"en": "The install command's interactive wizard now runs a three-step flow: environment selection (prod / staging / local), authentication, then IDE…",
|
|
35
|
-
"vi": "Trình hướng dẫn tương tác của lệnh install nay chạy theo 3 bước: chọn môi trường (prod / staging / local), xác thực, rồi cấu hình IDE."
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.1.2",
|
|
39
|
-
"d": "23/06/2026",
|
|
40
|
-
"type": "Fixed",
|
|
41
|
-
"en": "The server no longer crashes at startup in container environments built with npm ci --ignore-scripts; the better-sqlite3 native SQLite module has…",
|
|
42
|
-
"vi": "Server không còn bị crash khi khởi động trong môi trường container được build bằng npm ci --ignore-scripts; module SQLite native better-sqlite3 đã…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerArticleTools } from "./tools/articles.js";
|
|
|
7
7
|
import { registerCustomerTools } from "./tools/customers.js";
|
|
8
8
|
import { registerAutomationTools } from "./tools/automation.js";
|
|
9
9
|
import { registerProductTools } from "./tools/products.js";
|
|
10
|
+
import { registerCatalogWriteTools } from "./tools/catalog-write.js";
|
|
10
11
|
import { registerOrderTools } from "./tools/orders.js";
|
|
11
12
|
import { registerSiteStyleTools } from "./tools/site-style.js";
|
|
12
13
|
import { registerAppTools } from "./tools/apps.js";
|
|
@@ -22,6 +23,8 @@ IMPORTANT: When the user asks ANY question about their website, store, products,
|
|
|
22
23
|
|
|
23
24
|
You can also BUILD pages: use get_build_guide, list_elements, get_element to learn the BuilderX component model, new_section/new_element to compose, validate_page to check, then build_page (dry_run first) to create. Publishing is site-level via publish_site.
|
|
24
25
|
|
|
26
|
+
To make a generated site look real, also CREATE DATA so dataset bindings resolve: create_product_category + create_product (storefront), create_blog_category + create_article (blog). Get image URLs from search_images / upload_images first, then reference them. A good flow for a fresh site: create_site → create a few categories → create products in them → build_page (home + store/blog pages) → publish_site.
|
|
27
|
+
|
|
25
28
|
Workflow:
|
|
26
29
|
1. On first interaction, call get_current_context. The site is NOT set from env — if no site is selected yet, call list_my_sites and ask the user which site to work on, then switch_site (the choice is saved and reused next session). To start from scratch, create_site makes a new site and switches to it; then build a homepage with build_page (type:'main', is_homepage:true).
|
|
27
30
|
2. Before answering a site-specific question, query the relevant tool.
|
|
@@ -50,6 +53,7 @@ export function createServer(api, opts = {}) {
|
|
|
50
53
|
registerCustomerTools(server, api, handle);
|
|
51
54
|
registerAutomationTools(server, api, handle);
|
|
52
55
|
registerProductTools(server, api, handle);
|
|
56
|
+
registerCatalogWriteTools(server, api, handle);
|
|
53
57
|
registerOrderTools(server, api, handle);
|
|
54
58
|
registerSiteStyleTools(server, api, handle);
|
|
55
59
|
registerAppTools(server, api, handle);
|
package/dist/tools/articles.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
export function registerArticleTools(server, api, handle) {
|
|
3
4
|
server.tool("list_articles", "List blog articles (metadata only, without HTML content). Use get_article to get full content", {
|
|
4
5
|
page: z.number().optional().describe("Page number"),
|
|
@@ -27,16 +28,36 @@ export function registerArticleTools(server, api, handle) {
|
|
|
27
28
|
server.tool("get_article", "Get article details by ID", {
|
|
28
29
|
id: z.string().describe("Article ID"),
|
|
29
30
|
}, ({ id }) => handle(() => api.getArticle(id)));
|
|
30
|
-
server.tool("create_article",
|
|
31
|
+
server.tool("create_article", `Create a blog article so blog/post pages (post-list, grid-blog, post-overlay) have content.
|
|
32
|
+
Built via the dashboard command pipeline: title + optional summary, HTML content, image URLs, and
|
|
33
|
+
category linkage. Pass category_ids from create_blog_category / list articles' categories so the
|
|
34
|
+
post shows up under those categories (it is also auto-filed under the default category). Image URLs
|
|
35
|
+
must be hosted (search_images / upload_images). The backend generates the id and slug.`, {
|
|
31
36
|
name: z.string().describe("Article title"),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
content: z.string().optional().describe("HTML content of the post"),
|
|
38
|
+
summary: z.string().optional().describe("Short summary / excerpt"),
|
|
39
|
+
images: z.array(z.string()).optional().describe("Hosted image URLs; the first is the cover image"),
|
|
40
|
+
category_ids: z.array(z.string()).optional().describe("Blog category IDs to file the post under (from create_blog_category)"),
|
|
41
|
+
}, ({ name, content, summary, images, category_ids }) => handle(async () => {
|
|
42
|
+
const id = randomUUID();
|
|
43
|
+
const commands = [{ name: "create_article", data: { id, name } }];
|
|
44
|
+
if (summary)
|
|
45
|
+
commands.push({ name: "summary_article", data: { id, summary } });
|
|
46
|
+
if (images && images.length)
|
|
47
|
+
commands.push({ name: "image_article", data: { id, images } });
|
|
48
|
+
if (content)
|
|
49
|
+
commands.push({ name: "content_article", data: { id, content } });
|
|
50
|
+
if (category_ids && category_ids.length)
|
|
51
|
+
commands.push({ name: "bulk_add_category_to_article", data: { id, ids: category_ids } });
|
|
52
|
+
await api.createBlogArticle(commands);
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
article_id: id,
|
|
56
|
+
name,
|
|
57
|
+
categories: category_ids || [],
|
|
58
|
+
cover: images?.[0] || null,
|
|
59
|
+
};
|
|
60
|
+
}));
|
|
40
61
|
server.tool("update_article", "Update a blog article", {
|
|
41
62
|
id: z.string().describe("Article ID"),
|
|
42
63
|
name: z.string().optional().describe("New title"),
|
package/dist/tools/builder.js
CHANGED
|
@@ -96,14 +96,20 @@ The source must be { sections: [...] } — build sections with new_section. Vali
|
|
|
96
96
|
return { error: `Could not enable site.settings.${requiredFlag} (needed for a '${kind}' page). ${e?.message ?? e}` };
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
// Expand runtime -> bp1..bp4 so the saved source actually renders on the storefront.
|
|
100
|
+
finalizeForRender(parsed);
|
|
101
|
+
// createPage saves the page AND its source in one call (source is required there).
|
|
102
|
+
const created = await api.createPage({ name, source: parsed, ...(typeNum != null ? { type: typeNum } : {}) });
|
|
100
103
|
const pageId = newPageId(created);
|
|
101
104
|
if (!pageId) {
|
|
102
|
-
return { error: "Page created but no id was returned
|
|
105
|
+
return { error: "Page created but no id was returned.", created };
|
|
106
|
+
}
|
|
107
|
+
// slug / homepage are not applied at create — set them via update_page.
|
|
108
|
+
if (slug || is_homepage) {
|
|
109
|
+
await api
|
|
110
|
+
.updatePage(pageId, { ...(slug ? { slug } : {}), ...(is_homepage ? { is_homepage: true } : {}) })
|
|
111
|
+
.catch(() => { });
|
|
103
112
|
}
|
|
104
|
-
// Expand runtime -> bp1..bp4 so the saved source actually renders on the storefront.
|
|
105
|
-
finalizeForRender(parsed);
|
|
106
|
-
const saved = await api.updatePageSource(pageId, { source: parsed });
|
|
107
113
|
return {
|
|
108
114
|
success: true,
|
|
109
115
|
page_id: pageId,
|
|
@@ -111,7 +117,6 @@ The source must be { sections: [...] } — build sections with new_section. Vali
|
|
|
111
117
|
slug,
|
|
112
118
|
page_type: kind ?? null,
|
|
113
119
|
...(feature ? { data_source: { flag: feature.flag, newly_enabled: feature.changed } } : {}),
|
|
114
|
-
page_source_id: saved && saved.data && saved.data.id,
|
|
115
120
|
stats: validation.stats,
|
|
116
121
|
};
|
|
117
122
|
}));
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Write tools for catalog DATA — products, product categories, blog categories — so a
|
|
2
|
+
// generated site has real content to render (grid-product / grid-category / post-list
|
|
3
|
+
// bindings resolve only when these exist). Articles already have create_article.
|
|
4
|
+
//
|
|
5
|
+
// The category endpoints are COMMAND-based and the CALLER must generate the new id
|
|
6
|
+
// (the response is a generic {code:success}), so we mint a UUID and return it.
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
const variationSpec = z.object({
|
|
10
|
+
retail_price: z.number().describe("Selling price (what the customer pays)"),
|
|
11
|
+
original_price: z.number().optional().describe("List/compare-at price (defaults to retail_price)"),
|
|
12
|
+
remain_quantity: z.number().optional().describe("Stock quantity (default 100)"),
|
|
13
|
+
custom_id: z.string().optional().describe("SKU for this variation (auto-generated if omitted)"),
|
|
14
|
+
images: z.array(z.string()).optional().describe("Hosted image URLs for this variation"),
|
|
15
|
+
weight: z.number().optional().describe("Weight in grams (default 0)"),
|
|
16
|
+
fields: z
|
|
17
|
+
.array(z.object({ name: z.string(), value: z.string() }))
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Attribute values for this variation, e.g. [{name:'Color',value:'Đen'},{name:'Size',value:'M'}]"),
|
|
20
|
+
});
|
|
21
|
+
export function registerCatalogWriteTools(server, api, handle) {
|
|
22
|
+
server.tool("create_product", `Create a product so the storefront has real merchandise (grid-product / slider-product bindings need this).
|
|
23
|
+
Simple use: pass name + price (+ images, category_ids). One default variation with the price/stock is created for you.
|
|
24
|
+
Advanced use: pass attributes (e.g. Color/Size) + variations for a multi-SKU product.
|
|
25
|
+
Images must be HOSTED URLs — get them from search_images or upload_images first. The backend generates id, slug and publishes the product.`, {
|
|
26
|
+
name: z.string().describe("Product name"),
|
|
27
|
+
price: z.number().optional().describe("Selling price (used when you don't pass variations). Required unless variations are given."),
|
|
28
|
+
original_price: z.number().optional().describe("List/compare-at price for the simple single-variation case"),
|
|
29
|
+
stock: z.number().default(100).describe("Stock quantity for the simple single-variation case"),
|
|
30
|
+
sku: z.string().optional().describe("SKU / custom_id for the simple case (auto-generated if omitted)"),
|
|
31
|
+
images: z.array(z.string()).optional().describe("Hosted image URLs (search_images/upload_images). First image becomes the product thumbnail."),
|
|
32
|
+
description: z.string().optional().describe("Product description (HTML allowed)"),
|
|
33
|
+
category_ids: z.array(z.string()).optional().describe("Product category IDs to file the product under (from create_product_category / list_categories)"),
|
|
34
|
+
attributes: z
|
|
35
|
+
.array(z.object({ name: z.string(), values: z.array(z.string()) }))
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Variant axes, e.g. [{name:'Color',values:['Đen','Trắng']},{name:'Size',values:['S','M','L']}]"),
|
|
38
|
+
variations: z.array(variationSpec).optional().describe("Explicit per-SKU variations. Omit to auto-build one from price/stock/sku."),
|
|
39
|
+
}, ({ name, price, original_price, stock, sku, images, description, category_ids, attributes, variations }) => handle(async () => {
|
|
40
|
+
let vars = variations;
|
|
41
|
+
if (!vars || !vars.length) {
|
|
42
|
+
if (price == null)
|
|
43
|
+
throw new Error("Provide `price` (or explicit `variations`) to create a product.");
|
|
44
|
+
vars = [
|
|
45
|
+
{
|
|
46
|
+
custom_id: sku || `SKU-${randomUUID().slice(0, 8)}`,
|
|
47
|
+
retail_price: price,
|
|
48
|
+
original_price: original_price ?? price,
|
|
49
|
+
remain_quantity: stock ?? 100,
|
|
50
|
+
images: images || [],
|
|
51
|
+
weight: 0,
|
|
52
|
+
fields: [],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Normalise: fill SKU / original_price / stock defaults per variation.
|
|
58
|
+
vars = vars.map((v) => ({
|
|
59
|
+
custom_id: v.custom_id || `SKU-${randomUUID().slice(0, 8)}`,
|
|
60
|
+
retail_price: v.retail_price,
|
|
61
|
+
original_price: v.original_price ?? v.retail_price,
|
|
62
|
+
remain_quantity: v.remain_quantity ?? 100,
|
|
63
|
+
images: v.images || [],
|
|
64
|
+
weight: v.weight ?? 0,
|
|
65
|
+
fields: v.fields || [],
|
|
66
|
+
is_hidden: false,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
const productParams = {
|
|
70
|
+
name,
|
|
71
|
+
variations: vars,
|
|
72
|
+
// These MUST be arrays — the backend does Enum.reduce over them and 500s on nil.
|
|
73
|
+
categories: category_ids || [],
|
|
74
|
+
ribbons: [],
|
|
75
|
+
...(description ? { description } : {}),
|
|
76
|
+
...(attributes ? { product_attributes: attributes } : {}),
|
|
77
|
+
...(images && images.length ? { image: images[0] } : {}),
|
|
78
|
+
};
|
|
79
|
+
const res = await api.createProduct(productParams);
|
|
80
|
+
// Success response is { product: {...} } at the top level (not under data).
|
|
81
|
+
const prod = res?.product || res?.data?.product || res?.data?.attributes || res?.data || {};
|
|
82
|
+
const productId = prod?.id || null;
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
product_id: productId,
|
|
86
|
+
name,
|
|
87
|
+
slug: prod?.slug || null,
|
|
88
|
+
variations: vars.length,
|
|
89
|
+
categories: category_ids || [],
|
|
90
|
+
raw: productId ? undefined : res, // surface raw response only if we couldn't find the id
|
|
91
|
+
};
|
|
92
|
+
}));
|
|
93
|
+
server.tool("create_product_category", `Create a product category (so grid-category / a category page has something to show, and products can be filed under it).
|
|
94
|
+
Returns the new category id — pass it to create_product's category_ids. Image must be a hosted URL.`, {
|
|
95
|
+
name: z.string().describe("Category name"),
|
|
96
|
+
image: z.string().optional().describe("Hosted image URL for the category card"),
|
|
97
|
+
description: z.string().optional().describe("Category description"),
|
|
98
|
+
parent_id: z.string().optional().describe("Parent category id for a sub-category"),
|
|
99
|
+
}, ({ name, image, description, parent_id }) => handle(async () => {
|
|
100
|
+
const id = randomUUID();
|
|
101
|
+
const commands = [
|
|
102
|
+
{ name: "create_category", data: { id, name, ...(parent_id ? { parent_id } : {}) } },
|
|
103
|
+
];
|
|
104
|
+
if (image)
|
|
105
|
+
commands.push({ name: "image_category", data: { id, image } });
|
|
106
|
+
if (description) {
|
|
107
|
+
commands.push({
|
|
108
|
+
name: "multi_description",
|
|
109
|
+
data: { id, multi_description: [{ id: randomUUID(), title: name, description }] },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
await api.createProductCategory(commands);
|
|
113
|
+
return { success: true, category_id: id, name, ...(parent_id ? { parent_id } : {}) };
|
|
114
|
+
}));
|
|
115
|
+
server.tool("create_blog_category", `Create a blog/article category. Returns the new category id — pass it to create_article's category_id so posts are grouped (post-list / blog pages bind to it). Image must be a hosted URL.`, {
|
|
116
|
+
name: z.string().describe("Blog category name"),
|
|
117
|
+
image: z.string().optional().describe("Hosted image URL"),
|
|
118
|
+
description: z.string().optional().describe("Category description"),
|
|
119
|
+
}, ({ name, image, description }) => handle(async () => {
|
|
120
|
+
const id = randomUUID();
|
|
121
|
+
const commands = [{ name: "create_category", data: { id, name } }];
|
|
122
|
+
if (description)
|
|
123
|
+
commands.push({ name: "description_category", data: { id, description } });
|
|
124
|
+
if (image)
|
|
125
|
+
commands.push({ name: "image_category", data: { id, image } });
|
|
126
|
+
await api.createBlogCategory(commands);
|
|
127
|
+
return { success: true, category_id: id, name };
|
|
128
|
+
}));
|
|
129
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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",
|