webcake-storefront-mcp 1.17.0 → 1.18.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 -1
- package/dist/builder/page.js +21 -2
- package/dist/changelog.json +14 -14
- package/dist/guides.js +26 -0
- package/dist/server.js +4 -2
- package/dist/tools/catalog-write.js +3 -1
- package/dist/tools/context.js +20 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -208,9 +208,48 @@ export class WebcakeCmsApi {
|
|
|
208
208
|
saveSite(params = {}) {
|
|
209
209
|
return this.request("POST", `/api/v1/site/${this.siteId}/save`, { body: params, timeout: 60000 });
|
|
210
210
|
}
|
|
211
|
+
/** Rebuild every page's compiled CSS (page_source.app_css) by replaying the builder's
|
|
212
|
+
* /save pipeline — the ONLY path that regenerates the storefront's dynamic CSS. The
|
|
213
|
+
* backend builds CSS per page in `params["pages"]` from that page's `source`; /publish
|
|
214
|
+
* does NOT do this, so a site changed only through the MCP renders with stale/empty CSS
|
|
215
|
+
* until this runs. Sends each page's CURRENT saved source (stringified) + current
|
|
216
|
+
* settings; empty global arrays leave globals/popups untouched. */
|
|
217
|
+
async rebuildSiteCss(settings) {
|
|
218
|
+
const res = await this.listPages();
|
|
219
|
+
const list = (res && res.data) || res || [];
|
|
220
|
+
const pages = (Array.isArray(list) ? list : [])
|
|
221
|
+
.map((p) => {
|
|
222
|
+
const src = p && p.source && p.source.source;
|
|
223
|
+
if (src == null || src === "")
|
|
224
|
+
return null;
|
|
225
|
+
return {
|
|
226
|
+
id: p.id,
|
|
227
|
+
source: typeof src === "string" ? src : JSON.stringify(src),
|
|
228
|
+
settings: JSON.stringify(p.settings || {}),
|
|
229
|
+
custom_code: p.custom_code || {},
|
|
230
|
+
};
|
|
231
|
+
})
|
|
232
|
+
.filter(Boolean);
|
|
233
|
+
if (!pages.length)
|
|
234
|
+
return { rebuilt: 0 };
|
|
235
|
+
let s = settings;
|
|
236
|
+
if (s === undefined)
|
|
237
|
+
s = await this.getSiteSettings().catch(() => ({}));
|
|
238
|
+
const settingsStr = typeof s === "string" ? s : JSON.stringify(s || {});
|
|
239
|
+
const changes = {};
|
|
240
|
+
for (const p of pages)
|
|
241
|
+
changes[p.id] = 1;
|
|
242
|
+
await this.request("POST", `/api/v1/site/${this.siteId}/save`, {
|
|
243
|
+
body: { pages, settings: settingsStr, changes, global_sources: [], global_sections: [], page_contents: [] },
|
|
244
|
+
timeout: 120000,
|
|
245
|
+
});
|
|
246
|
+
return { rebuilt: pages.length };
|
|
247
|
+
}
|
|
211
248
|
/** Publish the site live. /publish runs the full "save" pipeline, which OVERWRITES
|
|
212
249
|
* site.settings with the body's `settings` — so we send the CURRENT settings (else
|
|
213
|
-
* they'd be nulled, disabling use_store/use_blog/etc.). Other collections default to [].
|
|
250
|
+
* they'd be nulled, disabling use_store/use_blog/etc.). Other collections default to [].
|
|
251
|
+
* We ALSO rebuild every page's CSS first (rebuildSiteCss) because /publish alone does
|
|
252
|
+
* not regenerate the storefront's dynamic CSS — without it the live site looks unstyled. */
|
|
214
253
|
async publishSite(params = {}) {
|
|
215
254
|
let settings = params.settings;
|
|
216
255
|
if (settings === undefined) {
|
|
@@ -219,6 +258,8 @@ export class WebcakeCmsApi {
|
|
|
219
258
|
// The save pipeline stores site.settings as a JSON STRING — an object body is
|
|
220
259
|
// rejected (422). Stringify unless the caller already passed a string.
|
|
221
260
|
const settingsStr = typeof settings === "string" ? settings : JSON.stringify(settings || {});
|
|
261
|
+
// Regenerate compiled CSS for every page before publishing (no-op-safe on failure).
|
|
262
|
+
await this.rebuildSiteCss(settingsStr).catch(() => { });
|
|
222
263
|
return this.request("POST", `/api/v1/site/${this.siteId}/publish`, {
|
|
223
264
|
body: { global_sources: [], global_sections: [], page_contents: [], ...params, settings: settingsStr },
|
|
224
265
|
timeout: 60000,
|
package/dist/builder/page.js
CHANGED
|
@@ -51,6 +51,25 @@ export function reassignIds(node) {
|
|
|
51
51
|
* finalizeForRender() later expands `runtime` into the per-breakpoint keys the
|
|
52
52
|
* storefront actually reads (bp1..bp4).
|
|
53
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* Element types that must FILL their grid cell's width to lay out correctly — the
|
|
56
|
+
* storefront renderer turns a single-value `constraintX` ("centerLeft") into
|
|
57
|
+
* `justify-self: center`, which shrinks the element to its content width. For repeaters
|
|
58
|
+
* (a grid-product then computes `repeat(auto-fit, minmax(min, 1fr))` against that width)
|
|
59
|
+
* that collapses the whole grid to ONE column. Giving them `["left","right"]` makes the
|
|
60
|
+
* renderer emit `justify-self: stretch` so they span the full content column.
|
|
61
|
+
*/
|
|
62
|
+
const FILL_WIDTH_TYPES = new Set([
|
|
63
|
+
"grid-product", "slider-product", "cart-items", "order-items", "post-list",
|
|
64
|
+
"grid-category", "grid-blog", "product-gallery", "product-image-carousel",
|
|
65
|
+
"custom-layout", "layout-dataset", "form",
|
|
66
|
+
]);
|
|
67
|
+
/** Default horizontal constraint for a child: stretch for fill-width components,
|
|
68
|
+
* else the builder's usual centred placement. Respects an explicit constraintX. */
|
|
69
|
+
function defaultConstraintX(child) {
|
|
70
|
+
return ((child.runtime && child.runtime.config && child.runtime.config.constraintX) ||
|
|
71
|
+
(FILL_WIDTH_TYPES.has(child.type) ? ["left", "right"] : ["centerLeft"]));
|
|
72
|
+
}
|
|
54
73
|
export function stackChildren(container, children, opts = {}) {
|
|
55
74
|
const gridCols = opts.gridCols || 1;
|
|
56
75
|
const colStart = opts.contentColStart || 1;
|
|
@@ -76,7 +95,7 @@ export function stackChildren(container, children, opts = {}) {
|
|
|
76
95
|
columnEnd: colEnd,
|
|
77
96
|
rowStart: i + 1,
|
|
78
97
|
rowEnd: i + 2,
|
|
79
|
-
constraintX: (child
|
|
98
|
+
constraintX: defaultConstraintX(child),
|
|
80
99
|
constraintY: (child.runtime.config && child.runtime.config.constraintY) || ["top"],
|
|
81
100
|
loaded: true,
|
|
82
101
|
};
|
|
@@ -124,7 +143,7 @@ export function rowChildren(container, children, opts = {}) {
|
|
|
124
143
|
columnEnd: i + 2,
|
|
125
144
|
rowStart: 1,
|
|
126
145
|
rowEnd: 2,
|
|
127
|
-
constraintX: (child
|
|
146
|
+
constraintX: defaultConstraintX(child),
|
|
128
147
|
constraintY: (child.runtime.config && child.runtime.config.constraintY) || ["top"],
|
|
129
148
|
loaded: true,
|
|
130
149
|
__cell: { index: i, ...meta },
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.18.0",
|
|
4
|
+
"d": "25/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New get_intake_guide tool returns a step-by-step questionnaire and recommended build flow (create_site → products → build_page → global sections →…",
|
|
7
|
+
"vi": "Tool mới get_intake_guide trả về bảng câu hỏi từng bước và quy trình build được khuyến nghị (create_site → sản phẩm → build_page → global sections →…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.17.1",
|
|
11
|
+
"d": "25/06/2026",
|
|
12
|
+
"type": "Changed",
|
|
13
|
+
"en": "get_http_function and get_site_custom_code now include a \"File / media uploads\" section in the embedded HTTP function guide, documenting three ways…",
|
|
14
|
+
"vi": "get_http_function và get_site_custom_code nay bổ sung mục \"File / media uploads\" vào hướng dẫn HTTP function nhúng sẵn, tài liệu hóa ba cách tải…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.17.0",
|
|
4
18
|
"d": "24/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "New get_global_section tool returns a compact tree representation of a single global section (Header/Footer/block), listing each element's id, type,…",
|
|
28
42
|
"vi": "Tool mới get_global_section trả về cây phần tử dạng compact của một global section (Header/Footer/block), liệt kê id, type, text, class, binding và…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.14.0",
|
|
32
|
-
"d": "24/06/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "New list_events tool returns the authoritative interaction-events catalog: 9 triggers (click, hover, submit, success, ...) and 38 actions…",
|
|
35
|
-
"vi": "Tool mới list_events trả về danh mục sự kiện tương tác đầy đủ: 9 trigger (click, hover, submit, success, ...) và 38 action (open_page, scroll_to,…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.13.0",
|
|
39
|
-
"d": "24/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "New restore_file_version tool rolls a CMS file back to a saved version in one step: it reads the chosen version's content (via get_file_versions)…",
|
|
42
|
-
"vi": "Tool mới restore_file_version khôi phục CMS file về một phiên bản đã lưu chỉ trong một bước: đọc nội dung phiên bản được chọn (qua…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/guides.js
CHANGED
|
@@ -95,6 +95,32 @@ they pick up site_id. Below is EXACTLY what each call sends to the backend + wha
|
|
|
95
95
|
automation set up on the site — find it with the MCP tool list_automations.
|
|
96
96
|
data is the payload passed into that automation/email template.
|
|
97
97
|
|
|
98
|
+
## File / media uploads → CDN url
|
|
99
|
+
Three ways, all returning a permanent CDN url:
|
|
100
|
+
|
|
101
|
+
1) A client POSTs a real file (multipart/form-data) to your function — it is auto-stored on
|
|
102
|
+
the CDN and request.params.<field> / request.data.<field> is simply its CDN url (string).
|
|
103
|
+
Use it directly, no upload call needed. (A function never sees raw file bytes.)
|
|
104
|
+
|
|
105
|
+
2) Upload from inside the function with '@webcake/media':
|
|
106
|
+
import { upload } from '@webcake/media';
|
|
107
|
+
const url = await upload(request, { url: someRemoteUrl }); // rehost a link
|
|
108
|
+
const url = await upload(request, { base64, content_type }); // bytes you hold
|
|
109
|
+
- { url } — rehost a remote file (image/video/any); the SERVER fetches it (no base64).
|
|
110
|
+
Use for an AI-generated image url, an external API's file, etc.
|
|
111
|
+
- { base64, content_type } — content the function built/holds (data URI or raw base64;
|
|
112
|
+
content_type e.g. "image/png", "application/pdf"). Throws on failure.
|
|
113
|
+
|
|
114
|
+
3) Direct call (admin/server, with the site's cms admin token):
|
|
115
|
+
POST /api/v1/cms_function/{site_id}/media/upload (Authorization: Bearer <token>)
|
|
116
|
+
multipart field "file" OR JSON { url } OR JSON { base64, content_type }
|
|
117
|
+
→ { success: true, data: "<cdn url>" }
|
|
118
|
+
|
|
119
|
+
All three save the file into the site's media library under a dedicated, non-deletable
|
|
120
|
+
folder ("API Uploads") and count toward the site's storage quota (BASIC < 3GB, STANDARD
|
|
121
|
+
< 8GB, PRO unlimited). Over quota: upload() / the direct call return an error; a multipart
|
|
122
|
+
file POSTed to a function comes through as null for that field.
|
|
123
|
+
|
|
98
124
|
## Sandbox globals (no import)
|
|
99
125
|
- fetch(url, options) — HTTP requests; response.ok/status/text()/json().
|
|
100
126
|
- URLSearchParams — build/parse query strings.
|
package/dist/server.js
CHANGED
|
@@ -10,10 +10,12 @@ You can also BUILD pages: use get_build_guide, list_elements, get_element to lea
|
|
|
10
10
|
|
|
11
11
|
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.
|
|
12
12
|
|
|
13
|
+
INTAKE FIRST — when the user asks to BUILD/CREATE a site, store, or page (a fresh build, not a small edit or a data question), do NOT jump straight to create_site/build_page on the same turn. Act as a professional shop-website designer doing client work: first ask ONE short batch of questions (call get_intake_guide for the exact list), restate the plan you'll build, get a "yes", THEN build. The shop owner is an ORDINARY person, not a designer — ask in plain words and visual outcomes ("tông nâu ấm, ảnh sản phẩm to"), never jargon. Ask the essentials (with sensible defaults so they answer fast): what they sell + a few products & prices, brand/shop name, primary color/style, which pages they need, real contact info (hotline/Zalo, address, email, hours) + the main call-to-action, and any promotion. NEVER invent or silently placeholder real data (shop name, products, prices, phone, address) — ask for it; placeholder only what the user explicitly skips, and tell them what to fill in. Skip intake only when the user already gave the brief, says "just do it / tự quyết", or it's a tiny edit.
|
|
14
|
+
|
|
13
15
|
Workflow:
|
|
14
|
-
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).
|
|
16
|
+
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, run the INTAKE above (get_intake_guide), then create_site makes a new site and switches to it; then build a homepage with build_page (type:'main', is_homepage:true).
|
|
15
17
|
2. Before answering a site-specific question, query the relevant tool (use search_tools if it isn't loaded directly).
|
|
16
|
-
3. When building a page, read get_build_guide first and validate before saving.
|
|
18
|
+
3. When building a page, read get_build_guide first and validate before saving. After saving/editing, call publish_site to take changes live — it also rebuilds the storefront CSS (a plain page-source save alone does not).
|
|
17
19
|
4. Always reply in the user's language; keep Vietnamese with full diacritics.`;
|
|
18
20
|
function makeResult(data) {
|
|
19
21
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
@@ -70,10 +70,12 @@ Images must be HOSTED URLs — get them from search_images or upload_images firs
|
|
|
70
70
|
name,
|
|
71
71
|
variations: vars,
|
|
72
72
|
// These MUST be arrays — the backend does Enum.reduce over them and 500s on nil.
|
|
73
|
+
// (product_attributes is reduced by get_all_attrs on the storefront product-attrs
|
|
74
|
+
// endpoint, so it must be [] — never omitted — even for a no-variation product.)
|
|
73
75
|
categories: category_ids || [],
|
|
74
76
|
ribbons: [],
|
|
77
|
+
product_attributes: attributes || [],
|
|
75
78
|
...(description ? { description } : {}),
|
|
76
|
-
...(attributes ? { product_attributes: attributes } : {}),
|
|
77
79
|
...(images && images.length ? { image: images[0] } : {}),
|
|
78
80
|
};
|
|
79
81
|
const res = await api.createProduct(productParams);
|
package/dist/tools/context.js
CHANGED
|
@@ -20,6 +20,26 @@ export function getConfirmMode() {
|
|
|
20
20
|
}
|
|
21
21
|
// ── Tools ──
|
|
22
22
|
export function registerContextTools(server, api, handle) {
|
|
23
|
+
server.tool("get_intake_guide", "Get the INTAKE questionnaire + build flow to run BEFORE creating a new site/store/page. Call this at the start of any fresh build: ask the user this one short batch (plain words, with defaults), restate the plan, get a yes, THEN build. Skip only for tiny edits, data questions, or when the user already gave the brief / says 'just do it'.", {}, () => handle(async () => ({
|
|
24
|
+
how_to_use: "Ask these as ONE friendly batch in the user's language (Vietnamese = full diacritics). Talk like a shop-website consultant to a non-designer: plain words and visual outcomes, no jargon. Offer the defaults so they can answer fast or just say 'theo gợi ý'. Then restate the plan (shop name + pages + colour/tone + main CTA) and WAIT for confirmation before building. Never invent or silently placeholder real data — ask for it; only placeholder what the user explicitly skips and tell them what to fill in.",
|
|
25
|
+
questions: [
|
|
26
|
+
{ key: "business", ask: "Bạn bán gì? Kể 3–6 sản phẩm tiêu biểu kèm giá (và giá gốc nếu có khuyến mãi).", why: "Tạo danh mục + sản phẩm thật để lưới sản phẩm hiển thị đúng.", required: true },
|
|
27
|
+
{ key: "brand", ask: "Tên shop/thương hiệu là gì? Có logo hay slogan không?", default: "Dùng tên bạn cung cấp; chưa có logo thì để chữ.", required: true },
|
|
28
|
+
{ key: "look", ask: "Bạn thích tông màu / phong cách nào? (ví dụ: nâu ấm cà phê, pastel nhẹ nhàng, tối hiện đại)", default: "Gợi ý một tông hợp ngành hàng để bạn duyệt.", required: false },
|
|
29
|
+
{ key: "pages", ask: "Cần những trang nào? Mặc định: Trang chủ + Cửa hàng (danh mục, chi tiết SP, giỏ hàng, thanh toán, cảm ơn). Thêm Giới thiệu / Blog / Liên hệ?", default: "Trang chủ + bộ trang cửa hàng chuẩn.", required: false },
|
|
30
|
+
{ key: "contact", ask: "Thông tin liên hệ thật: hotline/Zalo, địa chỉ, email, giờ mở cửa — và nút hành động chính (Mua ngay / Gọi đặt / Nhắn Zalo)?", why: "Hiển thị ở header/footer/CTA — không bịa.", required: true },
|
|
31
|
+
{ key: "promo", ask: "Có khuyến mãi hay điểm bán hàng nổi bật để làm CTA không? (ví dụ: giảm 10% đơn đầu, freeship từ 300k)", default: "Bỏ qua nếu chưa có.", required: false },
|
|
32
|
+
],
|
|
33
|
+
recommended_flow: [
|
|
34
|
+
"create_site (tên + slug) → tự chuyển sang site mới",
|
|
35
|
+
"create_product_category + create_product cho từng sản phẩm (ảnh từ search_images/upload_images trước)",
|
|
36
|
+
"build_page trang chủ (type:'main', is_homepage:true) với hero, lưới sản phẩm, câu chuyện, CTA, …",
|
|
37
|
+
"scaffold_store_pages để tạo trang danh mục/chi tiết/giỏ/thanh toán/cảm ơn",
|
|
38
|
+
"Tách Header/Footer thành global section (create_global_section) để dùng chung mọi trang",
|
|
39
|
+
"publish_site (cũng rebuild CSS storefront)",
|
|
40
|
+
],
|
|
41
|
+
notes: "Sau khi build xong, QA trên builder editor (app_base/editor/:site_id) hoặc storefront đã publish; publish_site sẽ rebuild CSS để hết tình trạng trang thiếu style.",
|
|
42
|
+
})));
|
|
23
43
|
server.tool("get_current_context", "Show current connection context: which site_id, API URL, session, and account info. Call this first to confirm you're working on the right site", {}, () => handle(async () => {
|
|
24
44
|
const [me, site] = await Promise.all([
|
|
25
45
|
api.getMe().catch(() => null),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.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",
|