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 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
@@ -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.24.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",