sbuilder-mcp 0.83.0 → 0.83.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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ 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.83.1] - 2026-10-11
10
+
11
+ ### Added
12
+ - `sb_bind` accepts `brand.*` and `blogCategory.*` binding sources, raising the known binding sources from 82 to 94.
13
+
14
+ ### Changed
15
+ - `sb_page_create` with the `brand` or `course` page type, or with `seed: false`, returns a `next` hint explaining how to build the page, since publishing an empty one makes every entity URL answer 500.
16
+
17
+ ### Fixed
18
+ - `sb_review` no longer reports `dead_binding_source` for valid `blogCategory.*` bindings.
19
+
9
20
  ## [0.83.0] - 2026-10-11
10
21
 
11
22
  ### Added
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,17 @@ 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.83.1] - 2026-10-11
10
+
11
+ ### Added
12
+ - `sb_bind` nhận nguồn binding `brand.*` và `blogCategory.*`, nâng số nguồn binding được biết từ 82 lên 94.
13
+
14
+ ### Changed
15
+ - `sb_page_create` với loại trang `brand` hoặc `course`, hoặc với `seed: false`, trả gợi ý `next` nêu cách dựng trang, vì publish trang rỗng khiến mọi URL thực thể trả 500.
16
+
17
+ ### Fixed
18
+ - `sb_review` không còn báo `dead_binding_source` cho binding `blogCategory.*` hợp lệ.
19
+
9
20
  ## [0.83.0] - 2026-10-11
10
21
 
11
22
  ### Added
package/README.md CHANGED
@@ -119,7 +119,7 @@ in this client: one would put the very key the platform exists to hold back into
119
119
  | `sb_undo` | Undo / redo this session's writes — page and form edits and `sb_api_call` PUTs; a page is refused if someone else saved it since. The platform's versions and history (`sb_page_version`) outlive this process |
120
120
 
121
121
  Twenty-eight tools, **830 API operations** (302 of the 381 writes carrying a body shape read
122
- off the handler), 123 elements, 82 binding sources. `sb_api_find`
122
+ off the handler), 123 elements, 94 binding sources. `sb_api_find`
123
123
  is an index rather than a tool per endpoint, so the tool list stays short while everything
124
124
  the platform can do stays reachable — and operations added to the platform arrive with the
125
125
  next `npm run codegen`.
package/README.vi.md CHANGED
@@ -116,7 +116,7 @@ nhét ngược lại vào mọi bản cài.
116
116
  | `sb_undo` | Hoàn tác / làm lại các thao tác ghi trong phiên — chỉnh sửa trang, form và PUT qua `sb_api_call`; trang bị từ chối nếu người khác đã lưu sau đó. Version và history của nền tảng (`sb_page_version`) sống lâu hơn process này |
117
117
 
118
118
  Hai mươi tám tool, **830 operation API** (302 trong 381 lệnh ghi có hình dạng body đọc thẳng
119
- từ handler), 123 element, 82 nguồn binding. `sb_api_find` là
119
+ từ handler), 123 element, 94 nguồn binding. `sb_api_find` là
120
120
  một chỉ mục chứ không phải mỗi endpoint một tool, nên danh sách tool vẫn ngắn trong khi mọi
121
121
  thứ nền tảng làm được vẫn với tới — và operation mới thêm bên nền tảng sẽ tự có sau lần
122
122
  `npm run codegen` kế tiếp.
