webcake-storefront-mcp 1.32.0 → 1.33.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/changelog.json +14 -14
- package/dist/tools/articles.js +3 -5
- package/dist/tools/catalog-write.js +9 -17
- package/dist/tools/registry.js +23 -5
- package/package.json +1 -1
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.33.1",
|
|
4
|
+
"d": "14/10/2026",
|
|
5
|
+
"type": "Changed",
|
|
6
|
+
"en": "create_product, create_product_category, create_blog_category, and create_article no longer accept a seo parameter; set SEO afterwards with…",
|
|
7
|
+
"vi": "create_product, create_product_category, create_blog_category và create_article không còn nhận tham số seo; hãy đặt SEO sau khi tạo bằng…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.33.0",
|
|
11
|
+
"d": "14/10/2026",
|
|
12
|
+
"type": "Added",
|
|
13
|
+
"en": "Every tool, whether registered natively or reached through the search gateway (search_tools/invoke_tool/list_tool_groups), now reports readOnlyHint…",
|
|
14
|
+
"vi": "Mọi tool, dù được đăng ký native hay được tiếp cận qua search gateway (search_tools/invoke_tool/list_tool_groups), nay đều có thêm annotation…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.32.0",
|
|
4
18
|
"d": "14/10/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Changed",
|
|
27
41
|
"en": "create_site and create_site_from_template now stamp the new site with by_ai: \"mcp\" (persisted to the backend sites.by_ai column), extending the…",
|
|
28
42
|
"vi": "create_site và create_site_from_template nay đánh dấu site mới với by_ai: \"mcp\" (lưu vào cột sites.by_ai ở backend), mở rộng dấu hiệu AI-tạo vốn chỉ…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.31.10",
|
|
32
|
-
"d": "08/09/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "list_articles accepts a term parameter to search articles by title or slug, and its response now includes page and limit alongside total.",
|
|
35
|
-
"vi": "list_articles nay nhận thêm tham số term để tìm bài viết theo tiêu đề hoặc slug, và kết quả trả về nay có thêm page và limit bên cạnh total."
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.31.9",
|
|
39
|
-
"d": "07/09/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "Pages created by create_page, build_page, and commit_page_draft are now stamped with by_ai: \"mcp\" (persisted to the backend pages.by_ai column) so…",
|
|
42
|
-
"vi": "Các trang được tạo bởi create_page, build_page và commit_page_draft nay được đánh dấu by_ai: \"mcp\" (lưu vào cột pages.by_ai ở backend) để builder có…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/tools/articles.js
CHANGED
|
@@ -144,14 +144,14 @@ that view is the public one, so hidden posts and posts scheduled in the future a
|
|
|
144
144
|
Built via the dashboard command pipeline: title + optional summary, HTML content, image URLs, and
|
|
145
145
|
category linkage. Pass category_ids from create_blog_category / list articles' categories so the
|
|
146
146
|
post shows up under those categories (it is also auto-filed under the default category). Image URLs
|
|
147
|
-
must be hosted (search_images / upload_images). The backend generates the id and slug
|
|
147
|
+
must be hosted (search_images / upload_images). The backend generates the id and slug.
|
|
148
|
+
SEO: set it afterwards with update_article({ id, seo }).`, {
|
|
148
149
|
name: z.string().describe("Article title"),
|
|
149
150
|
content: z.string().optional().describe("HTML content of the post"),
|
|
150
151
|
summary: z.string().optional().describe("Short summary / excerpt"),
|
|
151
152
|
images: z.array(z.string()).optional().describe("Hosted image URLs; the first is the cover image"),
|
|
152
153
|
category_ids: z.array(z.string()).optional().describe("Blog category IDs to file the post under (from create_blog_category)"),
|
|
153
|
-
|
|
154
|
-
}, ({ name, content, summary, images, category_ids, seo }) => handle(async () => {
|
|
154
|
+
}, ({ name, content, summary, images, category_ids }) => handle(async () => {
|
|
155
155
|
const id = randomUUID();
|
|
156
156
|
const commands = [{ name: "create_article", data: { id, name } }];
|
|
157
157
|
if (summary)
|
|
@@ -162,8 +162,6 @@ must be hosted (search_images / upload_images). The backend generates the id and
|
|
|
162
162
|
commands.push({ name: "content_article", data: { id, content } });
|
|
163
163
|
if (category_ids && category_ids.length)
|
|
164
164
|
commands.push({ name: "bulk_add_category_to_article", data: { id, ids: category_ids } });
|
|
165
|
-
if (seo)
|
|
166
|
-
commands.push({ name: "set_article_seo", data: { id, meta_tags: mergeSeoTags(seo) } });
|
|
167
165
|
await api.createBlogArticle(commands);
|
|
168
166
|
return {
|
|
169
167
|
success: true,
|
|
@@ -56,7 +56,8 @@ export function registerCatalogWriteTools(server, api, handle) {
|
|
|
56
56
|
server.tool("create_product", `Create a product so the storefront has real merchandise (grid-product / slider-product bindings need this).
|
|
57
57
|
Simple use: pass name + price (+ images, category_ids). One default variation with the price/stock is created for you.
|
|
58
58
|
Advanced use: pass attributes (e.g. Color/Size) + variations for a multi-SKU product.
|
|
59
|
-
Images must be HOSTED URLs — get them from search_images or upload_images first. The backend generates id, slug and publishes the product
|
|
59
|
+
Images must be HOSTED URLs — get them from search_images or upload_images first. The backend generates id, slug and publishes the product.
|
|
60
|
+
SEO: set it afterwards with update_product({ product_id, seo }).`, {
|
|
60
61
|
name: z.string().describe("Product name"),
|
|
61
62
|
price: z.number().optional().describe("Selling price (used when you don't pass variations). Required unless variations are given."),
|
|
62
63
|
original_price: z.number().optional().describe("List/compare-at price for the simple single-variation case"),
|
|
@@ -71,8 +72,7 @@ Images must be HOSTED URLs — get them from search_images or upload_images firs
|
|
|
71
72
|
.optional()
|
|
72
73
|
.describe("Variant axes, e.g. [{name:'Color',values:['Đen','Trắng']},{name:'Size',values:['S','M','L']}]"),
|
|
73
74
|
variations: z.array(variationSpec).optional().describe("Explicit per-SKU variations. Omit to auto-build one from price/stock/sku."),
|
|
74
|
-
|
|
75
|
-
}, ({ name, price, original_price, stock, sku, images, description, short_description, category_ids, attributes, variations, seo }) => handle(async () => {
|
|
75
|
+
}, ({ name, price, original_price, stock, sku, images, description, short_description, category_ids, attributes, variations }) => handle(async () => {
|
|
76
76
|
let vars = variations;
|
|
77
77
|
// Enrich attribute axes with the id + keyword shape the storefront variation selector
|
|
78
78
|
// needs (a raw {name,values} declares the axis but attaches it to no variation).
|
|
@@ -146,11 +146,7 @@ Images must be HOSTED URLs — get them from search_images or upload_images firs
|
|
|
146
146
|
// Success response is { product: {...} } at the top level (not under data).
|
|
147
147
|
const prod = res?.product || res?.data?.product || res?.data?.attributes || res?.data || {};
|
|
148
148
|
const productId = prod?.id || null;
|
|
149
|
-
const warning = seo && productId
|
|
150
|
-
? await applyProductSeo(api, productId, seo).then(() => undefined, (e) => `Product created, but SEO failed: ${e?.message ?? e}`)
|
|
151
|
-
: undefined;
|
|
152
149
|
return {
|
|
153
|
-
...(warning ? { warning } : {}),
|
|
154
150
|
success: true,
|
|
155
151
|
product_id: productId,
|
|
156
152
|
name,
|
|
@@ -161,13 +157,13 @@ Images must be HOSTED URLs — get them from search_images or upload_images firs
|
|
|
161
157
|
};
|
|
162
158
|
}));
|
|
163
159
|
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).
|
|
164
|
-
Returns the new category id — pass it to create_product's category_ids. Image must be a hosted URL
|
|
160
|
+
Returns the new category id — pass it to create_product's category_ids. Image must be a hosted URL.
|
|
161
|
+
SEO: set it afterwards with update_product_category({ id, seo }).`, {
|
|
165
162
|
name: z.string().describe("Category name"),
|
|
166
163
|
image: z.string().optional().describe("Hosted image URL for the category card"),
|
|
167
164
|
description: z.string().optional().describe("Category description"),
|
|
168
165
|
parent_id: z.string().optional().describe("Parent category id for a sub-category"),
|
|
169
|
-
|
|
170
|
-
}, ({ name, image, description, parent_id, seo }) => handle(async () => {
|
|
166
|
+
}, ({ name, image, description, parent_id }) => handle(async () => {
|
|
171
167
|
const id = randomUUID();
|
|
172
168
|
const commands = [
|
|
173
169
|
{ name: "create_category", data: { id, name, ...(parent_id ? { parent_id } : {}) } },
|
|
@@ -180,25 +176,21 @@ Returns the new category id — pass it to create_product's category_ids. Image
|
|
|
180
176
|
data: { id, multi_description: [{ id: randomUUID(), title: name, description }] },
|
|
181
177
|
});
|
|
182
178
|
}
|
|
183
|
-
if (seo)
|
|
184
|
-
commands.push({ name: "set_category_seo", data: { id, meta_tags: mergeSeoTags(seo) } });
|
|
185
179
|
await api.createProductCategory(commands);
|
|
186
180
|
return { success: true, category_id: id, name, ...(parent_id ? { parent_id } : {}) };
|
|
187
181
|
}));
|
|
188
|
-
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
|
|
182
|
+
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.
|
|
183
|
+
SEO: set it afterwards with update_blog_category({ id, seo }).`, {
|
|
189
184
|
name: z.string().describe("Blog category name"),
|
|
190
185
|
image: z.string().optional().describe("Hosted image URL"),
|
|
191
186
|
description: z.string().optional().describe("Category description"),
|
|
192
|
-
|
|
193
|
-
}, ({ name, image, description, seo }) => handle(async () => {
|
|
187
|
+
}, ({ name, image, description }) => handle(async () => {
|
|
194
188
|
const id = randomUUID();
|
|
195
189
|
const commands = [{ name: "create_category", data: { id, name } }];
|
|
196
190
|
if (description)
|
|
197
191
|
commands.push({ name: "description_category", data: { id, description } });
|
|
198
192
|
if (image)
|
|
199
193
|
commands.push({ name: "image_category", data: { id, image } });
|
|
200
|
-
if (seo)
|
|
201
|
-
commands.push({ name: "set_category_seo", data: { id, meta_tags: mergeSeoTags(seo) } });
|
|
202
194
|
await api.createBlogCategory(commands);
|
|
203
195
|
return { success: true, category_id: id, name };
|
|
204
196
|
}));
|
package/dist/tools/registry.js
CHANGED
|
@@ -49,6 +49,7 @@ export const GROUPS = [
|
|
|
49
49
|
{ name: "apps_advanced", title: "Advanced apps", desc: "Product design, personal product design (PPD), course app" },
|
|
50
50
|
{ name: "channels", title: "Sale channels", desc: "Sitemap sync, partner product feeds, Google merchants" },
|
|
51
51
|
{ name: "automation", title: "Automation", desc: "Marketing automations and send-mail" },
|
|
52
|
+
{ name: "image_seo", title: "Image alt / SEO audit", desc: "Scan site images, read them, bulk-fill and set alt text" },
|
|
52
53
|
];
|
|
53
54
|
/** Each tool module mapped to its group. Order matches the original registration order. */
|
|
54
55
|
const MODULES = [
|
|
@@ -80,7 +81,7 @@ const MODULES = [
|
|
|
80
81
|
["web", registerGlobalSectionTools],
|
|
81
82
|
["web", registerGlobalSectionWriteTools],
|
|
82
83
|
["session", registerResultCacheTools],
|
|
83
|
-
["
|
|
84
|
+
["image_seo", registerImageTools],
|
|
84
85
|
["web", registerBuilderTools],
|
|
85
86
|
["web", registerBuilderExtraTools],
|
|
86
87
|
["web", registerPageDraftTools],
|
|
@@ -119,6 +120,21 @@ export function resolveNativeGroups(spec) {
|
|
|
119
120
|
}
|
|
120
121
|
return set;
|
|
121
122
|
}
|
|
123
|
+
/** MCP tool annotations derived from the verb prefix, so clients can auto-allow read-only
|
|
124
|
+
* tools and warn on destructive ones. Unlisted verbs (update/upsert/set/publish…) keep the
|
|
125
|
+
* spec defaults (a non-read-only tool is assumed possibly destructive). */
|
|
126
|
+
const READ_ONLY = /^(list|get|search|read|query|find|count|semantic|check|validate|scan|fetch|new|ingest)_/;
|
|
127
|
+
const DESTRUCTIVE = /^(delete|remove|uninstall|empty|clear|block|unblock|refuse)_/;
|
|
128
|
+
const ADDITIVE = /^(create|add|upload|install|invite|duplicate|save)_/;
|
|
129
|
+
export function toolAnnotations(name) {
|
|
130
|
+
if (READ_ONLY.test(name))
|
|
131
|
+
return { readOnlyHint: true };
|
|
132
|
+
if (DESTRUCTIVE.test(name))
|
|
133
|
+
return { readOnlyHint: false, destructiveHint: true };
|
|
134
|
+
if (ADDITIVE.test(name))
|
|
135
|
+
return { readOnlyHint: false, destructiveHint: false };
|
|
136
|
+
return { readOnlyHint: false };
|
|
137
|
+
}
|
|
122
138
|
const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
123
139
|
/** Build a compact capability index (group list with loaded/on-demand tags) to inject into the
|
|
124
140
|
* system prompt so the model always knows which domains exist and when to use search_tools. */
|
|
@@ -150,7 +166,7 @@ export function installTools(server, api, handle, opts = {}) {
|
|
|
150
166
|
let nativeCount = 0;
|
|
151
167
|
for (const e of collector.entries) {
|
|
152
168
|
if (native.has(e.group)) {
|
|
153
|
-
server.tool(e.name, e.description, e.shape, e.handler);
|
|
169
|
+
server.tool(e.name, e.description, e.shape, toolAnnotations(e.name), e.handler);
|
|
154
170
|
nativeCount++;
|
|
155
171
|
}
|
|
156
172
|
}
|
|
@@ -167,7 +183,7 @@ export function installTools(server, api, handle, opts = {}) {
|
|
|
167
183
|
}
|
|
168
184
|
return schemaCache.get(e.name);
|
|
169
185
|
};
|
|
170
|
-
server.tool("list_tool_groups", "List every WebCake tool group, how many tools it has, and whether it is loaded natively or reached on-demand via search_tools + invoke_tool.", {}, () => handle(async () => GROUPS.map((g) => ({
|
|
186
|
+
server.tool("list_tool_groups", "List every WebCake tool group, how many tools it has, and whether it is loaded natively or reached on-demand via search_tools + invoke_tool.", {}, toolAnnotations("list_tool_groups"), () => handle(async () => GROUPS.map((g) => ({
|
|
171
187
|
name: g.name,
|
|
172
188
|
title: g.title,
|
|
173
189
|
description: g.desc,
|
|
@@ -180,7 +196,7 @@ courses, sale channels, automation…). Returns name, group, description and JSO
|
|
|
180
196
|
query: z.string().describe("Keywords, e.g. 'affiliate payout', 'translate product', 'add domain ssl', 'block phone'"),
|
|
181
197
|
group: z.string().optional().describe("Restrict to a group name (see list_tool_groups)"),
|
|
182
198
|
limit: z.number().optional().describe("Max results (default 8)"),
|
|
183
|
-
}, ({ query, group, limit }) => handle(async () => {
|
|
199
|
+
}, toolAnnotations("search_tools"), ({ query, group, limit }) => handle(async () => {
|
|
184
200
|
const ql = query.toLowerCase();
|
|
185
201
|
const terms = ql.split(/\s+/).filter(Boolean);
|
|
186
202
|
const PREFIX_MIN = 4;
|
|
@@ -230,6 +246,7 @@ courses, sale channels, automation…). Returns name, group, description and JSO
|
|
|
230
246
|
name: e.name,
|
|
231
247
|
group: e.group,
|
|
232
248
|
native: native.has(e.group),
|
|
249
|
+
annotations: toolAnnotations(e.name),
|
|
233
250
|
description: e.description,
|
|
234
251
|
input_schema: jsonSchema(e),
|
|
235
252
|
}));
|
|
@@ -238,7 +255,8 @@ courses, sale channels, automation…). Returns name, group, description and JSO
|
|
|
238
255
|
Arguments are validated before running. Use this for tools that are not loaded natively.`, {
|
|
239
256
|
name: z.string().describe("Exact tool name (from search_tools)"),
|
|
240
257
|
arguments: z.record(z.any()).optional().describe("Arguments object matching the tool's input schema"),
|
|
241
|
-
},
|
|
258
|
+
}, toolAnnotations("invoke_tool"), // runs any tool, so never read-only
|
|
259
|
+
async ({ name, arguments: args }) => {
|
|
242
260
|
const e = registry.get(name);
|
|
243
261
|
if (!e)
|
|
244
262
|
return errorResult(`Error: unknown tool "${name}". Use search_tools to find available tools.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.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",
|