webcake-storefront-mcp 1.17.1 → 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 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,
@@ -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.runtime.config && child.runtime.config.constraintX) || ["centerLeft"],
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.runtime.config && child.runtime.config.constraintX) || ["centerLeft"],
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 },
@@ -1,4 +1,11 @@
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
+ },
2
9
  {
3
10
  "v": "1.17.1",
4
11
  "d": "25/06/2026",
@@ -33,12 +40,5 @@
33
40
  "type": "Added",
34
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,…",
35
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à…"
36
- },
37
- {
38
- "v": "1.14.0",
39
- "d": "24/06/2026",
40
- "type": "Added",
41
- "en": "New list_events tool returns the authoritative interaction-events catalog: 9 triggers (click, hover, submit, success, ...) and 38 actions…",
42
- "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,…"
43
43
  }
44
44
  ]
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);
@@ -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.17.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",