webcake-storefront-mcp 1.29.0 → 1.31.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 +38 -3
- package/dist/changelog.json +14 -14
- package/dist/tools/collections.js +34 -3
- package/dist/tools/customers.js +30 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -29,7 +29,7 @@ export class WebcakeCmsApi {
|
|
|
29
29
|
getBundleParams() {
|
|
30
30
|
return { token: this._adminToken, x_cms_api_key: this._cmsApiKey };
|
|
31
31
|
}
|
|
32
|
-
async request(method, path, { body, query, timeout } = {}) {
|
|
32
|
+
async request(method, path, { body, query, timeout, headers: extraHeaders } = {}) {
|
|
33
33
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
34
34
|
if (query) {
|
|
35
35
|
for (const [k, v] of Object.entries(query)) {
|
|
@@ -41,6 +41,7 @@ export class WebcakeCmsApi {
|
|
|
41
41
|
"Content-Type": "application/json",
|
|
42
42
|
Authorization: `Bearer ${this.token}`,
|
|
43
43
|
...(this.sessionId && { "x-session-id": this.sessionId }),
|
|
44
|
+
...(extraHeaders || {}),
|
|
44
45
|
};
|
|
45
46
|
const controller = new AbortController();
|
|
46
47
|
const timer = setTimeout(() => controller.abort(), timeout || DEFAULT_TIMEOUT);
|
|
@@ -340,8 +341,37 @@ export class WebcakeCmsApi {
|
|
|
340
341
|
getCollection(id) {
|
|
341
342
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/${id}`);
|
|
342
343
|
}
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
/** Header carrying the CMS api key — collection-data endpoints (records) need it on top of
|
|
345
|
+
* the dashboard JWT (without it they 401). Fetches the key once and caches it. */
|
|
346
|
+
async cmsApiHeader() {
|
|
347
|
+
await this.fetchCmsTokens();
|
|
348
|
+
return this._cmsApiKey ? { "x-cms-api-key": this._cmsApiKey } : {};
|
|
349
|
+
}
|
|
350
|
+
/** Query a collection's records. Needs the CMS api-key header (else 401). The records
|
|
351
|
+
* endpoint accepts page/limit plus `where` (a filter object/JSON) and `order_by`. */
|
|
352
|
+
async queryCollectionRecords(tableName, query) {
|
|
353
|
+
const headers = await this.cmsApiHeader();
|
|
354
|
+
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records`, { query, headers });
|
|
355
|
+
}
|
|
356
|
+
/** Insert a record into a collection. Body = the record fields. CMS-api-key authed. */
|
|
357
|
+
async insertCollectionRecord(tableName, record) {
|
|
358
|
+
const headers = await this.cmsApiHeader();
|
|
359
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records`, { body: record, headers });
|
|
360
|
+
}
|
|
361
|
+
/** Update a record by id. Body = the changed fields. CMS-api-key authed. */
|
|
362
|
+
async updateCollectionRecord(tableName, recordId, record) {
|
|
363
|
+
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 });
|
|
365
|
+
}
|
|
366
|
+
/** Delete a record by id. CMS-api-key authed. */
|
|
367
|
+
async deleteCollectionRecord(tableName, recordId) {
|
|
368
|
+
const headers = await this.cmsApiHeader();
|
|
369
|
+
return this.request("DELETE", `/api/v1/dashboard/site/${this.siteId}/db_collections/collections/${tableName}/records/${recordId}`, { headers });
|
|
370
|
+
}
|
|
371
|
+
/** Create a collection (table). Body: { name, schema:[{name,type,...}], ... }. */
|
|
372
|
+
async createCollection(params) {
|
|
373
|
+
const headers = await this.cmsApiHeader();
|
|
374
|
+
return this.request("POST", `/api/v1/dashboard/site/${this.siteId}/db_collections`, { body: params, headers, timeout: 60000 });
|
|
345
375
|
}
|
|
346
376
|
// ── Blog Articles ──
|
|
347
377
|
listArticles(query) {
|
|
@@ -505,6 +535,11 @@ export class WebcakeCmsApi {
|
|
|
505
535
|
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/combo_product/items`, { query: { combo_product_id: comboProductId, ...query } });
|
|
506
536
|
}
|
|
507
537
|
// ── Customers ──
|
|
538
|
+
/** List/search customers. Real shape: { customers: { data:[…], total_entries } }. Accepts
|
|
539
|
+
* page/limit and a `term` keyword (name/phone/email). Endpoint is /customer/all (singular). */
|
|
540
|
+
listCustomers(query) {
|
|
541
|
+
return this.request("GET", `/api/v1/dashboard/site/${this.siteId}/customer/all`, { query });
|
|
542
|
+
}
|
|
508
543
|
findCustomerById(id) {
|
|
509
544
|
return this.request("GET", `/api/v1/cms_function/${this.siteId}/customer/identity/${id}`);
|
|
510
545
|
}
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.31.0",
|
|
4
|
+
"d": "26/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New create_collection tool creates a custom data table by accepting a name and a schema array of field definitions (with types such as string,…",
|
|
7
|
+
"vi": "Tool mới create_collection tạo bảng dữ liệu tùy chỉnh bằng cách nhận tham số name và mảng schema chứa định nghĩa các trường (với các kiểu dữ liệu…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.30.0",
|
|
11
|
+
"d": "26/06/2026",
|
|
12
|
+
"type": "Added",
|
|
13
|
+
"en": "New list_customers tool browses or searches the site's customer list; accepts page, limit, and term (name/phone/email keyword) and returns a compact…",
|
|
14
|
+
"vi": "Tool mới list_customers duyệt hoặc tìm kiếm danh sách khách hàng của site; nhận các tham số page, limit và term (từ khóa tên/điện thoại/email) và…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.29.0",
|
|
4
18
|
"d": "26/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Fixed",
|
|
27
41
|
"en": "list_products now correctly unwraps the { products, total_product } API response shape, returning a product array and accurate total instead of…",
|
|
28
42
|
"vi": "list_products nay giải nén đúng kiểu phản hồi { products, total_product } từ API, trả về mảng sản phẩm và tổng chính xác thay vì trả thẳng đối tượng…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.26.0",
|
|
32
|
-
"d": "26/06/2026",
|
|
33
|
-
"type": "Fixed",
|
|
34
|
-
"en": "create_product now accepts a short_description parameter and sends it to the backend as [{description}] — the array shape the real product schema…",
|
|
35
|
-
"vi": "create_product nay nhận thêm tham số short_description và gửi lên backend dưới dạng [{description}] — đúng kiểu mảng mà schema sản phẩm thực tế yêu…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.25.0",
|
|
39
|
-
"d": "26/06/2026",
|
|
40
|
-
"type": "Changed",
|
|
41
|
-
"en": "list_elements now documents 16 previously-uncovered element types, completing curated attribute coverage across all 132 factory types: popup…",
|
|
42
|
-
"vi": "list_elements nay tài liệu hóa 16 kiểu phần tử trước đây chưa được ghi lại, hoàn thiện độ phủ thuộc tính curated cho toàn bộ 132 kiểu factory: popup…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -25,9 +25,40 @@ export function registerCollectionTools(server, api, handle) {
|
|
|
25
25
|
server.tool("get_collection", "Get a specific collection's details including full schema (field names, types, constraints, references) and records", {
|
|
26
26
|
id: z.string().describe("Collection ID"),
|
|
27
27
|
}, ({ id }) => handle(() => api.getCollection(id)));
|
|
28
|
-
server.tool("query_collection_records", "Query records from a collection by table name.
|
|
29
|
-
table_name: z.string().describe("Collection table name (e.g. 'subscribers', 'custom_orders')"),
|
|
28
|
+
server.tool("query_collection_records", "Query records from a collection (custom data table) by table name. Supports paging + an optional `where` filter and `order_by` sort. (Uses the CMS api-key auth the records endpoint requires.)", {
|
|
29
|
+
table_name: z.string().describe("Collection table name (e.g. 'subscribers', 'custom_orders') — from get_collection.table_name"),
|
|
30
30
|
page: z.number().optional().describe("Page number"),
|
|
31
31
|
limit: z.number().optional().describe("Items per page"),
|
|
32
|
-
|
|
32
|
+
where: z.record(z.any()).optional().describe("Filter object, e.g. { status: 'active' } — matches records by field value."),
|
|
33
|
+
order_by: z.string().optional().describe("Field to sort by (e.g. 'inserted_at')."),
|
|
34
|
+
}, ({ table_name, page, limit, where, order_by }) => handle(() => {
|
|
35
|
+
const query = { page: page ?? 1, limit: limit ?? 50 };
|
|
36
|
+
if (where && Object.keys(where).length)
|
|
37
|
+
query.where = JSON.stringify(where);
|
|
38
|
+
if (order_by)
|
|
39
|
+
query.order_by = order_by;
|
|
40
|
+
return api.queryCollectionRecords(table_name, query);
|
|
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)));
|
|
33
64
|
}
|
package/dist/tools/customers.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export function registerCustomerTools(server, api, handle) {
|
|
3
|
+
server.tool("list_customers", "List/search the site's customers (browse or segment). Pass `term` to search by name/phone/email. Returns name, phone, email, order_count, purchased_amount, reward_point, tags, last_order_at. Use find_customer for an exact id/phone/email lookup.", {
|
|
4
|
+
page: z.number().optional().describe("Page number (default 1)"),
|
|
5
|
+
limit: z.number().optional().describe("Items per page (default 50)"),
|
|
6
|
+
term: z.string().optional().describe("Keyword — searches name / phone / email"),
|
|
7
|
+
}, ({ page, limit, term }) => handle(async () => {
|
|
8
|
+
const query = { page: page ?? 1, limit: limit ?? 50 };
|
|
9
|
+
if (term && term.trim())
|
|
10
|
+
query.term = term.trim();
|
|
11
|
+
const res = await api.listCustomers(query);
|
|
12
|
+
// Real shape: { customers: { data:[…], total_entries } }.
|
|
13
|
+
const box = (res && res.customers) || res || {};
|
|
14
|
+
const list = (Array.isArray(box) ? box : box.data) || [];
|
|
15
|
+
if (!Array.isArray(list))
|
|
16
|
+
return res;
|
|
17
|
+
return {
|
|
18
|
+
data: list.map((c) => ({
|
|
19
|
+
id: c.id,
|
|
20
|
+
name: c.name,
|
|
21
|
+
phone_number: c.phone_number || c.recent_phone_number || undefined,
|
|
22
|
+
email: c.email || undefined,
|
|
23
|
+
order_count: c.order_count ?? undefined,
|
|
24
|
+
succeed_order_count: c.succeed_order_count ?? undefined,
|
|
25
|
+
purchased_amount: c.purchased_amount ?? undefined,
|
|
26
|
+
reward_point: c.reward_point ?? undefined,
|
|
27
|
+
tags: c.tags && c.tags.length ? c.tags : undefined,
|
|
28
|
+
last_order_at: c.last_order_at || undefined,
|
|
29
|
+
})),
|
|
30
|
+
total: (box.total_entries ?? res.total) || list.length,
|
|
31
|
+
};
|
|
32
|
+
}));
|
|
3
33
|
server.tool("find_customer", "Find a customer by ID, phone number, or email", {
|
|
4
34
|
by: z.enum(["id", "phone", "email"]).describe("Search field"),
|
|
5
35
|
value: z.string().describe("Search value"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.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",
|