sbuilder-mcp 0.82.6 → 0.82.7
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/CHANGELOG.md +6 -0
- package/CHANGELOG.vi.md +6 -0
- package/README.md +1 -1
- package/dist/tools/page.js +76 -25
- package/dist/tools/pageupdate.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ All notable changes to this project are documented in this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.82.7] - 2026-10-11
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `sb_template_use` inserts a site's own template into the open page, instead of reporting success while the page stays empty; `dry_run` sends no request.
|
|
13
|
+
- `sb_page_update` accepts `default_template` and `render_for` on brand template pages.
|
|
14
|
+
|
|
9
15
|
## [0.82.6] - 2026-10-10
|
|
10
16
|
|
|
11
17
|
### Fixed
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,12 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
|
|
|
6
6
|
Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.82.7] - 2026-10-11
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `sb_template_use` chèn template của chính site vào trang đang mở, thay vì báo thành công mà trang vẫn trống; `dry_run` không gửi request nào.
|
|
13
|
+
- `sb_page_update` nhận `default_template` và `render_for` trên trang template brand.
|
|
14
|
+
|
|
9
15
|
## [0.82.6] - 2026-10-10
|
|
10
16
|
|
|
11
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
95
95
|
| `sb_remove` | Remove a node and its subtree |
|
|
96
96
|
| `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original — or onto another page or site, as the editor's copy/paste does (assets copied, references that cannot travel listed) |
|
|
97
97
|
| `sb_templates` | The store's saved section templates, plus the BUILT-IN layouts — hero, feature trio, stats, FAQ, CTA band, product shelf, category strip, brand wall, trust band — composed against the page's own tokens |
|
|
98
|
-
| `sb_template_use` |
|
|
98
|
+
| `sb_template_use` | Add a section template to a page |
|
|
99
99
|
| `sb_page_list` | Every page on the site |
|
|
100
100
|
| `sb_page_create` | Create a page — a store type arrives with the editor's own starting document; `type` is the route for checkout, product, category, post, course |
|
|
101
101
|
| `sb_page_update` | Edit a page's name, slug, home flag, order, SEO (title, description, Open Graph, robots, meta tags, JSON-LD) and members-only flag — merged into `settings`, so the keys it does not name survive. On a template page: make it the type's default, or choose which products/categories/posts render through it |
|
package/dist/tools/page.js
CHANGED
|
@@ -1024,6 +1024,20 @@ export class PageSession {
|
|
|
1024
1024
|
this.savedRev = d.rev;
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* A foreign subtree as a document `copyNodeInto` can read: a holder root over
|
|
1029
|
+
* it, so the copy re-mints every id and strips the shared-master stamps.
|
|
1030
|
+
*/
|
|
1031
|
+
function heldTree(rootId, nodes) {
|
|
1032
|
+
const holder = '__held';
|
|
1033
|
+
const rn = structuredClone(nodes);
|
|
1034
|
+
rn[rootId].data.parent = holder;
|
|
1035
|
+
return PageDoc.from({
|
|
1036
|
+
schema_version: 2,
|
|
1037
|
+
root_node_id: holder,
|
|
1038
|
+
nodes: { ...rn, [holder]: { id: holder, data: { type: 'root', parent: null, nodes: [rootId] } } },
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1027
1041
|
function formDocPath(siteId, formId) {
|
|
1028
1042
|
return `/api/sites/${encodeURIComponent(siteId)}/forms/${encodeURIComponent(formId)}/document`;
|
|
1029
1043
|
}
|
|
@@ -1902,15 +1916,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1902
1916
|
if (!r?.tree?.nodes || !r.tree.rootId || !(r.tree.rootId in r.tree.nodes)) {
|
|
1903
1917
|
throw new Error('sbuilder: the platform answered the reconcile with no tree — nothing was pasted.');
|
|
1904
1918
|
}
|
|
1905
|
-
|
|
1906
|
-
const holder = '__clipboard';
|
|
1907
|
-
const rn = structuredClone(r.tree.nodes);
|
|
1908
|
-
rn[r.tree.rootId].data.parent = holder;
|
|
1909
|
-
src = PageDoc.from({
|
|
1910
|
-
schema_version: 2,
|
|
1911
|
-
root_node_id: holder,
|
|
1912
|
-
nodes: { ...rn, [holder]: { id: holder, data: { type: 'root', parent: null, nodes: [r.tree.rootId] } } },
|
|
1913
|
-
});
|
|
1919
|
+
src = heldTree(r.tree.rootId, r.tree.nodes);
|
|
1914
1920
|
if (r.tree.rootId !== id)
|
|
1915
1921
|
id = r.tree.rootId;
|
|
1916
1922
|
reconciled = { copiedAssets: r.copiedAssets ?? 0, droppedRefs: r.droppedRefs ?? [] };
|
|
@@ -1950,7 +1956,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1950
1956
|
});
|
|
1951
1957
|
});
|
|
1952
1958
|
server.registerTool('sb_template_use', {
|
|
1953
|
-
description: '
|
|
1959
|
+
description: 'Add a section template to a page, above the footer — a sb_templates row (another site\'s is copied in first) or ' +
|
|
1954
1960
|
'one of the BUILT-IN layouts sb_templates lists, which are composed against this ' +
|
|
1955
1961
|
"page's own tokens rather than copied.",
|
|
1956
1962
|
inputSchema: {
|
|
@@ -1962,9 +1968,9 @@ export function registerPageTools(server, ctx) {
|
|
|
1962
1968
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1963
1969
|
}, async ({ site_id: given, template_id, page_id, dry_run }) => {
|
|
1964
1970
|
const site_id = siteFor(ctx, given);
|
|
1965
|
-
// A BUILT-IN IS NOT A SERVER COPY.
|
|
1966
|
-
//
|
|
1967
|
-
// as designed; a built-in has no row on the server, so it is composed HERE
|
|
1971
|
+
// A BUILT-IN IS NOT A SERVER COPY. A server row is a stored tree
|
|
1972
|
+
// (copied in by instantiate when another site owns it), which is why the
|
|
1973
|
+
// section arrives exactly as designed; a built-in has no row on the server, so it is composed HERE
|
|
1968
1974
|
// — against the target page's own tokens, which is the whole point of it
|
|
1969
1975
|
// being a pattern rather than a snapshot.
|
|
1970
1976
|
const pattern = PATTERN_BY_ID.get(template_id);
|
|
@@ -2068,23 +2074,68 @@ export function registerPageTools(server, ctx) {
|
|
|
2068
2074
|
: {}),
|
|
2069
2075
|
});
|
|
2070
2076
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2077
|
+
// A SERVER-SIDE TEMPLATE IS A TREE THIS SERVER INSERTS, as the editor does
|
|
2078
|
+
// (TemplateCards.vue). The site's own is placed straight from the list's
|
|
2079
|
+
// `document`; anyone else's (org, platform) goes through instantiate, which
|
|
2080
|
+
// reads NO body, touches no page, copies the template's images into this
|
|
2081
|
+
// site and answers `{ document, droppedRefs }`
|
|
2082
|
+
// (sectiontemplates/rest/rest.go). Either way the ids are minted fresh here,
|
|
2083
|
+
// as the editor's `cloneTree` does.
|
|
2084
|
+
const tplBase = `/api/sites/${encodeURIComponent(site_id)}/section-templates`;
|
|
2085
|
+
const listed = (await request({
|
|
2076
2086
|
base: ctx.base,
|
|
2077
|
-
method: '
|
|
2078
|
-
path,
|
|
2087
|
+
method: 'GET',
|
|
2088
|
+
path: tplBase,
|
|
2079
2089
|
token: siteToken(ctx),
|
|
2080
|
-
body: { pageId: page_id },
|
|
2081
2090
|
fetchImpl: ctx.fetchImpl,
|
|
2082
|
-
});
|
|
2091
|
+
}));
|
|
2092
|
+
const row = (listed.sectionTemplates ?? []).find((t) => t.id === template_id);
|
|
2093
|
+
if (!row) {
|
|
2094
|
+
throw new Error(`sbuilder: no template "${template_id}" on this site's shelf or among the built-ins — list them with sb_templates.`);
|
|
2095
|
+
}
|
|
2096
|
+
const own = row.source === 'site';
|
|
2097
|
+
const path = `${tplBase}/${encodeURIComponent(template_id)}/instantiate`;
|
|
2098
|
+
const insert = (tree, target) => {
|
|
2099
|
+
if (!tree?.nodes || !tree.root_node_id || !(tree.root_node_id in tree.nodes))
|
|
2100
|
+
return null;
|
|
2101
|
+
return copyNodeInto(heldTree(tree.root_node_id, tree.nodes), tree.root_node_id, target, target.doc.root_node_id, middleEnd(target.doc));
|
|
2102
|
+
};
|
|
2103
|
+
const doc = dry_run !== false
|
|
2104
|
+
? PageDoc.from((await loadSource(ctx, site_id, page_id)).document)
|
|
2105
|
+
: (await session.open(site_id, page_id), session.current());
|
|
2106
|
+
// Checked on the listed document BEFORE instantiate: that call copies media,
|
|
2107
|
+
// and a refusal after it would leave orphaned images in the library.
|
|
2108
|
+
const planned = insert(row.document, doc);
|
|
2109
|
+
const refuse = planned ? saveRefusal(doc, planned.patches).refusal : undefined;
|
|
2110
|
+
if (dry_run !== false) {
|
|
2111
|
+
return text({
|
|
2112
|
+
dry_run: true,
|
|
2113
|
+
...(refuse ? { would_refuse: refuse } : {}),
|
|
2114
|
+
would_add: row.name ?? template_id,
|
|
2115
|
+
...(planned ? { nodes: planned.ids.length } : {}),
|
|
2116
|
+
into: page_id,
|
|
2117
|
+
...(own ? {} : { would_post: path, note: "instantiate copies the template's images into this site first" }),
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
if (refuse)
|
|
2121
|
+
throw new Error(`sbuilder: ${refuse}`);
|
|
2122
|
+
let placed = planned;
|
|
2123
|
+
let dropped = [];
|
|
2124
|
+
if (!own) {
|
|
2125
|
+
const out = (await request({ base: ctx.base, method: 'POST', path, token: siteToken(ctx), fetchImpl: ctx.fetchImpl }));
|
|
2126
|
+
placed = insert(out.document, doc);
|
|
2127
|
+
dropped = out.droppedRefs ?? [];
|
|
2128
|
+
}
|
|
2129
|
+
if (!placed)
|
|
2130
|
+
throw new Error(`sbuilder: template "${template_id}" carries no usable document — nothing was added.`);
|
|
2131
|
+
await session.applyAndSave(placed.patches);
|
|
2083
2132
|
return text({
|
|
2084
|
-
|
|
2133
|
+
added: row.name ?? template_id,
|
|
2134
|
+
section: placed.ids[0],
|
|
2135
|
+
nodes: placed.ids.length,
|
|
2085
2136
|
into: page_id,
|
|
2086
|
-
|
|
2087
|
-
|
|
2137
|
+
rev: doc.rev,
|
|
2138
|
+
...(dropped.length ? { dropped_refs: dropped } : {}),
|
|
2088
2139
|
});
|
|
2089
2140
|
});
|
|
2090
2141
|
server.registerTool('sb_page_list', {
|
package/dist/tools/pageupdate.js
CHANGED
|
@@ -39,13 +39,14 @@ const LINK_TYPE_FOR_PAGE_TYPE = {
|
|
|
39
39
|
post: 'article',
|
|
40
40
|
blog: 'blogCategory',
|
|
41
41
|
course: 'course',
|
|
42
|
+
brand: 'brand',
|
|
42
43
|
};
|
|
43
44
|
const PUBLISH_NOTICE = 'Page settings are published verbatim: the storefront shows this change after sb_publish.';
|
|
44
45
|
export function registerPageUpdateTools(server, ctx) {
|
|
45
46
|
server.registerTool('sb_page_update', {
|
|
46
47
|
description: "Edit a page's name, slug, home flag, order, SEO (merged key by key) or members_only. " +
|
|
47
48
|
'Reads the page first and merges `settings`, so keys it does not name survive. On a ' +
|
|
48
|
-
'template page (product, category, post, blog, course): default_template makes it the ' +
|
|
49
|
+
'template page (product, category, post, blog, course, brand): default_template makes it the ' +
|
|
49
50
|
'type\'s default, render_for/render_default move entities onto it or back to the default.',
|
|
50
51
|
inputSchema: {
|
|
51
52
|
site_id: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.7",
|
|
4
4
|
"description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/sbuilder-mcp",
|
|
6
6
|
"type": "module",
|