sbuilder-mcp 0.83.0 → 0.83.2
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 +20 -0
- package/CHANGELOG.vi.md +20 -0
- package/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/catalog/api.generated.js +152 -37
- package/dist/catalog/deadkeys.generated.js +2 -2
- package/dist/catalog/elements.generated.js +12 -0
- package/dist/catalog/shapes.generated.js +52 -5
- package/dist/catalog/source.generated.js +2 -2
- package/dist/domains/site/readiness.js +10 -2
- package/dist/domains/site/review.js +4 -2
- package/dist/domains/site/storepage.js +34 -1
- package/dist/index.js +20 -5
- package/dist/tools/page.js +7 -1
- package/dist/transport/auth.js +41 -1
- package/dist/vision/capture.js +7 -1
- package/dist/vision/shoot.js +5 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ 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.2] - 2026-10-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_api_find` and `sb_api_call` reach 833 platform operations.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- The server logs the device session out when the process exits (stdin closes, SIGTERM, SIGINT) and before `sb_connect` signs in again, so leftover MCP sessions no longer count against the platform's 10-device limit and push the merchant out of the editor.
|
|
16
|
+
- Closing Chrome after a screenshot no longer kills the process before the logout runs.
|
|
17
|
+
|
|
18
|
+
## [0.83.1] - 2026-10-11
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `sb_bind` accepts `brand.*` and `blogCategory.*` binding sources, raising the known binding sources from 82 to 94.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- `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.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `sb_review` no longer reports `dead_binding_source` for valid `blogCategory.*` bindings.
|
|
28
|
+
|
|
9
29
|
## [0.83.0] - 2026-10-11
|
|
10
30
|
|
|
11
31
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,26 @@ 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.2] - 2026-10-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_api_find` và `sb_api_call` truy cập được 833 operation của platform.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Server đăng xuất phiên thiết bị khi tiến trình thoát (stdin đóng, SIGTERM, SIGINT) và trước khi `sb_connect` đăng nhập lại, nên phiên MCP bỏ lại không còn chiếm giới hạn 10 thiết bị của platform và đẩy merchant ra khỏi editor.
|
|
16
|
+
- Việc đóng Chrome sau khi chụp ảnh không còn giết tiến trình trước khi kịp đăng xuất.
|
|
17
|
+
|
|
18
|
+
## [0.83.1] - 2026-10-11
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `sb_bind` nhận nguồn binding `brand.*` và `blogCategory.*`, nâng số nguồn binding được biết từ 82 lên 94.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- `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.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `sb_review` không còn báo `dead_binding_source` cho binding `blogCategory.*` hợp lệ.
|
|
28
|
+
|
|
9
29
|
## [0.83.0] - 2026-10-11
|
|
10
30
|
|
|
11
31
|
### Added
|
package/README.md
CHANGED
|
@@ -118,8 +118,8 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
118
118
|
| `sb_store` | Run a store flow that must happen in a fixed order — `checkout` (the four writes that make a working one), `checkout_sync` (the store's current payment and delivery methods into every existing order form, checkout page republished), `form` (any of the platform's 17 templates with its own field document), `chrome` (one shared header or footer on a real site menu — desktop menu, mobile drawer, cart and account icons), `menu` (a menu node bound to the site's menu, its links resolved), `overlay_attach` (a pop-up or quick view on the open page), `cart` (the site's cart drawer, when it has none; `relocalize` puts an existing one in the site's language) and `app` (a built-in app plus the pages it needs), `global_attach` / `global_detach` (put an EXISTING shared section on the open page, or take it off), `global_promote` (make a section on the page a NEW shared section) and `template_save` (save a section as a reusable template) |
|
|
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
|
-
Twenty-eight tools, **
|
|
122
|
-
off the handler), 123 elements,
|
|
121
|
+
Twenty-eight tools, **833 API operations** (304 of the 384 writes carrying a body shape read
|
|
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
|
@@ -115,8 +115,8 @@ nhét ngược lại vào mọi bản cài.
|
|
|
115
115
|
| `sb_store` | Chạy một luồng cửa hàng bắt buộc đúng thứ tự — `checkout` (bốn lệnh ghi tạo nên trang thanh toán), `checkout_sync` (đưa phương thức thanh toán và giao hàng hiện tại vào mọi form đặt hàng đã có, publish lại trang thanh toán), `form` (một trong 17 template của nền tảng kèm field document của nó), `chrome` (một header hoặc footer dùng chung trên menu thật của site — menu desktop, drawer mobile, icon giỏ hàng và tài khoản), `menu` (một node menu bind vào menu của site, link đã phân giải), `overlay_attach` (một pop-up hay quick view trên trang đang mở), `cart` (ngăn giỏ hàng của site, khi chưa có; `relocalize` đưa ngăn giỏ đã có về ngôn ngữ của site), `app` (một app dựng sẵn kèm những trang nó cần), và `global_attach` / `global_detach` (đặt một section dùng chung ĐÃ CÓ lên trang đang mở, hoặc gỡ ra), `global_promote` (biến một section trên trang thành section dùng chung MỚI) và `template_save` (lưu một section làm mẫu dùng lạ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
|
-
Hai mươi tám tool, **
|
|
119
|
-
từ handler), 123 element,
|
|
118
|
+
Hai mươi tám tool, **833 operation API** (304 trong 384 lệnh ghi có hình dạng body đọc thẳng
|
|
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.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const SWAGGER_SOURCE = {
|
|
2
|
-
"operations":
|
|
3
|
-
"definitions":
|
|
4
|
-
"bodyCarrying":
|
|
5
|
-
"bodyUndescribed":
|
|
2
|
+
"operations": 833,
|
|
3
|
+
"definitions": 278,
|
|
4
|
+
"bodyCarrying": 364,
|
|
5
|
+
"bodyUndescribed": 93,
|
|
6
6
|
"generatedFrom": "server/docs/swagger.json"
|
|
7
7
|
};
|
|
8
8
|
export const API_OPERATIONS = [
|
|
@@ -1508,6 +1508,27 @@ export const API_OPERATIONS = [
|
|
|
1508
1508
|
"bodyRef": null,
|
|
1509
1509
|
"credential": "siteScoped"
|
|
1510
1510
|
},
|
|
1511
|
+
{
|
|
1512
|
+
"id": "post:/api/admin/accounts/{id}/sign-out",
|
|
1513
|
+
"method": "POST",
|
|
1514
|
+
"path": "/api/admin/accounts/{id}/sign-out",
|
|
1515
|
+
"tags": [
|
|
1516
|
+
"admin"
|
|
1517
|
+
],
|
|
1518
|
+
"summary": "Sign an account out of every device",
|
|
1519
|
+
"params": [
|
|
1520
|
+
{
|
|
1521
|
+
"name": "id",
|
|
1522
|
+
"in": "path",
|
|
1523
|
+
"required": true,
|
|
1524
|
+
"type": "string",
|
|
1525
|
+
"description": "Account (user) id"
|
|
1526
|
+
}
|
|
1527
|
+
],
|
|
1528
|
+
"bodyDescribed": false,
|
|
1529
|
+
"bodyRef": null,
|
|
1530
|
+
"credential": "siteScoped"
|
|
1531
|
+
},
|
|
1511
1532
|
{
|
|
1512
1533
|
"id": "get:/api/admin/address-units",
|
|
1513
1534
|
"method": "GET",
|
|
@@ -1932,6 +1953,27 @@ export const API_OPERATIONS = [
|
|
|
1932
1953
|
"bodyRef": null,
|
|
1933
1954
|
"credential": "none"
|
|
1934
1955
|
},
|
|
1956
|
+
{
|
|
1957
|
+
"id": "post:/api/auth/logout",
|
|
1958
|
+
"method": "POST",
|
|
1959
|
+
"path": "/api/auth/logout",
|
|
1960
|
+
"tags": [
|
|
1961
|
+
"auth"
|
|
1962
|
+
],
|
|
1963
|
+
"summary": "Sign out this device (revoke its refresh session)",
|
|
1964
|
+
"params": [
|
|
1965
|
+
{
|
|
1966
|
+
"name": "body",
|
|
1967
|
+
"in": "body",
|
|
1968
|
+
"required": true,
|
|
1969
|
+
"type": "object",
|
|
1970
|
+
"description": "refreshToken"
|
|
1971
|
+
}
|
|
1972
|
+
],
|
|
1973
|
+
"bodyDescribed": false,
|
|
1974
|
+
"bodyRef": null,
|
|
1975
|
+
"credential": "none"
|
|
1976
|
+
},
|
|
1935
1977
|
{
|
|
1936
1978
|
"id": "get:/api/auth/me",
|
|
1937
1979
|
"method": "GET",
|
|
@@ -8272,6 +8314,34 @@ export const API_OPERATIONS = [
|
|
|
8272
8314
|
"bodyRef": null,
|
|
8273
8315
|
"credential": "siteScoped"
|
|
8274
8316
|
},
|
|
8317
|
+
{
|
|
8318
|
+
"id": "post:/api/sites/{siteId}/einvoice/connection/check",
|
|
8319
|
+
"method": "POST",
|
|
8320
|
+
"path": "/api/sites/{siteId}/einvoice/connection/check",
|
|
8321
|
+
"tags": [
|
|
8322
|
+
"pos"
|
|
8323
|
+
],
|
|
8324
|
+
"summary": "Test an e-invoice provider connection",
|
|
8325
|
+
"params": [
|
|
8326
|
+
{
|
|
8327
|
+
"name": "siteId",
|
|
8328
|
+
"in": "path",
|
|
8329
|
+
"required": true,
|
|
8330
|
+
"type": "string",
|
|
8331
|
+
"description": "Site ID"
|
|
8332
|
+
},
|
|
8333
|
+
{
|
|
8334
|
+
"name": "body",
|
|
8335
|
+
"in": "body",
|
|
8336
|
+
"required": false,
|
|
8337
|
+
"type": "object",
|
|
8338
|
+
"description": "{provider?, sandbox?, credentials?}"
|
|
8339
|
+
}
|
|
8340
|
+
],
|
|
8341
|
+
"bodyDescribed": true,
|
|
8342
|
+
"bodyRef": "github_com_webbuilder_server_internal_einvoice.CheckInput",
|
|
8343
|
+
"credential": "siteScoped"
|
|
8344
|
+
},
|
|
8275
8345
|
{
|
|
8276
8346
|
"id": "get:/api/sites/{siteId}/einvoice/jobs",
|
|
8277
8347
|
"method": "GET",
|
|
@@ -8423,7 +8493,7 @@ export const API_OPERATIONS = [
|
|
|
8423
8493
|
}
|
|
8424
8494
|
],
|
|
8425
8495
|
"bodyDescribed": true,
|
|
8426
|
-
"bodyRef": "
|
|
8496
|
+
"bodyRef": "internal_einvoice_rest.settingsInput",
|
|
8427
8497
|
"credential": "siteScoped"
|
|
8428
8498
|
},
|
|
8429
8499
|
{
|
|
@@ -8451,7 +8521,7 @@ export const API_OPERATIONS = [
|
|
|
8451
8521
|
}
|
|
8452
8522
|
],
|
|
8453
8523
|
"bodyDescribed": true,
|
|
8454
|
-
"bodyRef": "
|
|
8524
|
+
"bodyRef": "internal_einvoice_rest.settingsInput",
|
|
8455
8525
|
"credential": "siteScoped"
|
|
8456
8526
|
},
|
|
8457
8527
|
{
|
|
@@ -14687,6 +14757,13 @@ export const API_OPERATIONS = [
|
|
|
14687
14757
|
"type": "string",
|
|
14688
14758
|
"description": "Location filter (GET)"
|
|
14689
14759
|
},
|
|
14760
|
+
{
|
|
14761
|
+
"name": "archived",
|
|
14762
|
+
"in": "query",
|
|
14763
|
+
"required": false,
|
|
14764
|
+
"type": "boolean",
|
|
14765
|
+
"description": "true = list archived areas (GET)"
|
|
14766
|
+
},
|
|
14690
14767
|
{
|
|
14691
14768
|
"name": "area",
|
|
14692
14769
|
"in": "body",
|
|
@@ -14722,6 +14799,13 @@ export const API_OPERATIONS = [
|
|
|
14722
14799
|
"type": "string",
|
|
14723
14800
|
"description": "Location filter (GET)"
|
|
14724
14801
|
},
|
|
14802
|
+
{
|
|
14803
|
+
"name": "archived",
|
|
14804
|
+
"in": "query",
|
|
14805
|
+
"required": false,
|
|
14806
|
+
"type": "boolean",
|
|
14807
|
+
"description": "true = list archived areas (GET)"
|
|
14808
|
+
},
|
|
14725
14809
|
{
|
|
14726
14810
|
"name": "area",
|
|
14727
14811
|
"in": "body",
|
|
@@ -14741,7 +14825,7 @@ export const API_OPERATIONS = [
|
|
|
14741
14825
|
"tags": [
|
|
14742
14826
|
"pos"
|
|
14743
14827
|
],
|
|
14744
|
-
"summary": "Rename, reorder or delete a floor area",
|
|
14828
|
+
"summary": "Rename, reorder, restore, archive or delete a floor area",
|
|
14745
14829
|
"params": [
|
|
14746
14830
|
{
|
|
14747
14831
|
"name": "siteId",
|
|
@@ -14776,7 +14860,7 @@ export const API_OPERATIONS = [
|
|
|
14776
14860
|
"tags": [
|
|
14777
14861
|
"pos"
|
|
14778
14862
|
],
|
|
14779
|
-
"summary": "Rename, reorder or delete a floor area",
|
|
14863
|
+
"summary": "Rename, reorder, restore, archive or delete a floor area",
|
|
14780
14864
|
"params": [
|
|
14781
14865
|
{
|
|
14782
14866
|
"name": "siteId",
|
|
@@ -26701,41 +26785,20 @@ export const API_DEFINITIONS = {
|
|
|
26701
26785
|
}
|
|
26702
26786
|
}
|
|
26703
26787
|
},
|
|
26704
|
-
"github_com_webbuilder_server_internal_einvoice.
|
|
26788
|
+
"github_com_webbuilder_server_internal_einvoice.CheckInput": {
|
|
26705
26789
|
"type": "object",
|
|
26706
26790
|
"properties": {
|
|
26707
|
-
"
|
|
26708
|
-
"type": "
|
|
26709
|
-
|
|
26710
|
-
|
|
26711
|
-
|
|
26712
|
-
},
|
|
26713
|
-
"issueMode": {
|
|
26714
|
-
"type": "string"
|
|
26791
|
+
"credentials": {
|
|
26792
|
+
"type": "object",
|
|
26793
|
+
"additionalProperties": {
|
|
26794
|
+
"type": "string"
|
|
26795
|
+
}
|
|
26715
26796
|
},
|
|
26716
26797
|
"provider": {
|
|
26717
26798
|
"type": "string"
|
|
26718
26799
|
},
|
|
26719
|
-
"
|
|
26720
|
-
"type": "
|
|
26721
|
-
},
|
|
26722
|
-
"sellerEmail": {
|
|
26723
|
-
"type": "string"
|
|
26724
|
-
},
|
|
26725
|
-
"sellerName": {
|
|
26726
|
-
"type": "string"
|
|
26727
|
-
},
|
|
26728
|
-
"sellerPhone": {
|
|
26729
|
-
"type": "string"
|
|
26730
|
-
},
|
|
26731
|
-
"sellerTaxCode": {
|
|
26732
|
-
"type": "string"
|
|
26733
|
-
},
|
|
26734
|
-
"templateCode": {
|
|
26735
|
-
"type": "string"
|
|
26736
|
-
},
|
|
26737
|
-
"updatedAt": {
|
|
26738
|
-
"type": "string"
|
|
26800
|
+
"sandbox": {
|
|
26801
|
+
"type": "boolean"
|
|
26739
26802
|
}
|
|
26740
26803
|
}
|
|
26741
26804
|
},
|
|
@@ -27801,6 +27864,10 @@ export const API_DEFINITIONS = {
|
|
|
27801
27864
|
"github_com_webbuilder_server_internal_pos.AreaInput": {
|
|
27802
27865
|
"type": "object",
|
|
27803
27866
|
"properties": {
|
|
27867
|
+
"archived": {
|
|
27868
|
+
"description": "Archived (PATCH only): false restores an archived area; true is refused —\narchiving has one door, DELETE, which checks the open tabs.",
|
|
27869
|
+
"type": "boolean"
|
|
27870
|
+
},
|
|
27804
27871
|
"locationId": {
|
|
27805
27872
|
"description": "POST only; an area never moves",
|
|
27806
27873
|
"type": "string"
|
|
@@ -32141,6 +32208,54 @@ export const API_DEFINITIONS = {
|
|
|
32141
32208
|
}
|
|
32142
32209
|
}
|
|
32143
32210
|
},
|
|
32211
|
+
"internal_einvoice_rest.settingsInput": {
|
|
32212
|
+
"type": "object",
|
|
32213
|
+
"properties": {
|
|
32214
|
+
"credentials": {
|
|
32215
|
+
"type": "object",
|
|
32216
|
+
"additionalProperties": {
|
|
32217
|
+
"type": "string"
|
|
32218
|
+
}
|
|
32219
|
+
},
|
|
32220
|
+
"enabled": {
|
|
32221
|
+
"type": "boolean"
|
|
32222
|
+
},
|
|
32223
|
+
"invoiceSeries": {
|
|
32224
|
+
"type": "string"
|
|
32225
|
+
},
|
|
32226
|
+
"issueMode": {
|
|
32227
|
+
"type": "string"
|
|
32228
|
+
},
|
|
32229
|
+
"provider": {
|
|
32230
|
+
"type": "string"
|
|
32231
|
+
},
|
|
32232
|
+
"sandbox": {
|
|
32233
|
+
"description": "Sandbox points the provider adapter at its test environment.",
|
|
32234
|
+
"type": "boolean"
|
|
32235
|
+
},
|
|
32236
|
+
"sellerAddress": {
|
|
32237
|
+
"type": "string"
|
|
32238
|
+
},
|
|
32239
|
+
"sellerEmail": {
|
|
32240
|
+
"type": "string"
|
|
32241
|
+
},
|
|
32242
|
+
"sellerName": {
|
|
32243
|
+
"type": "string"
|
|
32244
|
+
},
|
|
32245
|
+
"sellerPhone": {
|
|
32246
|
+
"type": "string"
|
|
32247
|
+
},
|
|
32248
|
+
"sellerTaxCode": {
|
|
32249
|
+
"type": "string"
|
|
32250
|
+
},
|
|
32251
|
+
"templateCode": {
|
|
32252
|
+
"type": "string"
|
|
32253
|
+
},
|
|
32254
|
+
"updatedAt": {
|
|
32255
|
+
"type": "string"
|
|
32256
|
+
}
|
|
32257
|
+
}
|
|
32258
|
+
},
|
|
32144
32259
|
"internal_giftcards_rest.adjustRequest": {
|
|
32145
32260
|
"type": "object",
|
|
32146
32261
|
"properties": {
|
|
@@ -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",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const SHAPE_SOURCE = {
|
|
2
|
-
"writeOperations":
|
|
3
|
-
"shaped":
|
|
4
|
-
"fromHandlers":
|
|
2
|
+
"writeOperations": 384,
|
|
3
|
+
"shaped": 304,
|
|
4
|
+
"fromHandlers": 294,
|
|
5
5
|
"fromSwaggerOnly": 10,
|
|
6
6
|
"withReadOnly": 30,
|
|
7
|
-
"structsRead":
|
|
7
|
+
"structsRead": 3194
|
|
8
8
|
};
|
|
9
9
|
export const REQUEST_SHAPES = {
|
|
10
10
|
"post:/api/sites/{id}/ai-credits/purchase": {
|
|
@@ -756,6 +756,16 @@ export const REQUEST_SHAPES = {
|
|
|
756
756
|
"source": "go",
|
|
757
757
|
"goType": "(inline)"
|
|
758
758
|
},
|
|
759
|
+
"post:/api/auth/logout": {
|
|
760
|
+
"fields": [
|
|
761
|
+
{
|
|
762
|
+
"name": "refreshToken",
|
|
763
|
+
"type": "string"
|
|
764
|
+
}
|
|
765
|
+
],
|
|
766
|
+
"source": "go",
|
|
767
|
+
"goType": "(inline)"
|
|
768
|
+
},
|
|
759
769
|
"post:/api/auth/change-password": {
|
|
760
770
|
"fields": [
|
|
761
771
|
{
|
|
@@ -3533,13 +3543,40 @@ export const REQUEST_SHAPES = {
|
|
|
3533
3543
|
"name": "invoiceSeries",
|
|
3534
3544
|
"type": "string"
|
|
3535
3545
|
},
|
|
3546
|
+
{
|
|
3547
|
+
"name": "sandbox",
|
|
3548
|
+
"type": "boolean",
|
|
3549
|
+
"note": "Sandbox points the provider adapter at its test environment."
|
|
3550
|
+
},
|
|
3536
3551
|
{
|
|
3537
3552
|
"name": "updatedAt",
|
|
3538
3553
|
"type": "string (RFC3339)"
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
"name": "credentials",
|
|
3557
|
+
"type": "{ [string]: string }"
|
|
3539
3558
|
}
|
|
3540
3559
|
],
|
|
3541
3560
|
"source": "go",
|
|
3542
|
-
"goType": "
|
|
3561
|
+
"goType": "settingsInput"
|
|
3562
|
+
},
|
|
3563
|
+
"post:/api/sites/{siteId}/einvoice/connection/check": {
|
|
3564
|
+
"fields": [
|
|
3565
|
+
{
|
|
3566
|
+
"name": "provider",
|
|
3567
|
+
"type": "string"
|
|
3568
|
+
},
|
|
3569
|
+
{
|
|
3570
|
+
"name": "sandbox",
|
|
3571
|
+
"type": "boolean | null"
|
|
3572
|
+
},
|
|
3573
|
+
{
|
|
3574
|
+
"name": "credentials",
|
|
3575
|
+
"type": "{ [string]: string }"
|
|
3576
|
+
}
|
|
3577
|
+
],
|
|
3578
|
+
"source": "go",
|
|
3579
|
+
"goType": "einvoice.CheckInput"
|
|
3543
3580
|
},
|
|
3544
3581
|
"post:/api/sites/{siteId}/einvoice/jobs/{id}/cancel": {
|
|
3545
3582
|
"fields": [
|
|
@@ -6064,6 +6101,11 @@ export const REQUEST_SHAPES = {
|
|
|
6064
6101
|
{
|
|
6065
6102
|
"name": "position",
|
|
6066
6103
|
"type": "number | null"
|
|
6104
|
+
},
|
|
6105
|
+
{
|
|
6106
|
+
"name": "archived",
|
|
6107
|
+
"type": "boolean | null",
|
|
6108
|
+
"note": "Archived (PATCH only): false restores an archived area; true is refused — archiving has one door, DELETE, which checks the open tabs."
|
|
6067
6109
|
}
|
|
6068
6110
|
],
|
|
6069
6111
|
"source": "go",
|
|
@@ -6083,6 +6125,11 @@ export const REQUEST_SHAPES = {
|
|
|
6083
6125
|
{
|
|
6084
6126
|
"name": "position",
|
|
6085
6127
|
"type": "number | null"
|
|
6128
|
+
},
|
|
6129
|
+
{
|
|
6130
|
+
"name": "archived",
|
|
6131
|
+
"type": "boolean | null",
|
|
6132
|
+
"note": "Archived (PATCH only): false restores an archived area; true is refused — archiving has one door, DELETE, which checks the open tabs."
|
|
6086
6133
|
}
|
|
6087
6134
|
],
|
|
6088
6135
|
"source": "go",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GENERATED by scripts/gen-catalog.ts — do not edit by hand.
|
|
2
2
|
// The platform commit every other generated file here describes.
|
|
3
3
|
export const PLATFORM_SOURCE = {
|
|
4
|
-
"commit": "
|
|
5
|
-
"committedAt": "2026-10-
|
|
4
|
+
"commit": "9db1b2a9eede5be5f1081975bd7f687f0becfd4a",
|
|
5
|
+
"committedAt": "2026-10-11T21:21:05+07:00",
|
|
6
6
|
"dirty": false
|
|
7
7
|
};
|
|
@@ -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];
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { createServer } from './server.js';
|
|
3
|
+
import { buildContext, createServer } from './server.js';
|
|
4
4
|
import { runInstallCli } from './install/index.js';
|
|
5
|
+
import { closeBrowser } from './vision/shoot.js';
|
|
5
6
|
async function main() {
|
|
6
7
|
// `sbuilder-mcp install` writes this server into the agent clients on this
|
|
7
8
|
// machine and exits. Checked BEFORE the transport opens: an installer that
|
|
@@ -9,16 +10,30 @@ async function main() {
|
|
|
9
10
|
if (process.argv[2] === 'install') {
|
|
10
11
|
process.exit(runInstallCli(process.argv.slice(3)));
|
|
11
12
|
}
|
|
12
|
-
const
|
|
13
|
+
const ctx = buildContext();
|
|
14
|
+
const server = createServer(ctx);
|
|
13
15
|
await server.connect(new StdioServerTransport());
|
|
14
16
|
// THE CLIENT CLOSED STDIN, SO THIS PROCESS IS DONE. The SDK does not exit on
|
|
15
17
|
// its own, and the live-room socket (plus its reconnect timer) keeps the event
|
|
16
18
|
// loop alive — an orphaned process that answers the server's pings, so the
|
|
17
19
|
// agent's avatar stays in the room indefinitely. Exiting closes the socket and
|
|
18
20
|
// the server publishes the leave.
|
|
19
|
-
|
|
20
|
-
process.
|
|
21
|
-
|
|
21
|
+
// Sign the device session out (see Session.logout) and close a pooled Chrome
|
|
22
|
+
// first, bounded so a dead platform cannot keep the process alive. A signal is
|
|
23
|
+
// re-raised once that is done, so the exit status is the one it would have had.
|
|
24
|
+
let exiting = false;
|
|
25
|
+
const done = (signal) => () => {
|
|
26
|
+
if (exiting)
|
|
27
|
+
return;
|
|
28
|
+
exiting = true;
|
|
29
|
+
const leave = () => (signal ? process.kill(process.pid, signal) : process.exit(0));
|
|
30
|
+
setTimeout(leave, 3000).unref();
|
|
31
|
+
void Promise.allSettled([ctx.session.logout(), closeBrowser()]).then(leave);
|
|
32
|
+
};
|
|
33
|
+
process.stdin.once('end', done());
|
|
34
|
+
process.stdin.once('close', done());
|
|
35
|
+
process.once('SIGTERM', done('SIGTERM'));
|
|
36
|
+
process.once('SIGINT', done('SIGINT'));
|
|
22
37
|
// stdout is the MCP channel. Every log line in this repo is console.error.
|
|
23
38
|
console.error('[sbuilder-mcp] ready on stdio');
|
|
24
39
|
}
|
package/dist/tools/page.js
CHANGED
|
@@ -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/dist/transport/auth.js
CHANGED
|
@@ -32,7 +32,17 @@ export class Session {
|
|
|
32
32
|
loggedIn() {
|
|
33
33
|
return this.access !== null;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
pending = Promise.resolve();
|
|
36
|
+
/** Serialised: two concurrent sb_connect calls would each sign in and strand one device. */
|
|
37
|
+
login(email, password) {
|
|
38
|
+
const run = this.pending.then(() => this.signIn(email, password));
|
|
39
|
+
this.pending = run.catch(() => { });
|
|
40
|
+
return run;
|
|
41
|
+
}
|
|
42
|
+
async signIn(email, password) {
|
|
43
|
+
// A second sb_connect in one process would otherwise strand the first
|
|
44
|
+
// device session — see logout().
|
|
45
|
+
await this.logout();
|
|
36
46
|
const out = (await request({
|
|
37
47
|
base: this.base,
|
|
38
48
|
method: 'POST',
|
|
@@ -58,4 +68,34 @@ export class Session {
|
|
|
58
68
|
this.access = out.tokens.accessToken;
|
|
59
69
|
this.refreshToken = out.tokens.refreshToken;
|
|
60
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Sign this device out. Best effort, never throws.
|
|
73
|
+
*
|
|
74
|
+
* EVERY password sign-in is its own DEVICE SESSION since web_builder 9db1b2a9e,
|
|
75
|
+
* capped at 10 per account; past the cap the one that has gone longest
|
|
76
|
+
* without a REFRESH is deleted. This client never refreshes (the access token
|
|
77
|
+
* lives 30 days), so a process that exits without signing out leaves a slot
|
|
78
|
+
* held, and about ten agent runs later the evicted session is the merchant's
|
|
79
|
+
* own editor — signed out mid-edit, by nothing they can see. A SIGKILLed
|
|
80
|
+
* process still leaks its slot; nothing on this side can prevent that.
|
|
81
|
+
*/
|
|
82
|
+
async logout() {
|
|
83
|
+
const refreshToken = this.refreshToken;
|
|
84
|
+
this.access = null;
|
|
85
|
+
this.refreshToken = null;
|
|
86
|
+
if (!refreshToken)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
await request({
|
|
90
|
+
base: this.base,
|
|
91
|
+
method: 'POST',
|
|
92
|
+
path: '/api/auth/logout',
|
|
93
|
+
body: { refreshToken },
|
|
94
|
+
fetchImpl: this.fetchImpl,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error('[sbuilder-mcp] sign-out failed:', err.message);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
61
101
|
}
|
package/dist/vision/capture.js
CHANGED
|
@@ -1378,7 +1378,13 @@ function capturePage(limits) {
|
|
|
1378
1378
|
async function withBrowser(fn) {
|
|
1379
1379
|
let browser;
|
|
1380
1380
|
try {
|
|
1381
|
-
|
|
1381
|
+
// No Playwright signal handlers: `src/index.ts` owns them (see shoot.ts).
|
|
1382
|
+
browser = await chromium.launch({
|
|
1383
|
+
channel: 'chrome',
|
|
1384
|
+
handleSIGINT: false,
|
|
1385
|
+
handleSIGTERM: false,
|
|
1386
|
+
handleSIGHUP: false,
|
|
1387
|
+
});
|
|
1382
1388
|
}
|
|
1383
1389
|
catch (e) {
|
|
1384
1390
|
throw new Error('sbuilder: could not start Chrome to read that page. This server uses the SYSTEM ' +
|
package/dist/vision/shoot.js
CHANGED
|
@@ -32,7 +32,7 @@ async function launch() {
|
|
|
32
32
|
// its SIGTERM handler closes browsers without exiting. Its handlers live until
|
|
33
33
|
// the browser process closes, so a signal re-raised by this module could land
|
|
34
34
|
// while Playwright is still listening and the server would survive a signal it
|
|
35
|
-
// should die on.
|
|
35
|
+
// should die on. `src/index.ts` owns the handlers; Playwright gets none.
|
|
36
36
|
const realLauncher = () => chromium.launch({
|
|
37
37
|
channel: 'chrome',
|
|
38
38
|
headless: true,
|
|
@@ -91,9 +91,10 @@ export async function closeBrowser() {
|
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
93
|
* A kept browser is a child process, and a child process outlives a parent that
|
|
94
|
-
* forgets it.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
94
|
+
* forgets it. The MCP server closes it on every way out (stdin close, SIGINT,
|
|
95
|
+
* SIGTERM) in `src/index.ts`, which owns the signal handlers: a second handler
|
|
96
|
+
* here re-raised the signal the moment Chrome was closed — at once when none was
|
|
97
|
+
* open — and killed the process before the server signed its device session out.
|
|
97
98
|
*
|
|
98
99
|
* THERE USED TO BE A `beforeExit` HANDLER HERE TOO, and it could never fire.
|
|
99
100
|
* `beforeExit` runs when the event loop DRAINS, and an open browser connection
|
|
@@ -110,11 +111,6 @@ export async function closeBrowser() {
|
|
|
110
111
|
* pooling is deliberate (a vision loop shoots constantly and must not pay a
|
|
111
112
|
* launch each time); the cost of it is this one line at every other call site.
|
|
112
113
|
*/
|
|
113
|
-
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
114
|
-
process.once(signal, () => {
|
|
115
|
-
void closeBrowser().finally(() => process.kill(process.pid, signal));
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
114
|
/**
|
|
119
115
|
* Photograph a rendered page at several widths, and measure every node.
|
|
120
116
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.2",
|
|
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",
|