sbuilder-mcp 0.24.0 → 0.25.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/CHANGELOG.md +5 -0
- package/CHANGELOG.vi.md +5 -0
- package/dist/tools/page.js +97 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,11 @@ 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.25.0] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- sb_page_create now attaches the site's own header and footer to a newly created page by default, read off the site's home page, so a page built with these tools carries the same navigation and footer as the rest of the site instead of arriving bare; pass chrome:false to skip it.
|
|
13
|
+
|
|
9
14
|
## [0.24.0] - 2026-09-10
|
|
10
15
|
|
|
11
16
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,11 @@ 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.25.0] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- sb_page_create giờ mặc định gắn header và footer của chính site vào trang mới tạo, đọc từ trang chủ của site, nên một trang được dựng bằng các tool này mang cùng menu điều hướng và footer với phần còn lại của site thay vì trống trơn; truyền chrome:false để bỏ qua.
|
|
13
|
+
|
|
9
14
|
## [0.24.0] - 2026-09-10
|
|
10
15
|
|
|
11
16
|
### Added
|
package/dist/tools/page.js
CHANGED
|
@@ -224,6 +224,49 @@ const specSchema = z.lazy(() => z.object({
|
|
|
224
224
|
specials: z.record(z.unknown()).optional(),
|
|
225
225
|
children: z.array(specSchema).optional(),
|
|
226
226
|
}));
|
|
227
|
+
/**
|
|
228
|
+
* THE SITE'S OWN HEADER AND FOOTER, read off the page that already answers it.
|
|
229
|
+
*
|
|
230
|
+
* A site can hold several globals of each kind — this one holds four headers and
|
|
231
|
+
* two footers, most of them experiments — so "the first header" is a guess and
|
|
232
|
+
* a name is a label nobody promised to keep. The HOME PAGE is the site's own
|
|
233
|
+
* answer: whatever chrome it carries is the chrome this site wears.
|
|
234
|
+
*
|
|
235
|
+
* Silent on anything it cannot read. A page created without its chrome is a page
|
|
236
|
+
* a person can fix; a page created with the WRONG chrome is one nobody notices.
|
|
237
|
+
*/
|
|
238
|
+
async function siteChrome(ctx, siteId) {
|
|
239
|
+
try {
|
|
240
|
+
const listed = (await request({
|
|
241
|
+
base: ctx.base,
|
|
242
|
+
method: 'GET',
|
|
243
|
+
path: `/api/sites/${encodeURIComponent(siteId)}/pages`,
|
|
244
|
+
token: siteToken(ctx),
|
|
245
|
+
fetchImpl: ctx.fetchImpl,
|
|
246
|
+
}));
|
|
247
|
+
const home = (listed.pages ?? []).find((p) => p.isHomepage === true);
|
|
248
|
+
if (!home || typeof home.id !== 'string')
|
|
249
|
+
return {};
|
|
250
|
+
const src = await loadSource(ctx, siteId, home.id);
|
|
251
|
+
const doc = PageDoc.from(src.document);
|
|
252
|
+
const out = {};
|
|
253
|
+
for (const id of doc.node(doc.doc.root_node_id).data.nodes) {
|
|
254
|
+
const sp = doc.doc.nodes[id]?.specials;
|
|
255
|
+
const gid = sp?.globalId;
|
|
256
|
+
const kind = sp?.globalKind;
|
|
257
|
+
if (typeof gid !== 'string')
|
|
258
|
+
continue;
|
|
259
|
+
if (kind === 'header' && !out.header)
|
|
260
|
+
out.header = gid;
|
|
261
|
+
if (kind === 'footer' && !out.footer)
|
|
262
|
+
out.footer = gid;
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
227
270
|
export function registerPageTools(server, ctx) {
|
|
228
271
|
const session = new PageSession(ctx);
|
|
229
272
|
server.registerTool('sb_page_open', {
|
|
@@ -821,12 +864,13 @@ export function registerPageTools(server, ctx) {
|
|
|
821
864
|
is_homepage: z.boolean().optional(),
|
|
822
865
|
settings: z.record(z.unknown()).optional(),
|
|
823
866
|
seed: z.boolean().optional().describe('Default true; false creates a blank page.'),
|
|
867
|
+
chrome: z.boolean().optional().describe("Carry the site's header and footer, default true"),
|
|
824
868
|
locale: z.string().optional().describe("vi (default) or en — the complete page's wording."),
|
|
825
869
|
headline: z.string().optional().describe("The complete page's thank-you line."),
|
|
826
870
|
dry_run: z.boolean().optional(),
|
|
827
871
|
},
|
|
828
872
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
829
|
-
}, async ({ site_id: given, name, type, slug, is_homepage, settings, seed, locale, headline, dry_run }) => {
|
|
873
|
+
}, async ({ site_id: given, name, type, slug, is_homepage, settings, seed, chrome, locale, headline, dry_run }) => {
|
|
830
874
|
const site_id = siteFor(ctx, given);
|
|
831
875
|
const path = `/api/sites/${encodeURIComponent(site_id)}/pages`;
|
|
832
876
|
// TYPE IS THE ROUTE for several kinds of page: /checkout and
|
|
@@ -851,12 +895,25 @@ export function registerPageTools(server, ctx) {
|
|
|
851
895
|
// exists.
|
|
852
896
|
const willSeed = seed !== false && hasSeed(type);
|
|
853
897
|
const summary = willSeed && type ? seedSummary(type) : null;
|
|
898
|
+
// WHAT MAKES THE NEW PAGE PART OF THE SITE.
|
|
899
|
+
//
|
|
900
|
+
// A page created through the editor carries the site's header and footer;
|
|
901
|
+
// one created here carried NEITHER, so an agent building a site produced
|
|
902
|
+
// pages with no navigation and no footer on a site that has both — and
|
|
903
|
+
// nothing reported it, because `sb_review` reads the page and the page is
|
|
904
|
+
// fine, while `siteChrome` asks whether the SITE has globals and it does.
|
|
905
|
+
// Measured: three pages built with these tools, every one of them bare,
|
|
906
|
+
// beside a store page carrying its header as ROOT's first child.
|
|
907
|
+
const wear = chrome !== false ? await siteChrome(ctx, site_id) : {};
|
|
854
908
|
if (dry_run !== false) {
|
|
855
909
|
return text({
|
|
856
910
|
dry_run: true,
|
|
857
911
|
would_post: path,
|
|
858
912
|
body: redact(body),
|
|
859
913
|
...(summary ? { would_seed: { type, ...summary } } : {}),
|
|
914
|
+
...(wear.header || wear.footer
|
|
915
|
+
? { would_wear: { ...(wear.header ? { header: wear.header } : {}), ...(wear.footer ? { footer: wear.footer } : {}) } }
|
|
916
|
+
: {}),
|
|
860
917
|
});
|
|
861
918
|
}
|
|
862
919
|
const res = redact(await request({
|
|
@@ -907,9 +964,48 @@ export function registerPageTools(server, ctx) {
|
|
|
907
964
|
}
|
|
908
965
|
}
|
|
909
966
|
}
|
|
967
|
+
// THE REFERENCE SHAPE IS THE PLATFORM'S OWN (decompose.go:382): a
|
|
968
|
+
// flex-section carrying `globalRef` + `globalKind`. Header FIRST and
|
|
969
|
+
// footer LAST, because compose turns them into real bands and ROOT's
|
|
970
|
+
// children must read header, middle, footer or every save is refused.
|
|
971
|
+
let wearing;
|
|
972
|
+
if ((wear.header || wear.footer) && typeof newId === 'string' && newId) {
|
|
973
|
+
try {
|
|
974
|
+
await session.open(site_id, newId);
|
|
975
|
+
const doc = session.current();
|
|
976
|
+
const patches = [];
|
|
977
|
+
const ids = [];
|
|
978
|
+
if (wear.header) {
|
|
979
|
+
const made = addSubtree(doc, doc.doc.root_node_id, {
|
|
980
|
+
type: 'flex-section',
|
|
981
|
+
specials: { globalRef: wear.header, globalKind: 'header' },
|
|
982
|
+
}, 0);
|
|
983
|
+
doc.apply(made.patches);
|
|
984
|
+
ids.push('header');
|
|
985
|
+
}
|
|
986
|
+
if (wear.footer) {
|
|
987
|
+
const made = addSubtree(doc, doc.doc.root_node_id, {
|
|
988
|
+
type: 'flex-section',
|
|
989
|
+
specials: { globalRef: wear.footer, globalKind: 'footer' },
|
|
990
|
+
});
|
|
991
|
+
doc.apply(made.patches);
|
|
992
|
+
ids.push('footer');
|
|
993
|
+
}
|
|
994
|
+
void patches;
|
|
995
|
+
await session.save();
|
|
996
|
+
wearing = { carries: ids, open: newId };
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
wearing = {
|
|
1000
|
+
failed: e.message.replace(/^sbuilder:\s*/, '').slice(0, 160),
|
|
1001
|
+
note: 'The page exists. Attach the chrome by hand, or create it again.',
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
910
1005
|
return text({
|
|
911
1006
|
...res,
|
|
912
1007
|
...(seeded ? { seeded } : {}),
|
|
1008
|
+
...(wearing ? { chrome: wearing } : {}),
|
|
913
1009
|
...(renamed
|
|
914
1010
|
? {
|
|
915
1011
|
slug_renamed: `The slug "${slug}" was already taken, so the platform stored ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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",
|