webcake-storefront-mcp 1.31.1 → 1.31.3

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
@@ -353,25 +353,29 @@ export class WebcakeCmsApi {
353
353
  const headers = await this.cmsApiHeader();
354
354
  return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records`, { query, headers });
355
355
  }
356
- /** Insert a record into a collection. Body = the record fields. CMS-api-key authed. */
357
- async insertCollectionRecord(tableName, record) {
356
+ /** Create a collection (TABLE). VERIFIED body: { name, table_name } the table starts with
357
+ * only the system columns (id/inserted_at/updated_at/creator_id). Add custom columns after
358
+ * with updateCollectionSchema(). (Sending a schema in the create body 500s.) */
359
+ async createCollection(params) {
358
360
  const headers = await this.cmsApiHeader();
359
- return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records`, { body: record, headers });
361
+ return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/db_collections`, { body: params, headers, timeout: 60000 });
360
362
  }
361
- /** Update a record by id. Body = the changed fields. CMS-api-key authed. */
362
- async updateCollectionRecord(tableName, recordId, record) {
363
+ /** Get one collection by id (includes its full `schema`). */
364
+ async getCollectionById(id) {
363
365
  const headers = await this.cmsApiHeader();
364
- return this.request("PATCH", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records/${recordId}`, { body: record, headers });
366
+ return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/${id}`, { headers });
365
367
  }
366
- /** Delete a record by id. CMS-api-key authed. */
367
- async deleteCollectionRecord(tableName, recordId) {
368
+ /** Add/edit columns: PATCH the collection's FULL schema array. NOTE: this REPLACES the schema,
369
+ * so it MUST include the existing system columns + your custom ones. Each custom column =
370
+ * { name, type, display_name?, create_type:"custom", is_required?, is_unique? }. */
371
+ async updateCollectionSchema(id, schema) {
368
372
  const headers = await this.cmsApiHeader();
369
- return this.request("DELETE", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records/${recordId}`, { headers });
373
+ return this.request("PATCH", `/api/v1/dashboard/site/${this.siteId}/db_collections/${id}`, { body: { schema }, headers, timeout: 60000 });
370
374
  }
