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/index.js
CHANGED
|
@@ -13,6 +13,17 @@ const CATALOGUE_URI = "frameworc://blocks";
|
|
|
13
13
|
(0, sites_js_1.warnIfWorldReadable)();
|
|
14
14
|
const session = new session_js_1.Session();
|
|
15
15
|
const client = new api_client_js_1.ApiClient(session);
|
|
16
|
+
/** Resolves the block-op target from page_id / prefill_id args. */
|
|
17
|
+
function blockHost(args) {
|
|
18
|
+
const hasPage = args.page_id !== undefined && args.page_id !== null;
|
|
19
|
+
const hasPrefill = args.prefill_id !== undefined && args.prefill_id !== null;
|
|
20
|
+
if (hasPage === hasPrefill) {
|
|
21
|
+
throw new Error("Pass exactly one of page_id / prefill_id.");
|
|
22
|
+
}
|
|
23
|
+
return hasPrefill
|
|
24
|
+
? { type: "prefill", id: Number(args.prefill_id) }
|
|
25
|
+
: { type: "page", id: Number(args.page_id) };
|
|
26
|
+
}
|
|
16
27
|
const tools = [
|
|
17
28
|
{
|
|
18
29
|
name: "use_site",
|
|
@@ -37,12 +48,34 @@ const tools = [
|
|
|
37
48
|
properties: {},
|
|
38
49
|
},
|
|
39
50
|
},
|
|
51
|
+
{
|
|
52
|
+
name: "list_cms_sites",
|
|
53
|
+
description: "List the multisite sites configured INSIDE the pinned OctoberCMS install (languages, regional variants). Returns [{id, name, code, locale, is_enabled, is_primary}]. Call this before creating content on a multi-language project so you know which site_id to target. Not to be confused with list_sites, which lists separate CMS installs from your local config file.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: { site_url: { type: "string" } },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "use_cms_site",
|
|
61
|
+
description: "Pin which multisite site (language) subsequent calls read and write. Pass the id from list_cms_sites, or null to go back to the primary site. Every page/single tool also accepts a one-off site_id argument that overrides this.",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
site_id: { type: ["integer", "null"], description: "Site id from list_cms_sites, or null for the primary site." },
|
|
66
|
+
},
|
|
67
|
+
required: ["site_id"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
40
70
|
{
|
|
41
71
|
name: "list_pages",
|
|
42
|
-
description: "List all FrameworC pages on the pinned site.",
|
|
72
|
+
description: "List all FrameworC pages on the pinned site (and pinned CMS site, if set).",
|
|
43
73
|
inputSchema: {
|
|
44
74
|
type: "object",
|
|
45
|
-
properties: {
|
|
75
|
+
properties: {
|
|
76
|
+
site_url: { type: "string", description: "Override the pinned site URL for this call." },
|
|
77
|
+
site_id: { type: "integer", description: "Override the pinned multisite site for this call." },
|
|
78
|
+
},
|
|
46
79
|
},
|
|
47
80
|
},
|
|
48
81
|
{
|
|
@@ -53,6 +86,7 @@ const tools = [
|
|
|
53
86
|
properties: {
|
|
54
87
|
id: { type: "integer", description: "Page id." },
|
|
55
88
|
site_url: { type: "string" },
|
|
89
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
56
90
|
},
|
|
57
91
|
required: ["id"],
|
|
58
92
|
},
|
|
@@ -73,6 +107,7 @@ const tools = [
|
|
|
73
107
|
required: ["page"],
|
|
74
108
|
},
|
|
75
109
|
site_url: { type: "string" },
|
|
110
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
76
111
|
},
|
|
77
112
|
required: ["payload"],
|
|
78
113
|
},
|
|
@@ -86,6 +121,7 @@ const tools = [
|
|
|
86
121
|
id: { type: "integer" },
|
|
87
122
|
payload: { type: "object" },
|
|
88
123
|
site_url: { type: "string" },
|
|
124
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
89
125
|
},
|
|
90
126
|
required: ["id", "payload"],
|
|
91
127
|
},
|
|
@@ -98,101 +134,383 @@ const tools = [
|
|
|
98
134
|
properties: {
|
|
99
135
|
id: { type: "integer" },
|
|
100
136
|
site_url: { type: "string" },
|
|
137
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
101
138
|
},
|
|
102
139
|
required: ["id"],
|
|
103
140
|
},
|
|
104
141
|
},
|
|
105
142
|
{
|
|
106
143
|
name: "add_block",
|
|
107
|
-
description: "Append a block to a page's builder. Optional `position` (1-indexed) inserts at that position; otherwise appends at the end. The `block` arg has the shape { content_group, base, content,
|
|
144
|
+
description: "Append a block to a page's builder — or a prefill's, by passing prefill_id instead of page_id. Optional `position` (1-indexed) inserts at that position; otherwise appends at the end. The `block` arg has the shape { content_group, base, content, form? } per the catalogue — `form` sits at block level, while Menu and Prefill references live at `content.menu` and `content.block`. If the section you are adding already exists on another page, do NOT copy it: reference its Prefill entry with a `Prefill` block (extract_block_to_prefill turns an existing page block into one).",
|
|
108
145
|
inputSchema: {
|
|
109
146
|
type: "object",
|
|
110
147
|
properties: {
|
|
111
|
-
page_id: { type: "integer" },
|
|
148
|
+
page_id: { type: "integer", description: "Page to edit. Pass exactly one of page_id / prefill_id." },
|
|
149
|
+
prefill_id: { type: "integer", description: "Prefill entry to edit instead of a page." },
|
|
112
150
|
block: { type: "object" },
|
|
113
151
|
position: { type: "integer" },
|
|
114
152
|
site_url: { type: "string" },
|
|
153
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
115
154
|
},
|
|
116
|
-
required: ["
|
|
155
|
+
required: ["block"],
|
|
117
156
|
},
|
|
118
157
|
},
|
|
119
158
|
{
|
|
120
159
|
name: "update_block",
|
|
121
|
-
description: "Replace a single block by id (the row id
|
|
160
|
+
description: "Replace a single block by id (the row id from get_page's builder[].id) on a page — or a prefill via prefill_id. The new `block` must include `content_group` plus `base` + `content`. The block is replaced, not patched: the returned row id is NEW. Media a human assigned in the backend is carried over automatically wherever your payload leaves the field empty — unless you change content_group. Other blocks are untouched.",
|
|
122
161
|
inputSchema: {
|
|
123
162
|
type: "object",
|
|
124
163
|
properties: {
|
|
125
|
-
page_id: { type: "integer" },
|
|
164
|
+
page_id: { type: "integer", description: "Page to edit. Pass exactly one of page_id / prefill_id." },
|
|
165
|
+
prefill_id: { type: "integer", description: "Prefill entry to edit instead of a page." },
|
|
126
166
|
block_id: { type: "integer" },
|
|
127
167
|
block: { type: "object" },
|
|
128
168
|
site_url: { type: "string" },
|
|
169
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
129
170
|
},
|
|
130
|
-
required: ["
|
|
171
|
+
required: ["block_id", "block"],
|
|
131
172
|
},
|
|
132
173
|
},
|
|
133
174
|
{
|
|
134
175
|
name: "remove_block",
|
|
135
|
-
description: "Remove a block by its row id from
|
|
176
|
+
description: "Remove a block by its row id from a page's builder (or a prefill's via prefill_id). Remaining blocks keep their ids; ordering closes the gap.",
|
|
136
177
|
inputSchema: {
|
|
137
178
|
type: "object",
|
|
138
179
|
properties: {
|
|
139
|
-
page_id: { type: "integer" },
|
|
180
|
+
page_id: { type: "integer", description: "Page to edit. Pass exactly one of page_id / prefill_id." },
|
|
181
|
+
prefill_id: { type: "integer", description: "Prefill entry to edit instead of a page." },
|
|
140
182
|
block_id: { type: "integer" },
|
|
141
183
|
site_url: { type: "string" },
|
|
184
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
142
185
|
},
|
|
143
|
-
required: ["
|
|
186
|
+
required: ["block_id"],
|
|
144
187
|
},
|
|
145
188
|
},
|
|
146
189
|
{
|
|
147
190
|
name: "reorder_blocks",
|
|
148
|
-
description: "Reorder blocks. Send every block row id in the desired order (all existing block ids must be present, no extras).",
|
|
191
|
+
description: "Reorder blocks on a page (or a prefill via prefill_id). Send every block row id in the desired order (all existing block ids must be present, no extras).",
|
|
149
192
|
inputSchema: {
|
|
150
193
|
type: "object",
|
|
151
194
|
properties: {
|
|
152
|
-
page_id: { type: "integer" },
|
|
195
|
+
page_id: { type: "integer", description: "Page to edit. Pass exactly one of page_id / prefill_id." },
|
|
196
|
+
prefill_id: { type: "integer", description: "Prefill entry to edit instead of a page." },
|
|
153
197
|
order: { type: "array", items: { type: "integer" } },
|
|
154
198
|
site_url: { type: "string" },
|
|
199
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
200
|
+
},
|
|
201
|
+
required: ["order"],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "extract_block_to_prefill",
|
|
206
|
+
description: "Move an existing page block into a NEW Prefill entry and replace it in place with a `Prefill` reference block. Lossless — media a human assigned survives the move. Use this whenever a section should appear on more than one page: extract it once, then add_block a `Prefill` block ({content_group:'Prefill', content:{block:<prefill id>}}) on the other pages. This is the intended FrameworC workflow; never copy the same section onto multiple pages.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
page_id: { type: "integer" },
|
|
211
|
+
block_id: { type: "integer", description: "Row id of the block to extract." },
|
|
212
|
+
title: { type: "string", description: "Title for the new Prefill entry, e.g. 'Newsletter CTA'." },
|
|
213
|
+
site_url: { type: "string" },
|
|
214
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
155
215
|
},
|
|
156
|
-
required: ["page_id", "
|
|
216
|
+
required: ["page_id", "block_id", "title"],
|
|
157
217
|
},
|
|
158
218
|
},
|
|
159
219
|
{
|
|
160
220
|
name: "list_forms",
|
|
161
|
-
description: "List Form
|
|
221
|
+
description: "List Form entries (id, title, slug, field_count, ...). Use get_form for a form's fields, create_form / update_form / delete_form for CRUD, and the id for the `Form` block's block-level `form` key.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: {
|
|
225
|
+
site_url: { type: "string" },
|
|
226
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "get_form_schema",
|
|
232
|
+
description: "Fetch the live field-group catalogue for Form entries from the CMS: every fwcFields group with its exact fields, enums and defaults, plus the form-level fields. Use before create_form / update_form when unsure of the shape.",
|
|
162
233
|
inputSchema: {
|
|
163
234
|
type: "object",
|
|
164
235
|
properties: { site_url: { type: "string" } },
|
|
165
236
|
},
|
|
166
237
|
},
|
|
238
|
+
{
|
|
239
|
+
name: "get_form",
|
|
240
|
+
description: "Fetch one Form entry including its fwcFields rows (each {id, group, ...fields}).",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
id: { type: "integer" },
|
|
245
|
+
site_url: { type: "string" },
|
|
246
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
247
|
+
},
|
|
248
|
+
required: ["id"],
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "create_form",
|
|
253
|
+
description: "Create a Form entry. Payload: {form: {title (required), headline?, recipients?: [emails], is_enabled?}, fwcFields?: [rows]}. Each row names its `group` — one of: section, text, number, email, phone, textarea, file, select, checkbox, radio, agreement — plus that group's fields: input groups take {label, placeholder?, name (required, unique), fieldId?, required?, width?: full|half|third|twoThirds}; select/checkbox/radio add options: [{value, label}]; agreement adds text (rich HTML); section takes only content (rich HTML, not an input). Call get_form_schema when unsure of a group's exact fields. Reference the created form from a page with a `Form` block ({content_group:'Form', form:<id>, content:{variant:...}}).",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
payload: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: { form: { type: "object" }, fwcFields: { type: "array" } },
|
|
260
|
+
required: ["form"],
|
|
261
|
+
},
|
|
262
|
+
site_url: { type: "string" },
|
|
263
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
264
|
+
},
|
|
265
|
+
required: ["payload"],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "update_form",
|
|
270
|
+
description: "Update a Form entry. `form` merges scalar fields; if `fwcFields` is present the field rows are fully rebuilt from the array (send ALL rows, not a diff — they carry no media, so nothing is lost).",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
id: { type: "integer" },
|
|
275
|
+
payload: { type: "object", properties: { form: { type: "object" }, fwcFields: { type: "array" } } },
|
|
276
|
+
site_url: { type: "string" },
|
|
277
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
278
|
+
},
|
|
279
|
+
required: ["id", "payload"],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "delete_form",
|
|
284
|
+
description: "Delete a Form entry. Refused with a 422 naming the referencing pages if any `Form` block still points at it; pass force:true to delete anyway.",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
id: { type: "integer" },
|
|
289
|
+
force: { type: "boolean" },
|
|
290
|
+
site_url: { type: "string" },
|
|
291
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
292
|
+
},
|
|
293
|
+
required: ["id"],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
167
296
|
{
|
|
168
297
|
name: "list_menus",
|
|
169
|
-
description: "List Menu
|
|
298
|
+
description: "List Menu entries (id, title, item_count, ...). Use get_menu / create_menu / update_menu / delete_menu for CRUD, and the id for `content.menu` on a MenuBlock or the `nav` field of the Navigation/Footer singles.",
|
|
170
299
|
inputSchema: {
|
|
171
300
|
type: "object",
|
|
172
|
-
properties: {
|
|
301
|
+
properties: {
|
|
302
|
+
site_url: { type: "string" },
|
|
303
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "get_menu",
|
|
309
|
+
description: "Fetch one Menu entry including its navigation tree (items {id, title, url, anchor, blank, children?}).",
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: "object",
|
|
312
|
+
properties: {
|
|
313
|
+
id: { type: "integer" },
|
|
314
|
+
site_url: { type: "string" },
|
|
315
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
316
|
+
},
|
|
317
|
+
required: ["id"],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: "create_menu",
|
|
322
|
+
description: "Create a Menu entry. Payload: {menu: {title (required)}, navigation?: [items]}. Each item: {title (required), url, anchor?, blank?, children?: [items]}. `url` is a string ('/', '/kontakt#form', full URL) OR {page_id: n} to link a FrameworC page robustly. Max 2 levels — children may not have children.",
|
|
323
|
+
inputSchema: {
|
|
324
|
+
type: "object",
|
|
325
|
+
properties: {
|
|
326
|
+
payload: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: { menu: { type: "object" }, navigation: { type: "array" } },
|
|
329
|
+
required: ["menu"],
|
|
330
|
+
},
|
|
331
|
+
site_url: { type: "string" },
|
|
332
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
333
|
+
},
|
|
334
|
+
required: ["payload"],
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "update_menu",
|
|
339
|
+
description: "Update a Menu entry. `menu` merges title/slug/is_enabled; if `navigation` is present the whole tree is replaced from the array (send ALL items — they carry no media).",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
id: { type: "integer" },
|
|
344
|
+
payload: { type: "object", properties: { menu: { type: "object" }, navigation: { type: "array" } } },
|
|
345
|
+
site_url: { type: "string" },
|
|
346
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
347
|
+
},
|
|
348
|
+
required: ["id", "payload"],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "delete_menu",
|
|
353
|
+
description: "Delete a Menu entry. Refused with a 422 naming the referencing hosts (MenuBlocks, Navigation/Footer singles) if still referenced; pass force:true to delete anyway.",
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
id: { type: "integer" },
|
|
358
|
+
force: { type: "boolean" },
|
|
359
|
+
site_url: { type: "string" },
|
|
360
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
361
|
+
},
|
|
362
|
+
required: ["id"],
|
|
173
363
|
},
|
|
174
364
|
},
|
|
175
365
|
{
|
|
176
366
|
name: "list_prefills",
|
|
177
|
-
description: "List Prefill
|
|
367
|
+
description: "List Prefill entries (id, title, block_count, ...). Prefills hold sections shared across pages; reference one from a page with a `Prefill` block ({content_group:'Prefill', content:{block:<id>}}). Use get_prefill / create_prefill / update_prefill / delete_prefill for CRUD.",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
type: "object",
|
|
370
|
+
properties: {
|
|
371
|
+
site_url: { type: "string" },
|
|
372
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "get_prefill",
|
|
378
|
+
description: "Fetch one Prefill entry including its builder blocks — same block shape as get_page.",
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: {
|
|
382
|
+
id: { type: "integer" },
|
|
383
|
+
site_url: { type: "string" },
|
|
384
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
385
|
+
},
|
|
386
|
+
required: ["id"],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: "create_prefill",
|
|
391
|
+
description: "Create a Prefill entry — a reusable section shared across pages. Payload: {prefill: {title (required), slug?}, builder?: [blocks]} with the SAME block shape as create_page. When several pages need the same section, create the Prefill FIRST and reference it from each page with a `Prefill` block instead of duplicating the section. (To factor out a section that already exists on a page, use extract_block_to_prefill instead.) Per-block edits work via add_block/update_block/remove_block/reorder_blocks with prefill_id.",
|
|
392
|
+
inputSchema: {
|
|
393
|
+
type: "object",
|
|
394
|
+
properties: {
|
|
395
|
+
payload: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: { prefill: { type: "object" }, builder: { type: "array" } },
|
|
398
|
+
required: ["prefill"],
|
|
399
|
+
},
|
|
400
|
+
site_url: { type: "string" },
|
|
401
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
402
|
+
},
|
|
403
|
+
required: ["payload"],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "update_prefill",
|
|
408
|
+
description: "Update a Prefill entry. `prefill` merges title/slug/is_enabled; a `builder` array fully rebuilds the blocks (prefer the per-block tools with prefill_id — a rebuild loses human-assigned media). Edits apply everywhere the prefill is referenced.",
|
|
409
|
+
inputSchema: {
|
|
410
|
+
type: "object",
|
|
411
|
+
properties: {
|
|
412
|
+
id: { type: "integer" },
|
|
413
|
+
payload: { type: "object", properties: { prefill: { type: "object" }, builder: { type: "array" } } },
|
|
414
|
+
site_url: { type: "string" },
|
|
415
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
416
|
+
},
|
|
417
|
+
required: ["id", "payload"],
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "delete_prefill",
|
|
422
|
+
description: "Delete a Prefill entry. Refused with a 422 naming the referencing pages if any `Prefill` block still points at it; pass force:true to delete anyway (the referencing blocks would break).",
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: "object",
|
|
425
|
+
properties: {
|
|
426
|
+
id: { type: "integer" },
|
|
427
|
+
force: { type: "boolean" },
|
|
428
|
+
site_url: { type: "string" },
|
|
429
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
430
|
+
},
|
|
431
|
+
required: ["id"],
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "get_settings",
|
|
436
|
+
description: "Read the FrameworC plugin settings exposed over the API: navbar layout options and the custom SCSS variables. Settings are GLOBAL (not per multisite site). Integration/secret settings are not accessible by design.",
|
|
178
437
|
inputSchema: {
|
|
179
438
|
type: "object",
|
|
180
439
|
properties: { site_url: { type: "string" } },
|
|
181
440
|
},
|
|
182
441
|
},
|
|
442
|
+
{
|
|
443
|
+
name: "update_settings",
|
|
444
|
+
description: "Update FrameworC plugin settings. `fields` is a partial map; writable keys: navigation_width (full|container), navigation_align (left|center|right), navigation_mobile_extra_links (navbar|open), variable_variablesScss (SCSS overriding the design tokens). Settings are GLOBAL and affect the whole install — confirm with the user first. Integration/secret settings cannot be read or written through this API by design.",
|
|
445
|
+
inputSchema: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
fields: { type: "object", description: "Field name => value map (partial)." },
|
|
449
|
+
site_url: { type: "string" },
|
|
450
|
+
},
|
|
451
|
+
required: ["fields"],
|
|
452
|
+
},
|
|
453
|
+
},
|
|
183
454
|
{
|
|
184
455
|
name: "get_block_schema",
|
|
185
|
-
description: "Get the JSON schema + usage notes for
|
|
456
|
+
description: "Get the JSON schema + usage notes for one block by content_group name. Fetched live from the CMS, which derives it from the actual Tailor blueprints, so it is always current. Use this to recall field enums, defaults and conditional visibility.",
|
|
186
457
|
inputSchema: {
|
|
187
458
|
type: "object",
|
|
188
459
|
properties: {
|
|
189
460
|
name: { type: "string", enum: catalogue_js_1.BLOCKS.map((b) => b.content_group) },
|
|
461
|
+
site_url: { type: "string" },
|
|
462
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
190
463
|
},
|
|
191
464
|
required: ["name"],
|
|
192
465
|
},
|
|
193
466
|
},
|
|
467
|
+
{
|
|
468
|
+
name: "get_page_meta",
|
|
469
|
+
description: "Read a per-site single: Meta (SEO defaults, blog base path), Navigation (navbar) or Footer. Use before composing pages so titles and descriptions match the site's conventions.",
|
|
470
|
+
inputSchema: {
|
|
471
|
+
type: "object",
|
|
472
|
+
properties: {
|
|
473
|
+
handle: { type: "string", enum: ["Meta", "Navigation", "Footer"] },
|
|
474
|
+
site_url: { type: "string" },
|
|
475
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
476
|
+
},
|
|
477
|
+
required: ["handle"],
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "update_page_meta",
|
|
482
|
+
description: "Update a per-site single (Meta / Navigation / Footer). Send only the fields you want to change, e.g. {handle:'Meta', fields:{metaTitle:'...', description:'...'}}. Media fields must be left empty. Affects every page on that site, so confirm with the user first.",
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: "object",
|
|
485
|
+
properties: {
|
|
486
|
+
handle: { type: "string", enum: ["Meta", "Navigation", "Footer"] },
|
|
487
|
+
fields: { type: "object", description: "Field name => value map." },
|
|
488
|
+
site_url: { type: "string" },
|
|
489
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
490
|
+
},
|
|
491
|
+
required: ["handle", "fields"],
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: "create_translation",
|
|
496
|
+
description: "Create the sibling of an existing page — or Prefill entry, with prefill:true — on another multisite site, linked to the original so the language switcher connects them. Pass target_site_id, and optionally `page` (title/slug overrides) and `builder` (the translated blocks). Omitting builder copies nothing — the translation starts empty, ready for you to fill. Form and Menu entries have no translation linking; create them per site with site_id instead.",
|
|
497
|
+
inputSchema: {
|
|
498
|
+
type: "object",
|
|
499
|
+
properties: {
|
|
500
|
+
id: { type: "integer", description: "Source page (or prefill) id." },
|
|
501
|
+
target_site_id: { type: "integer", description: "Site to create the translation on." },
|
|
502
|
+
source_site_id: { type: "integer", description: "Site the source record lives on. Defaults to the pinned CMS site." },
|
|
503
|
+
prefill: { type: "boolean", description: "Set true when id is a Prefill entry, not a page." },
|
|
504
|
+
page: { type: "object" },
|
|
505
|
+
builder: { type: "array" },
|
|
506
|
+
site_url: { type: "string" },
|
|
507
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
508
|
+
},
|
|
509
|
+
required: ["id", "target_site_id"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
194
512
|
];
|
|
195
|
-
const server = new index_js_1.Server({ name: "frameworc-mcp", version: "0.
|
|
513
|
+
const server = new index_js_1.Server({ name: "frameworc-mcp", version: "0.4.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
196
514
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
197
515
|
tools,
|
|
198
516
|
}));
|
|
@@ -200,6 +518,10 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
|
200
518
|
const name = req.params.name;
|
|
201
519
|
const args = (req.params.arguments ?? {});
|
|
202
520
|
(0, sites_js_1.refreshIfChanged)();
|
|
521
|
+
// A one-off site_id argument overrides the pinned CMS site for this call.
|
|
522
|
+
const site = args.site_id !== undefined && args.site_id !== null
|
|
523
|
+
? Number(args.site_id)
|
|
524
|
+
: undefined;
|
|
203
525
|
if (name !== "use_site" && name !== "list_sites" && !args.site_url && !session.getUrl()) {
|
|
204
526
|
return {
|
|
205
527
|
content: [
|
|
@@ -254,68 +576,176 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
|
254
576
|
content: [{ type: "text", text: JSON.stringify({ sites }, null, 2) }],
|
|
255
577
|
};
|
|
256
578
|
}
|
|
579
|
+
case "list_cms_sites": {
|
|
580
|
+
const r = await client.listCmsSites();
|
|
581
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
582
|
+
}
|
|
583
|
+
case "use_cms_site": {
|
|
584
|
+
const id = args.site_id === null || args.site_id === undefined ? null : Number(args.site_id);
|
|
585
|
+
session.setSiteId(id);
|
|
586
|
+
return {
|
|
587
|
+
content: [{
|
|
588
|
+
type: "text",
|
|
589
|
+
text: id === null
|
|
590
|
+
? "Using the primary CMS site."
|
|
591
|
+
: `Using CMS site id ${id} for subsequent calls.`,
|
|
592
|
+
}],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
257
595
|
case "list_pages": {
|
|
258
|
-
const r = await client.listPages();
|
|
596
|
+
const r = await client.listPages(site);
|
|
259
597
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
260
598
|
}
|
|
261
599
|
case "get_page": {
|
|
262
|
-
const r = await client.getPage(Number(args.id));
|
|
600
|
+
const r = await client.getPage(Number(args.id), site);
|
|
263
601
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
264
602
|
}
|
|
265
603
|
case "create_page": {
|
|
266
|
-
const r = await client.createPage(args.payload);
|
|
604
|
+
const r = await client.createPage(args.payload, site);
|
|
267
605
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
268
606
|
}
|
|
269
607
|
case "update_page": {
|
|
270
|
-
const r = await client.updatePage(Number(args.id), args.payload);
|
|
608
|
+
const r = await client.updatePage(Number(args.id), args.payload, site);
|
|
271
609
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
272
610
|
}
|
|
273
611
|
case "delete_page": {
|
|
274
|
-
const r = await client.deletePage(Number(args.id));
|
|
612
|
+
const r = await client.deletePage(Number(args.id), site);
|
|
613
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
614
|
+
}
|
|
615
|
+
case "create_translation": {
|
|
616
|
+
const payload = { page: args.page ?? {}, builder: args.builder };
|
|
617
|
+
const source = args.source_site_id !== undefined ? Number(args.source_site_id) : undefined;
|
|
618
|
+
const r = args.prefill
|
|
619
|
+
? await client.createPrefillTranslation(Number(args.id), Number(args.target_site_id), { prefill: args.page ?? {}, builder: args.builder }, source)
|
|
620
|
+
: await client.createTranslation(Number(args.id), Number(args.target_site_id), payload, source);
|
|
275
621
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
276
622
|
}
|
|
277
623
|
case "add_block": {
|
|
278
|
-
const r = await client.addBlock(
|
|
624
|
+
const r = await client.addBlock(blockHost(args), args.block, args.position !== undefined ? Number(args.position) : undefined, site);
|
|
279
625
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
280
626
|
}
|
|
281
627
|
case "update_block": {
|
|
282
|
-
const r = await client.updateBlock(
|
|
628
|
+
const r = await client.updateBlock(blockHost(args), Number(args.block_id), args.block, site);
|
|
283
629
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
284
630
|
}
|
|
285
631
|
case "remove_block": {
|
|
286
|
-
const r = await client.removeBlock(
|
|
632
|
+
const r = await client.removeBlock(blockHost(args), Number(args.block_id), site);
|
|
287
633
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
288
634
|
}
|
|
289
635
|
case "reorder_blocks": {
|
|
290
|
-
const r = await client.reorderBlocks(
|
|
636
|
+
const r = await client.reorderBlocks(blockHost(args), args.order, site);
|
|
637
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
638
|
+
}
|
|
639
|
+
case "extract_block_to_prefill": {
|
|
640
|
+
const r = await client.extractBlockToPrefill(Number(args.page_id), Number(args.block_id), args.title ?? "", site);
|
|
291
641
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
292
642
|
}
|
|
293
643
|
case "list_forms": {
|
|
294
|
-
const r = await client.listForms();
|
|
644
|
+
const r = await client.listForms(site);
|
|
645
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
646
|
+
}
|
|
647
|
+
case "get_form_schema": {
|
|
648
|
+
const r = await client.getFormSchema();
|
|
649
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
650
|
+
}
|
|
651
|
+
case "get_form": {
|
|
652
|
+
const r = await client.getForm(Number(args.id), site);
|
|
653
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
654
|
+
}
|
|
655
|
+
case "create_form": {
|
|
656
|
+
const r = await client.createForm(args.payload, site);
|
|
657
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
658
|
+
}
|
|
659
|
+
case "update_form": {
|
|
660
|
+
const r = await client.updateForm(Number(args.id), args.payload, site);
|
|
661
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
662
|
+
}
|
|
663
|
+
case "delete_form": {
|
|
664
|
+
const r = await client.deleteForm(Number(args.id), Boolean(args.force), site);
|
|
295
665
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
296
666
|
}
|
|
297
667
|
case "list_menus": {
|
|
298
|
-
const r = await client.listMenus();
|
|
668
|
+
const r = await client.listMenus(site);
|
|
669
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
670
|
+
}
|
|
671
|
+
case "get_menu": {
|
|
672
|
+
const r = await client.getMenu(Number(args.id), site);
|
|
673
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
674
|
+
}
|
|
675
|
+
case "create_menu": {
|
|
676
|
+
const r = await client.createMenu(args.payload, site);
|
|
677
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
678
|
+
}
|
|
679
|
+
case "update_menu": {
|
|
680
|
+
const r = await client.updateMenu(Number(args.id), args.payload, site);
|
|
681
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
682
|
+
}
|
|
683
|
+
case "delete_menu": {
|
|
684
|
+
const r = await client.deleteMenu(Number(args.id), Boolean(args.force), site);
|
|
299
685
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
300
686
|
}
|
|
301
687
|
case "list_prefills": {
|
|
302
|
-
const r = await client.listPrefills();
|
|
688
|
+
const r = await client.listPrefills(site);
|
|
689
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
690
|
+
}
|
|
691
|
+
case "get_prefill": {
|
|
692
|
+
const r = await client.getPrefill(Number(args.id), site);
|
|
693
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
694
|
+
}
|
|
695
|
+
case "create_prefill": {
|
|
696
|
+
const r = await client.createPrefill(args.payload, site);
|
|
697
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
698
|
+
}
|
|
699
|
+
case "update_prefill": {
|
|
700
|
+
const r = await client.updatePrefill(Number(args.id), args.payload, site);
|
|
701
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
702
|
+
}
|
|
703
|
+
case "delete_prefill": {
|
|
704
|
+
const r = await client.deletePrefill(Number(args.id), Boolean(args.force), site);
|
|
705
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
706
|
+
}
|
|
707
|
+
case "get_settings": {
|
|
708
|
+
const r = await client.getSettings();
|
|
709
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
710
|
+
}
|
|
711
|
+
case "update_settings": {
|
|
712
|
+
const r = await client.updateSettings(args.fields ?? {});
|
|
713
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
714
|
+
}
|
|
715
|
+
case "get_page_meta": {
|
|
716
|
+
const r = await client.getSingle(String(args.handle), site);
|
|
717
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
718
|
+
}
|
|
719
|
+
case "update_page_meta": {
|
|
720
|
+
const r = await client.updateSingle(String(args.handle), args.fields ?? {}, site);
|
|
303
721
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
304
722
|
}
|
|
305
723
|
case "get_block_schema": {
|
|
306
|
-
|
|
307
|
-
|
|
724
|
+
// Served by the CMS so it always matches the live blueprints; the
|
|
725
|
+
// bundled catalogue is only a fallback for an older plugin version.
|
|
726
|
+
try {
|
|
727
|
+
const r = await client.getBlockSchema(String(args.name));
|
|
728
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
const block = catalogue_js_1.BLOCKS.find((b) => b.content_group === args.name);
|
|
732
|
+
if (!block) {
|
|
733
|
+
return {
|
|
734
|
+
content: [{
|
|
735
|
+
type: "text",
|
|
736
|
+
text: `Unknown block "${args.name}". Available: ${catalogue_js_1.BLOCKS.map((b) => b.content_group).join(", ")}`,
|
|
737
|
+
}],
|
|
738
|
+
isError: true,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
308
741
|
return {
|
|
309
|
-
content: [
|
|
310
|
-
{
|
|
742
|
+
content: [{
|
|
311
743
|
type: "text",
|
|
312
|
-
text: `
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
isError: true,
|
|
744
|
+
text: `(served from the bundled catalogue; live schema unavailable: ${err?.message ?? err})\n`
|
|
745
|
+
+ JSON.stringify(block, null, 2),
|
|
746
|
+
}],
|
|
316
747
|
};
|
|
317
748
|
}
|
|
318
|
-
return { content: [{ type: "text", text: JSON.stringify(block, null, 2) }] };
|
|
319
749
|
}
|
|
320
750
|
default: {
|
|
321
751
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|