frameworc-mcp 0.2.0 → 0.4.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/README.md +230 -215
- package/dist/api-client.js +141 -29
- package/dist/catalogue.js +12 -12
- package/dist/index.js +471 -41
- package/dist/session.js +11 -0
- package/package.json +1 -1
package/dist/api-client.js
CHANGED
|
@@ -28,56 +28,168 @@ class ApiClient {
|
|
|
28
28
|
}
|
|
29
29
|
if (!res.ok) {
|
|
30
30
|
const msg = json && typeof json === "object" && json.errors
|
|
31
|
-
? `HTTP ${res.status}: ${JSON.stringify(json.errors)}`
|
|
31
|
+
? `HTTP ${res.status}: ${json.message ?? ""} ${JSON.stringify(json.errors)}`
|
|
32
32
|
: `HTTP ${res.status}: ${typeof json === "string" ? json : JSON.stringify(json)}`;
|
|
33
|
-
throw new Error(msg);
|
|
33
|
+
throw new Error(msg.trim());
|
|
34
34
|
}
|
|
35
35
|
return json;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/** Appends the effective CMS site id to a query string. */
|
|
38
|
+
q(path, siteId) {
|
|
39
|
+
const id = siteId ?? this.session.getSiteId();
|
|
40
|
+
if (id === null || id === undefined)
|
|
41
|
+
return path;
|
|
42
|
+
return path + (path.includes("?") ? "&" : "?") + `site_id=${id}`;
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
/** Merges the effective CMS site id into a JSON body. */
|
|
45
|
+
b(body, siteId) {
|
|
46
|
+
const id = siteId ?? this.session.getSiteId();
|
|
47
|
+
const merged = { ...(body ?? {}) };
|
|
48
|
+
if (id !== null && id !== undefined && merged.site_id === undefined) {
|
|
49
|
+
merged.site_id = id;
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(merged);
|
|
52
|
+
}
|
|
53
|
+
// --- sites & schema ---------------------------------------------------
|
|
54
|
+
listCmsSites() {
|
|
55
|
+
return this.request("/sites");
|
|
56
|
+
}
|
|
57
|
+
listBlocks() {
|
|
58
|
+
return this.request("/blocks");
|
|
59
|
+
}
|
|
60
|
+
getBlockSchema(name) {
|
|
61
|
+
return this.request(`/blocks/${encodeURIComponent(name)}`);
|
|
62
|
+
}
|
|
63
|
+
// --- pages ------------------------------------------------------------
|
|
64
|
+
listPages(siteId) {
|
|
65
|
+
return this.request(this.q("/pages", siteId));
|
|
42
66
|
}
|
|
43
|
-
|
|
44
|
-
return this.request(
|
|
67
|
+
getPage(id, siteId) {
|
|
68
|
+
return this.request(this.q(`/pages/${id}`, siteId));
|
|
45
69
|
}
|
|
46
|
-
|
|
47
|
-
return this.request(
|
|
70
|
+
createPage(payload, siteId) {
|
|
71
|
+
return this.request("/pages", { method: "POST", body: this.b(payload, siteId) });
|
|
72
|
+
}
|
|
73
|
+
updatePage(id, payload, siteId) {
|
|
74
|
+
return this.request(`/pages/${id}`, { method: "PATCH", body: this.b(payload, siteId) });
|
|
75
|
+
}
|
|
76
|
+
deletePage(id, siteId) {
|
|
77
|
+
return this.request(this.q(`/pages/${id}`, siteId), { method: "DELETE" });
|
|
78
|
+
}
|
|
79
|
+
createTranslation(id, targetSiteId, payload, sourceSiteId) {
|
|
80
|
+
const body = { ...(payload ?? {}), site_id: targetSiteId };
|
|
81
|
+
const source = sourceSiteId ?? this.session.getSiteId();
|
|
82
|
+
if (source !== null && source !== undefined) {
|
|
83
|
+
body.source_site_id = source;
|
|
84
|
+
}
|
|
85
|
+
return this.request(`/pages/${id}/translations`, { method: "POST", body: JSON.stringify(body) });
|
|
48
86
|
}
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
// --- blocks (per page or per prefill) ---------------------------------
|
|
88
|
+
blockBase(host) {
|
|
89
|
+
return host.type === "prefill" ? `/prefills/${host.id}` : `/pages/${host.id}`;
|
|
51
90
|
}
|
|
52
|
-
addBlock(
|
|
53
|
-
return this.request(
|
|
91
|
+
addBlock(host, block, position, siteId) {
|
|
92
|
+
return this.request(`${this.blockBase(host)}/blocks`, {
|
|
54
93
|
method: "POST",
|
|
55
|
-
body:
|
|
94
|
+
body: this.b({ block, position }, siteId),
|
|
56
95
|
});
|
|
57
96
|
}
|
|
58
|
-
updateBlock(
|
|
59
|
-
return this.request(
|
|
97
|
+
updateBlock(host, blockId, block, siteId) {
|
|
98
|
+
return this.request(`${this.blockBase(host)}/blocks/${blockId}`, {
|
|
60
99
|
method: "PATCH",
|
|
61
|
-
body:
|
|
100
|
+
body: this.b(block, siteId),
|
|
62
101
|
});
|
|
63
102
|
}
|
|
64
|
-
removeBlock(
|
|
65
|
-
return this.request(
|
|
103
|
+
removeBlock(host, blockId, siteId) {
|
|
104
|
+
return this.request(this.q(`${this.blockBase(host)}/blocks/${blockId}`, siteId), { method: "DELETE" });
|
|
66
105
|
}
|
|
67
|
-
reorderBlocks(
|
|
68
|
-
return this.request(
|
|
106
|
+
reorderBlocks(host, order, siteId) {
|
|
107
|
+
return this.request(`${this.blockBase(host)}/blocks/order`, {
|
|
69
108
|
method: "POST",
|
|
70
|
-
body:
|
|
109
|
+
body: this.b({ order }, siteId),
|
|
71
110
|
});
|
|
72
111
|
}
|
|
73
|
-
|
|
74
|
-
return this.request(
|
|
112
|
+
extractBlockToPrefill(pageId, blockId, title, siteId) {
|
|
113
|
+
return this.request(`/pages/${pageId}/blocks/${blockId}/extract-to-prefill`, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
body: this.b({ title }, siteId),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// --- forms --------------------------------------------------------------
|
|
119
|
+
listForms(siteId) {
|
|
120
|
+
return this.request(this.q("/forms", siteId));
|
|
121
|
+
}
|
|
122
|
+
getFormSchema() {
|
|
123
|
+
return this.request("/forms/schema");
|
|
124
|
+
}
|
|
125
|
+
getForm(id, siteId) {
|
|
126
|
+
return this.request(this.q(`/forms/${id}`, siteId));
|
|
127
|
+
}
|
|
128
|
+
createForm(payload, siteId) {
|
|
129
|
+
return this.request("/forms", { method: "POST", body: this.b(payload, siteId) });
|
|
130
|
+
}
|
|
131
|
+
updateForm(id, payload, siteId) {
|
|
132
|
+
return this.request(`/forms/${id}`, { method: "PATCH", body: this.b(payload, siteId) });
|
|
133
|
+
}
|
|
134
|
+
deleteForm(id, force, siteId) {
|
|
135
|
+
return this.request(this.q(`/forms/${id}${force ? "?force=1" : ""}`, siteId), { method: "DELETE" });
|
|
75
136
|
}
|
|
76
|
-
|
|
77
|
-
|
|
137
|
+
// --- menus --------------------------------------------------------------
|
|
138
|
+
listMenus(siteId) {
|
|
139
|
+
return this.request(this.q("/menus", siteId));
|
|
78
140
|
}
|
|
79
|
-
|
|
80
|
-
return this.request(
|
|
141
|
+
getMenu(id, siteId) {
|
|
142
|
+
return this.request(this.q(`/menus/${id}`, siteId));
|
|
143
|
+
}
|
|
144
|
+
createMenu(payload, siteId) {
|
|
145
|
+
return this.request("/menus", { method: "POST", body: this.b(payload, siteId) });
|
|
146
|
+
}
|
|
147
|
+
updateMenu(id, payload, siteId) {
|
|
148
|
+
return this.request(`/menus/${id}`, { method: "PATCH", body: this.b(payload, siteId) });
|
|
149
|
+
}
|
|
150
|
+
deleteMenu(id, force, siteId) {
|
|
151
|
+
return this.request(this.q(`/menus/${id}${force ? "?force=1" : ""}`, siteId), { method: "DELETE" });
|
|
152
|
+
}
|
|
153
|
+
// --- prefills -----------------------------------------------------------
|
|
154
|
+
listPrefills(siteId) {
|
|
155
|
+
return this.request(this.q("/prefills", siteId));
|
|
156
|
+
}
|
|
157
|
+
getPrefill(id, siteId) {
|
|
158
|
+
return this.request(this.q(`/prefills/${id}`, siteId));
|
|
159
|
+
}
|
|
160
|
+
createPrefill(payload, siteId) {
|
|
161
|
+
return this.request("/prefills", { method: "POST", body: this.b(payload, siteId) });
|
|
162
|
+
}
|
|
163
|
+
updatePrefill(id, payload, siteId) {
|
|
164
|
+
return this.request(`/prefills/${id}`, { method: "PATCH", body: this.b(payload, siteId) });
|
|
165
|
+
}
|
|
166
|
+
deletePrefill(id, force, siteId) {
|
|
167
|
+
return this.request(this.q(`/prefills/${id}${force ? "?force=1" : ""}`, siteId), { method: "DELETE" });
|
|
168
|
+
}
|
|
169
|
+
createPrefillTranslation(id, targetSiteId, payload, sourceSiteId) {
|
|
170
|
+
const body = { ...(payload ?? {}), site_id: targetSiteId };
|
|
171
|
+
const source = sourceSiteId ?? this.session.getSiteId();
|
|
172
|
+
if (source !== null && source !== undefined) {
|
|
173
|
+
body.source_site_id = source;
|
|
174
|
+
}
|
|
175
|
+
return this.request(`/prefills/${id}/translations`, { method: "POST", body: JSON.stringify(body) });
|
|
176
|
+
}
|
|
177
|
+
// --- FrameworC settings (global) ---------------------------------------
|
|
178
|
+
getSettings() {
|
|
179
|
+
return this.request("/settings");
|
|
180
|
+
}
|
|
181
|
+
updateSettings(fields) {
|
|
182
|
+
return this.request("/settings", { method: "PATCH", body: JSON.stringify({ fields }) });
|
|
183
|
+
}
|
|
184
|
+
// --- singles ----------------------------------------------------------
|
|
185
|
+
getSingle(handle, siteId) {
|
|
186
|
+
return this.request(this.q(`/singles/${encodeURIComponent(handle)}`, siteId));
|
|
187
|
+
}
|
|
188
|
+
updateSingle(handle, fields, siteId) {
|
|
189
|
+
return this.request(`/singles/${encodeURIComponent(handle)}`, {
|
|
190
|
+
method: "PATCH",
|
|
191
|
+
body: this.b(fields, siteId),
|
|
192
|
+
});
|
|
81
193
|
}
|
|
82
194
|
}
|
|
83
195
|
exports.ApiClient = ApiClient;
|
package/dist/catalogue.js
CHANGED
|
@@ -83,10 +83,10 @@ exports.BLOCKS = [
|
|
|
83
83
|
content_group: "Tiles",
|
|
84
84
|
name: "Tiles (grid of cards)",
|
|
85
85
|
description: "Grid of cards (icon + headline + rich text + button each).",
|
|
86
|
-
whenToUse: "Feature lists, service grids, 'value proposition' card rows,
|
|
86
|
+
whenToUse: "Feature lists, service grids, 'value proposition' card rows, or any list of related items.",
|
|
87
87
|
base: baseBlock,
|
|
88
88
|
content: [
|
|
89
|
-
{ name: "tiles", type: "
|
|
89
|
+
{ name: "tiles", type: "repeater", comment: "Array of tile items, each {headline, image, text, buttonLabel1, buttonVariant1, buttonContrast1, buttonLink1, buttonIcon1, buttonBlank1, buttonLabel2, ...}. Use this to list the tiles." },
|
|
90
90
|
{ name: "tileLink", type: "switch", default: false, comment: "Whole tile acts as a link." },
|
|
91
91
|
{ name: "tileIcon", type: "switch", default: false, comment: "Display icon-only tiles." },
|
|
92
92
|
{ name: "contrastHover", type: "switch", default: false },
|
|
@@ -116,7 +116,7 @@ exports.BLOCKS = [
|
|
|
116
116
|
whenToUse: "Image-heavy content that benefits from sequential browsing — testimonials, step-by-step walkthroughs, gallery-like rows.",
|
|
117
117
|
base: baseBlock,
|
|
118
118
|
content: [
|
|
119
|
-
{ name: "slides", type: "
|
|
119
|
+
{ name: "slides", type: "repeater", comment: "Array of slide items, each {image, headline, text, buttonLabel1, buttonVariant1, ...}." },
|
|
120
120
|
{ name: "gallery", type: "switch", default: false, comment: "Clicking a slide opens the gallery lightbox." },
|
|
121
121
|
{ name: "loop", type: "switch", default: false, comment: "Infinite loop playback." },
|
|
122
122
|
{ name: "autoWidth", type: "switch", default: false, comment: "Slide width matches image width." },
|
|
@@ -149,7 +149,7 @@ exports.BLOCKS = [
|
|
|
149
149
|
whenToUse: "Long content split into topic tabs (e.g. 'What we do' / 'Who we are' / 'Contact'). Don't use if there are only 1–2 logical sections — use Section instead.",
|
|
150
150
|
base: baseBlock,
|
|
151
151
|
content: [
|
|
152
|
-
{ name: "slides", type: "
|
|
152
|
+
{ name: "slides", type: "repeater", comment: "Array of tab items, each {image, headline, text, buttonLabel1, ...}." },
|
|
153
153
|
{ name: "gallery", type: "switch", default: false },
|
|
154
154
|
{ name: "variant", type: "dropdown", enum: ["tabs-horizontal", "tabs-vertical"], default: "tabs-horizontal" },
|
|
155
155
|
],
|
|
@@ -173,7 +173,7 @@ exports.BLOCKS = [
|
|
|
173
173
|
whenToUse: "FAQs, 'Common questions', or any content the user wants collapsed by default. Each item has a headline + rich text.",
|
|
174
174
|
base: baseBlock,
|
|
175
175
|
content: [
|
|
176
|
-
{ name: "items", type: "
|
|
176
|
+
{ name: "items", type: "repeater", comment: "Array of items, each {headline, text}." },
|
|
177
177
|
{ name: "variant", type: "dropdown", enum: ["default", "outline", "shadow"], default: "default" },
|
|
178
178
|
{ name: "firstItemOpen", type: "switch", default: false, comment: "First item is expanded on load." },
|
|
179
179
|
{ name: "singleMode", type: "switch", default: false, comment: "Only one item open at a time." },
|
|
@@ -194,12 +194,12 @@ exports.BLOCKS = [
|
|
|
194
194
|
content_group: "Form",
|
|
195
195
|
name: "Form",
|
|
196
196
|
description: "Contact / inquiry form wired to a separately-defined Form Tailor entry.",
|
|
197
|
-
whenToUse: "Only when a contact form is needed AND the referenced Form entry already exists.
|
|
197
|
+
whenToUse: "Only when a contact form is needed AND the referenced Form entry already exists. Set the block-level `form` key to the entry id from the `list_forms` tool. The actual form fields live on the linked Form entry, not on this block.",
|
|
198
198
|
base: baseBlock,
|
|
199
199
|
content: [
|
|
200
200
|
{ name: "variant", type: "dropdown", enum: ["default", "outline", "card"], default: "default" },
|
|
201
201
|
],
|
|
202
|
-
referencing: { kind: "form", location: "block", jsonKey: "
|
|
202
|
+
referencing: { kind: "form", location: "block", jsonKey: "form", column: "form_id" },
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
content_group: "Gallery",
|
|
@@ -245,7 +245,7 @@ exports.BLOCKS = [
|
|
|
245
245
|
description: "One repeater item per column. Each column has {blockId, builder: [...blocks]} where builder is a full Builder array.",
|
|
246
246
|
items: [
|
|
247
247
|
{ name: "blockId", type: "text", comment: "Optional column anchor id (used for the column wrapper)." },
|
|
248
|
-
{ name: "builder", type: "
|
|
248
|
+
{ name: "builder", type: "repeater", comment: "Array of block objects (same shape as top-level 'builder' in a page). You can nest any block here, including another Columns (avoid deep nesting)." },
|
|
249
249
|
],
|
|
250
250
|
},
|
|
251
251
|
],
|
|
@@ -254,10 +254,10 @@ exports.BLOCKS = [
|
|
|
254
254
|
content_group: "Prefill",
|
|
255
255
|
name: "Prefill (reusable block template)",
|
|
256
256
|
description: "References a Prefill Tailor entry whose builder content is shared across pages — edit once, updates everywhere it is used.",
|
|
257
|
-
whenToUse: "For shared/global blocks like a newsletter CTA, a contact form snippet, or a footer-style band that should appear the same on many pages. The actual block content lives on the Prefill entry — get its id via `list_prefills`. Don't use this for one-off content.",
|
|
257
|
+
whenToUse: "For shared/global blocks like a newsletter CTA, a contact form snippet, or a footer-style band that should appear the same on many pages. The actual block content lives on the Prefill entry — get its id via `list_prefills`. Whenever the same section would appear on more than one page, do NOT copy it: keep it in one Prefill (create_prefill, or extract_block_to_prefill for an existing page block) and insert this block on every page that needs it. Don't use this for one-off content.",
|
|
258
258
|
base: baseBlock,
|
|
259
259
|
content: [],
|
|
260
|
-
referencing: { kind: "prefill", location: "content", jsonKey: "
|
|
260
|
+
referencing: { kind: "prefill", location: "content", jsonKey: "block", column: "block_id" },
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
content_group: "BlogList",
|
|
@@ -276,10 +276,10 @@ exports.BLOCKS = [
|
|
|
276
276
|
content_group: "MenuBlock",
|
|
277
277
|
name: "Menu block",
|
|
278
278
|
description: "Renders an existing Menu Tailor entry inline in the page body (flattened to 2 levels).",
|
|
279
|
-
whenToUse: "When you need to embed navigation as content (e.g. an on-page section that lists links).
|
|
279
|
+
whenToUse: "When you need to embed navigation as content (e.g. an on-page section that lists links). Set `content.menu` to the entry id from `list_menus`.",
|
|
280
280
|
base: baseBlock,
|
|
281
281
|
content: [],
|
|
282
|
-
referencing: { kind: "menu", location: "content", jsonKey: "
|
|
282
|
+
referencing: { kind: "menu", location: "content", jsonKey: "menu", column: "menu_id" },
|
|
283
283
|
},
|
|
284
284
|
{
|
|
285
285
|
content_group: "ImageStrip",
|