371
- /** Create a collection (table). Body: { name, schema:[{name,type,...}], ... }. */
372
- async createCollection(params) {
375
+ /** Delete a collection (table) by id. */
376
+ async deleteCollection(id) {
373
377
  const headers = await this.cmsApiHeader();
374
- return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/db_collections`, { body: params, headers, timeout: 60000 });
378
+ return this.request("DELETE", `/api/v1/dashboard/site/${this.siteId}/db_collections/${id}`, { headers });
375
379
  }
376
380
  // ── Blog Articles ──
377
381
  listArticles(query) {
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "v": "1.31.3",
4
+ "d": "26/06/2026",
5
+ "type": "Added",
6
+ "en": "New update_collection_columns tool reads the current collection schema and PATCHes it with the system columns plus the provided custom columns,…",
7
+ "vi": "Tool mới update_collection_columns đọc schema hiện tại của collection rồi PATCH lại với các cột hệ thống cộng các cột tùy chỉnh được cung cấp, cho…"
8
+ },
9
+ {
10
+ "v": "1.31.2",
11
+ "d": "26/06/2026",
12
+ "type": "Changed",
13
+ "en": "The HTTP_FUNCTION_GUIDE embedded in get_http_function and get_site_custom_code now includes a \"Common patterns\" section with battle-tested…",
14
+ "vi": "HTTP_FUNCTION_GUIDE được nhúng trong get_http_function và get_site_custom_code nay bổ sung phần \"Common patterns\" với các recipe đã được kiểm chứng…"
15
+ },
2
16
  {
3
17
  "v": "1.31.1",
4
18
  "d": "26/06/2026",
@@ -26,19 +40,5 @@
26
40
  "type": "Added",
27
41
  "en": "scaffold_global_sections now accepts a logo parameter (hosted image URL) that renders an image element in the header instead of the brand-name text…",
28
42
  "vi": "scaffold_global_sections nay nhận thêm tham số logo (URL hình ảnh được host) để hiển thị phần tử image trong header thay vì node text tên thương hiệu."
29
- },
30
- {
31
- "v": "1.28.0",
32
- "d": "26/06/2026",
33
- "type": "Added",
34
- "en": "New scaffold_popup tool builds and saves a designed newsletter/promo popup (heading + subtext + email subscribe form + close button, centred modal)…",
35
- "vi": "Tool mới scaffold_popup xây dựng và lưu một popup newsletter/promo được thiết kế sẵn (tiêu đề + phụ đề + form đăng ký email + nút đóng, dạng modal…"
36
- },
37
- {
38
- "v": "1.27.0",
39
- "d": "26/06/2026",
40
- "type": "Added",
41
- "en": "build_page now accepts an seo object (title, description, keyword, favicon, thumbnail) that is written to page.settings.seo with Open Graph…",
42
- "vi": "build_page nay nhận thêm đối tượng seo (title, description, keyword, favicon, thumbnail) và ghi vào page.settings.seo kèm mirroring Open Graph; hỗ…"
43
43
  }
44
44
  ]
package/dist/guides.js CHANGED
@@ -76,7 +76,33 @@ const count = await Members.countDocuments({ playspace: psId, status: { $in: [0,
76
76
  const created = await Members.create({ thanh_vien: userId, playspace: psId, status: 0 });
77
77
  await Members.updateOne({ id: created.id }, { tien_con_lai: 100000 });
78
78
 
79
- ## Built-in @webcake/* modules (first arg is always request; they auth via global.token)
79
+ ### Common patterns (battle-tested in production functions)
80
+ - FIND-OR-CREATE (ensure a row exists):
81
+ let row = await M.findOne({ key: v });
82
+ if (!row) row = await M.create({ key: v, ...defaults });
83
+ return row;
84
+ - UPSERT + read back the new version:
85
+ const updated = await M.findOneAndUpdate({ key: v }, { field: x }, { new: true });
86
+ - COUNT then DENORMALIZE onto a parent (cheap reads later):
87
+ const n = await Members.countDocuments({ playspace: psId, status: { $in: [0,1,2] } });
88
+ await PlaySpace.updateOne({ id: psId }, { so_thanh_vien: n });
89
+ - MULTI-TABLE write (no transactions — do the steps in order, validate first):
90
+ for (const it of items) await Members.updateOne({ id: it.id }, { tien_con_lai: it.bal });
91
+ await PlaySpace.updateOne({ id: psId }, { tien_con_lai: total });
92
+ await History.create({ playspace: psId, items }); // append an audit/history row
93
+ - SOFT DELETE (keep history) — set a status instead of deleteMany:
94
+ await M.updateOne({ id }, { status: INACTIVE }); // and filter it out with { status: { $ne: INACTIVE } }
95
+ - REFERENCE id helpers (a ref field is an id string, or an object after populate):
96
+ const toId = (v) => (v && typeof v === "object" ? String(v.id || "") : String(v || ""));
97
+ const toNumber = (v, d = 0) => (Number.isFinite(Number(v)) ? Number(v) : d);
98
+ - STATUS as numeric enums (define constants up top): const STATUS = { OWNER:0, ACTIVE:1, GUEST:2, INACTIVE:3 };
99
+ then query with { status: { $in: [STATUS.OWNER, STATUS.ACTIVE] } }.
100
+ - ALWAYS guard auth + wrap in try/catch returning a coded mess:
101
+ const userId = request.customer?.id ?? ""; if (!userId) return { mess: "NO_ACCOUNT_CALL" };
102
+ try { /* … */ return { mess: "OK", ...data }; } catch (err) { console.error(err?.message || err); return { mess: "SYSTEM_ERROR" }; }
103
+
104
+ ## Built-in @webcake/* modules (first arg is always request; they auth via global.token — these run
105
+ ## INSIDE the function sandbox, so they reach the /cms_function endpoints the dashboard JWT can't)
80
106
  Thin wrappers over the backend's /cms_function/{site_id}/... endpoints. Pass request so
81
107
  they pick up site_id. Below is EXACTLY what each call sends to the backend + what it returns.
82
108
 
@@ -96,15 +122,20 @@ they pick up site_id. Below is EXACTLY what each call sends to the backend + wha
96
122
  updateArticleById(request, id, data) PATCH .../blog/article/{id} (same fields) → response
97
123
  deleteArticleById(request, id) DELETE .../blog/article/{id} → response
98
124
 
99
- - '@webcake/customer' (backend: /cms_function/{site}/customer/…) → customer object ({} if none)
100
- findCustomerById(request, id) GET .../customer/identity/{id}
125
+ - '@webcake/customer' (backend: /cms_function/{site}/customer/…) → customer object ({} if none).
126
+ The object has at least { id, name, avatar, email, phone_number }. ALWAYS check \`customer?.id\`
127
+ before using it (returns {} when not found). Confirmed against a real production function.
128
+ findCustomerById(request, id) GET .../customer/identity/{id}
101
129
  findCustomerByPhone(request, phone) GET .../customer/phone/{phone}
102
130
  findCustomerByEmail(request, email) GET .../customer/email/{email}
131
+ Typical lookup-by-anything helper: try a code/sku table first, else normalize the phone and
132
+ call findCustomerByPhone, else (has "@") findCustomerByEmail, else findCustomerById.
103
133
 
104
- - '@webcake/promotion' (backend: /cms_function/{site}/promotion/add_bonus)
134
+ - '@webcake/promotion' (backend: /cms_function/{site}/promotion/add_bonus) — confirmed in real code
105
135
  addBonus(request, data) POST add_bonus → response.
106
136
  It ADDS REWARD POINTS to a customer. data: { customer_id (required),
107
137
  point (number, required), message? (defaults "Bạn được cộng điểm") }.
138
+ e.g. await addBonus(request, { customer_id: "abc", point: 10, message: "Cộng 10 điểm" }).
108
139
 
109
140
  - '@webcake/token' (backend: /external/oauth/token)
110
141
  getAccessToken(request) → access_token string (throws if none).
@@ -39,26 +39,52 @@ export function registerCollectionTools(server, api, handle) {
39
39
  query.order_by = order_by;
40
40
  return api.queryCollectionRecords(table_name, query);
41
41
  }));
42
- // ── Write tools (custom data CRUD). Endpoints follow the same /db_collections/collections/
43
- // {table}/records[/{id}] REST pattern as the verified read endpoint, with the CMS api-key
44
- // header. They mutate live data — run a small test first to confirm your field shape. ──
45
- server.tool("create_collection", "Create a new collection (custom data TABLE) so you can store/query arbitrary data. Pass a name + the field schema. The backend adds id/inserted_at/updated_at automatically.", {
46
- name: z.string().describe("Collection / table name (e.g. 'subscribers')."),
47
- schema: z
48
- .array(z.object({ name: z.string(), type: z.string().describe("Field type: string | text | integer | float | boolean | naive_datetime | binary_id | map | array"), required: z.boolean().optional() }))
49
- .describe("Field definitions, e.g. [{name:'email',type:'string',required:true},{name:'joined_at',type:'naive_datetime'}]."),
50
- }, ({ name, schema }) => handle(() => api.createCollection({ name, schema })));
51
- server.tool("insert_collection_record", "Insert a record into a collection (custom data table). `record` is a field→value object matching the table schema.", {
52
- table_name: z.string().describe("Collection table name."),
53
- record: z.record(z.any()).describe("Record fields, e.g. { email:'a@b.com', joined_at:'2026-06-26T10:00:00' }."),
54
- }, ({ table_name, record }) => handle(() => api.insertCollectionRecord(table_name, record)));
55
- server.tool("update_collection_record", "Update a record in a collection by id. `record` carries only the changed fields.", {
56
- table_name: z.string().describe("Collection table name."),
57
- record_id: z.string().describe("Record id to update."),
58
- record: z.record(z.any()).describe("Changed fields."),
59
- }, ({ table_name, record_id, record }) => handle(() => api.updateCollectionRecord(table_name, record_id, record)));
60
- server.tool("delete_collection_record", "Delete a record from a collection by id.", {
61
- table_name: z.string().describe("Collection table name."),
62
- record_id: z.string().describe("Record id to delete."),
63
- }, ({ table_name, record_id }) => handle(() => api.deleteCollectionRecord(table_name, record_id)));
42
+ // ── Table-management tools (VERIFIED live: create table, edit columns, delete table). ──
43
+ // Field type string | text | integer | float | boolean | naive_datetime | binary_id | map | array.
44
+ const COLUMN = z.object({
45
+ name: z.string().describe("Column name (snake_case)."),
46
+ type: z.string().describe("string | text | integer | float | boolean | naive_datetime | binary_id | map | array"),
47
+ display_name: z.string().optional(),
48
+ is_required: z.boolean().optional(),
49
+ is_unique: z.boolean().optional(),
50
+ });
51
+ server.tool("create_collection", "Create a new collection (custom data TABLE). It starts with the system columns (id/inserted_at/updated_at/creator_id); pass `columns` to add custom fields. NOTE: to WRITE records into it, use an HTTP function (webcake-data: db.model(table).create({...})) — the dashboard has no direct record-insert API. See get_http_function for the SDK guide.", {
52
+ name: z.string().describe("Display name."),
53
+ table_name: z.string().optional().describe("Table name (snake_case, unique). Defaults to name."),
54
+ columns: z.array(COLUMN).optional().describe("Custom columns to add, e.g. [{name:'email',type:'string'},{name:'amount',type:'integer'}]."),
55
+ }, ({ name, table_name, columns }) => handle(async () => {
56
+ const tn = (table_name || name).trim();
57
+ const created = await api.createCollection({ name, table_name: tn });
58
+ const col = (created && (created.data || created)) || {};
59
+ const id = col.id;
60
+ if (!id)
61
+ return { error: "Collection created but no id returned.", raw: created };
62
+ let schema = col.schema || [];
63
+ if (columns && columns.length) {
64
+ // PATCH the FULL schema = existing system columns + the new custom ones.
65
+ const custom = columns.map((c) => ({ ...c, display_name: c.display_name || c.name, create_type: "custom" }));
66
+ const res = await api.updateCollectionSchema(id, [...schema, ...custom]);
67
+ schema = (res && (res.data || res) || {}).schema || [...schema, ...custom];
68
+ }
69
+ return {
70
+ success: true,
71
+ collection_id: id,
72
+ table_name: col.table_name || tn,
73
+ columns: schema.map((f) => ({ name: f.name, type: f.type, kind: f.create_type || "system" })),
74
+ note: "Write rows via an HTTP function (db.model('" + (col.table_name || tn) + "').create({...})); query them with query_collection_records.",
75
+ };
76
+ }));
77
+ server.tool("update_collection_columns", "Add or change a collection's custom columns. Reads the current schema, then PATCHes it with the system columns + your custom columns (the PATCH replaces the whole schema, so omitting a column drops it).", {
78
+ collection_id: z.string().describe("Collection id (from list_collections / get_collection)."),
79
+ columns: z.array(COLUMN).describe("The FULL set of custom columns the table should have."),
80
+ }, ({ collection_id, columns }) => handle(async () => {
81
+ const cur = await api.getCollectionById(collection_id);
82
+ const schema = ((cur && (cur.data || cur)) || {}).schema || [];
83
+ const system = schema.filter((f) => (f.create_type || "system") === "system");
84
+ const custom = columns.map((c) => ({ ...c, display_name: c.display_name || c.name, create_type: "custom" }));
85
+ const res = await api.updateCollectionSchema(collection_id, [...system, ...custom]);
86
+ const newSchema = ((res && (res.data || res)) || {}).schema || [...system, ...custom];
87
+ return { success: true, collection_id, columns: newSchema.map((f) => ({ name: f.name, type: f.type, kind: f.create_type || "system" })) };
88
+ }));
89
+ server.tool("delete_collection", "Delete a collection (table) and all its records by id. Irreversible.", { collection_id: z.string().describe("Collection id.") }, ({ collection_id }) => handle(() => api.deleteCollection(collection_id)));
64
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-storefront-mcp",
3
- "version": "1.31.1",
3
+ "version": "1.31.3",
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",