sbuilder-mcp 0.35.0 → 0.36.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 +13 -0
- package/CHANGELOG.vi.md +9 -0
- package/dist/catalog/api.generated.js +59 -3
- package/dist/catalog/search.js +23 -3
- package/dist/catalog/shapes.generated.js +38 -3
- package/dist/tools/api.js +31 -1
- package/dist/vision/capture.js +16 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ 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.36.1] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
- fix(import,api): a capture waited on iframes, and a recovery list was unreadable
|
|
12
|
+
|
|
13
|
+
## [0.36.0] - 2026-09-11
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- The `/apps` call sheet now explains marketplace app installation as two separate questions: suggesting one is always possible on any credential (`GET /api/sites/{siteId}/apps` plus `GET /oauth/authorize-info` for the app's publisher, privacy policy, scopes, and price), while installing one requires a session (`POST /oauth/authorize`) because an agent key is not an access token and the platform refuses it by design. It also documents that a paid app is refused without an explicit `acceptedPrice`, so this server can never commit a merchant to a subscription on its own.
|
|
17
|
+
- The catalog now carries the two `/oauth` operations behind that flow, `GET /oauth/authorize-info` and `POST /oauth/authorize`, including the request shape for the latter.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- The `builtin-apps` call sheet no longer tells the caller that a marketplace app "cannot be installed from here at all"; that line was stricter than the platform actually enforces.
|
|
21
|
+
|
|
9
22
|
## [0.35.0] - 2026-09-11
|
|
10
23
|
|
|
11
24
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,15 @@ 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.36.0] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Call sheet của `/apps` giờ giải thích việc cài một app từ marketplace như hai câu hỏi tách biệt: gợi ý một app luôn khả thi với bất kỳ credential nào (`GET /api/sites/{siteId}/apps` cùng `GET /oauth/authorize-info` để lấy nhà phát hành, chính sách riêng tư, scope và giá của app), còn cài đặt thì cần một session (`POST /oauth/authorize`) vì agent key không phải access token và nền tảng cố tình từ chối nó. Call sheet cũng ghi rõ một app trả phí sẽ bị từ chối nếu thiếu `acceptedPrice` tường minh, nên server này không bao giờ tự ý khiến merchant phải trả tiền cho một gói đăng ký.
|
|
13
|
+
- Catalog giờ mang theo hai operation `/oauth` phía sau luồng đó, `GET /oauth/authorize-info` và `POST /oauth/authorize`, kèm request shape cho operation thứ hai.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Call sheet của `builtin-apps` không còn nói với caller rằng một app marketplace "không thể cài từ đây được"; câu đó khắt khe hơn mức nền tảng thực sự yêu cầu.
|
|
17
|
+
|
|
9
18
|
## [0.35.0] - 2026-09-11
|
|
10
19
|
|
|
11
20
|
### Added
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const SWAGGER_SOURCE = {
|
|
2
|
-
"operations":
|
|
2
|
+
"operations": 508,
|
|
3
3
|
"definitions": 105,
|
|
4
|
-
"bodyCarrying":
|
|
5
|
-
"bodyUndescribed":
|
|
4
|
+
"bodyCarrying": 183,
|
|
5
|
+
"bodyUndescribed": 68,
|
|
6
6
|
"generatedFrom": "server/docs/swagger.json"
|
|
7
7
|
};
|
|
8
8
|
export const API_OPERATIONS = [
|
|
@@ -15239,6 +15239,62 @@ export const API_OPERATIONS = [
|
|
|
15239
15239
|
"bodyDescribed": false,
|
|
15240
15240
|
"bodyRef": null,
|
|
15241
15241
|
"credential": "apiKey"
|
|
15242
|
+
},
|
|
15243
|
+
{
|
|
15244
|
+
"id": "post:/oauth/authorize",
|
|
15245
|
+
"method": "POST",
|
|
15246
|
+
"path": "/oauth/authorize",
|
|
15247
|
+
"tags": [
|
|
15248
|
+
"apps"
|
|
15249
|
+
],
|
|
15250
|
+
"summary": "Agree to an app's scopes and install it on a site",
|
|
15251
|
+
"params": [
|
|
15252
|
+
{
|
|
15253
|
+
"name": "body",
|
|
15254
|
+
"in": "body",
|
|
15255
|
+
"required": true,
|
|
15256
|
+
"type": "object",
|
|
15257
|
+
"description": "{siteId, clientId, versionId, redirectUri, scopes, state, acceptedPrice?}"
|
|
15258
|
+
}
|
|
15259
|
+
],
|
|
15260
|
+
"bodyDescribed": false,
|
|
15261
|
+
"bodyRef": null,
|
|
15262
|
+
"credential": "siteScoped"
|
|
15263
|
+
},
|
|
15264
|
+
{
|
|
15265
|
+
"id": "get:/oauth/authorize-info",
|
|
15266
|
+
"method": "GET",
|
|
15267
|
+
"path": "/oauth/authorize-info",
|
|
15268
|
+
"tags": [
|
|
15269
|
+
"apps"
|
|
15270
|
+
],
|
|
15271
|
+
"summary": "What an app is asking for, before anyone agrees to it",
|
|
15272
|
+
"params": [
|
|
15273
|
+
{
|
|
15274
|
+
"name": "client_id",
|
|
15275
|
+
"in": "query",
|
|
15276
|
+
"required": true,
|
|
15277
|
+
"type": "string",
|
|
15278
|
+
"description": "The app's client id"
|
|
15279
|
+
},
|
|
15280
|
+
{
|
|
15281
|
+
"name": "version_id",
|
|
15282
|
+
"in": "query",
|
|
15283
|
+
"required": false,
|
|
15284
|
+
"type": "string",
|
|
15285
|
+
"description": "A specific version; the current one otherwise"
|
|
15286
|
+
},
|
|
15287
|
+
{
|
|
15288
|
+
"name": "redirect_uri",
|
|
15289
|
+
"in": "query",
|
|
15290
|
+
"required": true,
|
|
15291
|
+
"type": "string",
|
|
15292
|
+
"description": "Must match one the app registered"
|
|
15293
|
+
}
|
|
15294
|
+
],
|
|
15295
|
+
"bodyDescribed": false,
|
|
15296
|
+
"bodyRef": null,
|
|
15297
|
+
"credential": "siteScoped"
|
|
15242
15298
|
}
|
|
15243
15299
|
];
|
|
15244
15300
|
export const API_DEFINITIONS = {
|
package/dist/catalog/search.js
CHANGED
|
@@ -114,9 +114,29 @@ export function describeOperation(op) {
|
|
|
114
114
|
'reduces the subtree back to the reference. Configure the block through its own ' +
|
|
115
115
|
'props/slots, never by editing what it rendered.',
|
|
116
116
|
installing: 'A BUILT-IN app installs with POST /api/sites/{siteId}/builtin-apps/{key} and that key ' +
|
|
117
|
-
"parameter's description names every installable one.
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
"parameter's description names every installable one.",
|
|
118
|
+
// A MARKETPLACE APP IS A PERMISSION GRANT, and who may make it is the
|
|
119
|
+
// platform's line rather than this client's caution. `consent`
|
|
120
|
+
// authenticates as a USER — the platform parses an ACCESS TOKEN, so a
|
|
121
|
+
// session reaches it and a `wbk_` key does not — because an installed app
|
|
122
|
+
// holds SCOPES against the store. And `acceptedPrice` is a pointer:
|
|
123
|
+
// omitting it means a free app, and omitting it for a paid one is REFUSED
|
|
124
|
+
// rather than assumed, so nothing automated can commit a merchant to a
|
|
125
|
+
// subscription.
|
|
126
|
+
//
|
|
127
|
+
// So the honest answer is neither "ask a human" nor "just install it". It
|
|
128
|
+
// is: always be able to SUGGEST, and install only what the credential in
|
|
129
|
+
// hand is allowed to install, having shown what it grants.
|
|
130
|
+
marketplace: 'SUGGESTING one is always available: GET /api/sites/{siteId}/apps lists what this store ' +
|
|
131
|
+
'can install, and GET /oauth/authorize-info?client_id=… answers with the app, its ' +
|
|
132
|
+
'publisher, its privacy policy, the SCOPES it would hold and whether it is paid. Show ' +
|
|
133
|
+
'that to whoever is accountable for it. INSTALLING is POST /oauth/authorize with ' +
|
|
134
|
+
'{siteId, clientId, versionId, redirectUri, scopes} — it needs a SESSION ' +
|
|
135
|
+
'(SB_EMAIL/SB_PASSWORD), because an agent key is not an access token and the platform ' +
|
|
136
|
+
'refuses it by design: the app holds scopes against the store. A FREE app installs with ' +
|
|
137
|
+
'no acceptedPrice; a PAID one is refused without it rather than having a price assumed ' +
|
|
138
|
+
'on the merchant\'s behalf, so never send one they have not seen. Then read ' +
|
|
139
|
+
'/apps/blocks again for what it contributed.',
|
|
120
140
|
};
|
|
121
141
|
}
|
|
122
142
|
const shape = REQUEST_SHAPES[op.id];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const SHAPE_SOURCE = {
|
|
2
|
-
"writeOperations":
|
|
3
|
-
"shaped":
|
|
4
|
-
"fromHandlers":
|
|
2
|
+
"writeOperations": 223,
|
|
3
|
+
"shaped": 170,
|
|
4
|
+
"fromHandlers": 170,
|
|
5
5
|
"fromSwaggerOnly": 0,
|
|
6
6
|
"withReadOnly": 26,
|
|
7
7
|
"structsRead": 1658
|
|
@@ -31,6 +31,41 @@ export const REQUEST_SHAPES = {
|
|
|
31
31
|
"source": "go",
|
|
32
32
|
"goType": "versionChoice"
|
|
33
33
|
},
|
|
34
|
+
"post:/oauth/authorize": {
|
|
35
|
+
"fields": [
|
|
36
|
+
{
|
|
37
|
+
"name": "siteId",
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "clientId",
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "versionId",
|
|
46
|
+
"type": "string"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "redirectUri",
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "scopes",
|
|
54
|
+
"type": "string[]"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "state",
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "AcceptedPrice",
|
|
62
|
+
"type": "object",
|
|
63
|
+
"note": "AcceptedPrice is the amount the merchant was LOOKING AT when they pressed the button, echoed back from the authorize response above."
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"source": "go",
|
|
67
|
+
"goType": "(inline)"
|
|
68
|
+
},
|
|
34
69
|
"post:/api/orgs/{orgId}/apps": {
|
|
35
70
|
"fields": [
|
|
36
71
|
{
|
package/dist/tools/api.js
CHANGED
|
@@ -45,6 +45,33 @@ function pickFields(item, fields) {
|
|
|
45
45
|
* it would have been and how to narrow the call. A non-list answer is never
|
|
46
46
|
* cut: there is no honest place to stop inside one object.
|
|
47
47
|
*/
|
|
48
|
+
/**
|
|
49
|
+
* LIST OPERATIONS WHOSE EVERY ROW CARRIES A WHOLE PAGE DOCUMENT.
|
|
50
|
+
*
|
|
51
|
+
* The page recovery surface answers with the documents themselves — which is
|
|
52
|
+
* right, since a restore has to have something to restore from — and useless to
|
|
53
|
+
* read. MEASURED against a live server: one version of a TWO-NODE page is 1,690
|
|
54
|
+
* bytes, so a realistic 120-node page runs about 70 KB per version and a listing
|
|
55
|
+
* of twenty is **1.4 MB in one answer**. An agent choosing which version to
|
|
56
|
+
* restore would be handed a truncated blob and no reliable way to pick.
|
|
57
|
+
*
|
|
58
|
+
* `sb_publish` already had this exact problem and the same answer: a published
|
|
59
|
+
* row carries `document`, `html` and `css` for every page the cascade touched,
|
|
60
|
+
* so it PROJECTS the rows. This is that, applied where the caller cannot know to
|
|
61
|
+
* ask — and it is a DEFAULT rather than a rule: an explicit `pick` still wins,
|
|
62
|
+
* so the document is one argument away for a caller that wants to read one.
|
|
63
|
+
*/
|
|
64
|
+
const LIST_PROJECTIONS = {
|
|
65
|
+
'get:/api/sites/{siteId}/pages/{pageId}/versions': [
|
|
66
|
+
'id',
|
|
67
|
+
'versionNo',
|
|
68
|
+
'label',
|
|
69
|
+
'createdBy',
|
|
70
|
+
'createdAt',
|
|
71
|
+
'isLive',
|
|
72
|
+
],
|
|
73
|
+
'get:/api/sites/{siteId}/pages/{pageId}/history': ['id', 'createdBy', 'createdAt'],
|
|
74
|
+
};
|
|
48
75
|
export function shapeResponse(raw, opts) {
|
|
49
76
|
const asked = opts.pick !== undefined || opts.max_items !== undefined;
|
|
50
77
|
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
@@ -271,7 +298,10 @@ export async function callOperation(ctx, args) {
|
|
|
271
298
|
if (raw === null || raw === undefined) {
|
|
272
299
|
return { ok: true, method: op.method, path, note: 'The platform answered with no content.' };
|
|
273
300
|
}
|
|
274
|
-
|
|
301
|
+
// The caller's own `pick` outranks the default: asking for `document` is how
|
|
302
|
+
// you read a version rather than merely choose one.
|
|
303
|
+
const projection = args.pick ?? LIST_PROJECTIONS[op.id];
|
|
304
|
+
return shapeResponse(raw, { pick: projection, max_items: args.max_items });
|
|
275
305
|
}
|
|
276
306
|
export function registerApiTools(server, ctx) {
|
|
277
307
|
server.registerTool('sb_api_find', {
|
package/dist/vision/capture.js
CHANGED
|
@@ -1127,7 +1127,22 @@ async function readPage(browser, url, width, work) {
|
|
|
1127
1127
|
let page;
|
|
1128
1128
|
try {
|
|
1129
1129
|
page = await browser.newPage({ viewport: { width, height: 900 } });
|
|
1130
|
-
|
|
1130
|
+
// `domcontentloaded`, NOT `load`, and `settleDom` does the rest.
|
|
1131
|
+
//
|
|
1132
|
+
// `load` waits for every SUBRESOURCE — including third-party iframes, which
|
|
1133
|
+
// an import has no use for: this walk reads the iframe's `src` ATTRIBUTE and
|
|
1134
|
+
// never needs the frame to render. So a page carrying an ad frame, a chat
|
|
1135
|
+
// widget or a slow video embed stalled the whole capture for up to thirty
|
|
1136
|
+
// seconds and then THREW, losing an import whose DOM had been ready the
|
|
1137
|
+
// entire time. Caught by this repo's own test, whose fixture embeds real
|
|
1138
|
+
// YouTube, Vimeo and Google Maps frames: it started failing at exactly 30s
|
|
1139
|
+
// with nothing about the page having changed.
|
|
1140
|
+
//
|
|
1141
|
+
// The same lesson `sb_look` already paid for with `networkidle`, one wait
|
|
1142
|
+
// earlier: the right question is "has the DOM stopped changing", and
|
|
1143
|
+
// `settleDom` answers it directly and bounded. A page that genuinely needs
|
|
1144
|
+
// its images is the SHOOT path's problem, and that one still waits.
|
|
1145
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
1131
1146
|
// THE SAME SETTLE `sb_look` USES, not a flat sleep. A fixed 600ms is wrong
|
|
1132
1147
|
// at both ends: example.com is finished long before it, and a page that
|
|
1133
1148
|
// builds itself with scripts is not finished after it — which is exactly the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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",
|