@@ -41952,6 +41952,17 @@ export const BINDING_SOURCES = [
41952
41952
  "article.tags",
41953
41953
  "article.title",
41954
41954
  "article.url",
41955
+ "blogCategory.description",
41956
+ "blogCategory.image",
41957
+ "blogCategory.title",
41958
+ "blogCategory.url",
41959
+ "brand.description",
41960
+ "brand.image",
41961
+ "brand.title",
41962
+ "brand.url",
41963
+ "cartLine.lineTotalCents",
41964
+ "cartLine.quantity",
41965
+ "cartLine.variant",
41955
41966
  "category.description",
41956
41967
  "category.image",
41957
41968
  "category.title",
@@ -42008,6 +42019,7 @@ export const BINDING_SOURCES = [
42008
42019
  "review.stars",
42009
42020
  "review.title",
42010
42021
  "review.verified",
42022
+ "searchRow.url",
42011
42023
  "site.addressLine",
42012
42024
  "site.country",
42013
42025
  "site.description",
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { cartGalleryThumbs, cartRelocalize, cartSeedLocale } from './cartlang.js';
20
20
  import { PRODUCT_ACTION_BINDING_ID } from './navhref.js';
21
+ import { hasSeed } from './storepage.js';
21
22
  /** Element types that only make sense in a store. */
22
23
  const COMMERCE_TYPES = [
23
24
  'cart-order',
@@ -435,14 +436,21 @@ export function readinessGaps(input) {
435
436
  if (!pages || (count ?? 0) === 0 || published(pages, type))
436
437
  continue;
437
438
  const draft = drafted(pages, type);
439
+ // A type the platform ships no seed for opens EMPTY, and an empty entity
440
+ // template answers 500 at every one of its URLs (storefront/entityroute.go:
441
+ // empty body → "entity render failed") — so "publish it" alone trades the
442
+ // 404s for 500s and clears this gap. Measured on a brand page.
443
+ const withContent = hasSeed(type)
444
+ ? ''
445
+ : ` WITH content on it — an empty one answers 500 at every ${prefix}; sb_page_create's \`next\` says what to add`;
438
446
  gaps.push({
439
447
  id,
440
448
  draft,
441
449
  problem: `This site has ${count} ${what}${count === 1 ? '' : count && count > 1 ? 's' : ''} and no ` +
442
450
  `published page of the "${type}" type. ${prefix} needs one, so every ${what} link 404s.`,
443
451
  fix: draft
444
- ? `Publish the ${what} template page that already exists.`
445
- : `Create a page of type "${type}" and publish it.`,
452
+ ? `Publish the ${what} template page that already exists${withContent}.`
453
+ : `Create a page of type "${type}" and publish it${withContent}.`,
446
454
  });
447
455
  }
448
456
  // A CART DRAWER IN ANOTHER LANGUAGE. Asked before the store gate: a drawer
@@ -131,17 +131,19 @@ function drawsItsOwnContent(type) {
131
131
  return (BOUND_SPECIALS[type] ?? []).some((k) => !LINK_SPECIALS.has(k));
132
132
  }
133
133
  /** Binding-source prefixes that name a RECORD (not `site.*`, which every page has). */
134
- const RECORD_ENTITIES = new Set(['product', 'category', 'course', 'article', 'blogCategory']);
134
+ const RECORD_ENTITIES = new Set(['product', 'category', 'course', 'article', 'blogCategory', 'brand']);
135
135
  /**
136
136
  * Which records a page TYPE supplies outside any repeater — read off the
137
137
  * platform's own seeds (STORE_PAGE_SEEDS bind exactly these at top level).
138
138
  */
139
- const PAGE_ENTITIES = {
139
+ export const PAGE_ENTITIES = {
140
140
  product: ['product'],
141
141
  category: ['category'],
142
142
  course: ['course'],
143
143
  post: ['article'],
144
144
  blog: ['blogCategory', 'category'],
145
+ // /brands/{slug} — the page supplies brandContext (web_builder f70fe86d1).
146
+ brand: ['brand'],
145
147
  };
146
148
  /** Does this node or an ancestor PIN a record (`bindings[0].target.id`)? */
147
149
  function pinnedAbove(d, id) {
@@ -1,5 +1,7 @@
1
- import { COMPLETION_HEADLINE, COMPLETION_HEADLINE_SENTINEL, PAGE_LAYOUT_SEEDS, STORE_PAGE_SEEDS } from '../../catalog/storepages.generated.js';
1
+ import { COMPLETION_HEADLINE, COMPLETION_HEADLINE_SENTINEL, PAGE_LAYOUT_SEEDS, PAGE_TYPES, STORE_PAGE_SEEDS } from '../../catalog/storepages.generated.js';
2
+ import { BINDING_SOURCES } from '../../catalog/elements.generated.js';
2
3
  import { withFreshIds } from './ids.js';
4
+ import { PAGE_ENTITIES } from './review.js';
3
5
  /**
4
6
  * WHAT A NEW STORE PAGE OPENS WITH — for an agent, as for a merchant.
5
7
  *
@@ -55,6 +57,37 @@ export function seedDocument(type, opts = {}) {
55
57
  const escaped = JSON.stringify(headline).slice(1, -1);
56
58
  return withFreshIds(JSON.parse(raw.split(COMPLETION_HEADLINE_SENTINEL).join(escaped)));
57
59
  }
60
+ /**
61
+ * What to put on an ENTITY TEMPLATE that opens empty, or null.
62
+ *
63
+ * An entity template (a type whose route carries `{slug}`) with an empty
64
+ * document answers 500 "entity render failed" at EVERY one of its URLs once
65
+ * published — `storefront/entityroute.go` refuses an empty body rather than
66
+ * serve a blank 200. Measured on a brand page: empty → 500 at /brands/acme,
67
+ * one heading → 200. The editor seeds some of these types and not others
68
+ * (brand and course today), and `seed:false` or a refused seed write leaves any
69
+ * of them empty, so the rule is "entity template + nothing seeded", not a name.
70
+ *
71
+ * A NOTE, NOT AN INVENTED SEED: the platform ships none for brand, and a seed
72
+ * authored here would be a document nothing upstream keeps current.
73
+ */
74
+ export function emptyEntityTemplateNote(type) {
75
+ const route = PAGE_TYPES.find((t) => t.type === type)?.routePattern;
76
+ const entity = type ? PAGE_ENTITIES[type]?.[0] : undefined;
77
+ if (!route?.includes('{slug}') || !entity)
78
+ return null;
79
+ const has = (k) => BINDING_SOURCES.includes(`${entity}.${k}`);
80
+ const parts = [
81
+ ...(has('title') ? [`a heading bound to ${entity}.title`] : []),
82
+ ...(has('description') ? [`a text bound to ${entity}.description`] : []),
83
+ ...(type === 'brand'
84
+ ? ['a product grid narrowed to the brand the URL names: sb_template_use template_id "sb_product_shelf", then sb_set its list-dataset config.collectionType "page_brand" (a bare list-dataset has no card and renders empty)']
85
+ : []),
86
+ ];
87
+ return (`This "${type}" page is EMPTY. Published like that, every ${route} answers 500 ` +
88
+ '"entity render failed". ' +
89
+ `Before publishing, sb_add ${parts.join(', ')} — bind the heading and text with sb_bind, field "specials.text".`);
90
+ }
58
91
  /** A one-line summary of what a seed puts on the page, for the tool result. */
59
92
  export function seedSummary(type) {
60
93
  const seed = STORE_PAGE_SEEDS[type];
@@ -12,7 +12,7 @@ import { addSubtree, setMany, baseOnlyKeys, moveNode, removeNode, duplicateNode,
12
12
  import { baseOnlyNote } from '../domains/site/baseonly.js';
13
13
  import { detachNote, presetIdOf, presetLayer } from '../domains/site/theme.js';
14
14
  import { inertHintsFor } from '../domains/site/inert.js';
15
- import { hasSeed, layoutDocument, seedDocument, seedSummary, seededTypes, } from '../domains/site/storepage.js';
15
+ import { emptyEntityTemplateNote, hasSeed, layoutDocument, seedDocument, seedSummary, seededTypes, } from '../domains/site/storepage.js';
16
16
  import { PAGE_TYPES } from '../catalog/storepages.generated.js';
17
17
  import { skinLevelNote } from '../domains/site/fieldskin.js';
18
18
  import { siteTheme } from '../domains/site/theme-fetch.js';
@@ -2358,6 +2358,7 @@ export function registerPageTools(server, ctx) {
2358
2358
  body: redact(body),
2359
2359
  ...(summary ? { would_seed: { type, ...summary } } : {}),
2360
2360
  ...(layout ? { would_open_as: layout } : {}),
2361
+ ...(!summary && !layout && emptyEntityTemplateNote(type) ? { next: emptyEntityTemplateNote(type) } : {}),
2361
2362
  ...(wear.header || wear.footer
2362
2363
  ? { would_wear: { ...(wear.header ? { header: wear.header } : {}), ...(wear.footer ? { footer: wear.footer } : {}) } }
2363
2364
  : {}),
@@ -2415,9 +2416,14 @@ export function registerPageTools(server, ctx) {
2415
2416
  }
2416
2417
  }
2417
2418
  const wearing = typeof newId === 'string' && newId ? await wearChrome(session, site_id, newId, wear) : undefined;
2419
+ // An entity template that ended up with nothing on it 500s at every
2420
+ // entity URL once published — say what to add, whatever the reason
2421
+ // (no platform seed, seed:false, a refused seed write).
2422
+ const blankNote = !seeded || 'failed' in seeded ? emptyEntityTemplateNote(type) : null;
2418
2423
  return text({
2419
2424
  ...res,
2420
2425
  ...(seeded ? { seeded } : {}),
2426
+ ...(blankNote ? { next: blankNote } : {}),
2421
2427
  ...(wearing ? { chrome: wearing } : {}),
2422
2428
  ...(renamed
2423
2429
  ? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.83.0",
3
+ "version": "0.83.1",
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",