frameworc-mcp 0.5.1 → 0.6.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/README.md +4 -3
- package/dist/api-client.js +7 -0
- package/dist/catalogue.js +5 -2
- package/dist/index.js +34 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Cla
|
|
|
9
9
|
- Creates pages, adds / updates / removes / reorders individual blocks — on pages and on Prefill entries
|
|
10
10
|
- Full CRUD for `Form` entries (incl. their field rows), `Menu` entries (incl. the navigation tree) and `Prefill` entries (incl. their builder blocks)
|
|
11
11
|
- Extracts a repeated page section into a Prefill entry and replaces it with a reference (`extract_block_to_prefill`) — the intended FrameworC de-duplication workflow
|
|
12
|
-
- Reads and writes the per-site singles (Meta & SEO, Navigation, Footer) and the
|
|
12
|
+
- Reads and writes the per-site singles (Meta & SEO, Navigation, Footer) and the FrameworC settings (global navbar options and SCSS, per-site SCSS; integration secrets are not accessible by design)
|
|
13
13
|
- Multisite-aware: every content tool takes/pins a `site_id`; page and prefill translations are linked for the language switcher
|
|
14
14
|
- Publishes the block catalogue as an MCP resource (`frameworc://blocks`) so the chat agent knows each block's fields, defaults, and when-to-use notes
|
|
15
15
|
- Draft by default — created pages have `is_enabled = false`; the human flips the switch in the OctCMS backend after assigning images
|
|
@@ -96,7 +96,7 @@ If the file is missing or empty, `use_site` errors with a pointer to where to ad
|
|
|
96
96
|
|
|
97
97
|
### Adding a new OctCMS site
|
|
98
98
|
|
|
99
|
-
1. Spin up the new OctCMS install with the FrameworC suite incl. the `crscompany/frameworcmcp` plugin (v1.
|
|
99
|
+
1. Spin up the new OctCMS install with the FrameworC suite incl. the `crscompany/frameworcmcp` plugin (v1.3.0 or newer; media assignment needs v1.2.0, per-site settings need v1.3.0 with FrameworC v1.10.0).
|
|
100
100
|
2. Backend → Settings → FrameworC → **MCP API** → paste a freshly generated random string (e.g. `openssl rand -hex 32`) → Save.
|
|
101
101
|
3. Edit `~/.config/frameworc/sites.json` on your laptop, add one entry: `{ "label": "New Client", "url": "https://newsite.test", "token": "<that string>" }`.
|
|
102
102
|
4. In your next chat (no Claude Desktop restart needed): *"Use `https://newsite.test`."* → MCP hot-reloads the file, finds the token, sends it. Done.
|
|
@@ -136,7 +136,8 @@ No env var, no chat-client config edit, no ToolHive touch, no repo push. The who
|
|
|
136
136
|
| `list_prefills` / `get_prefill(id)` | Prefill entries; `get_prefill` includes the builder blocks. |
|
|
137
137
|
| `create_prefill` / `update_prefill` / `delete_prefill` | Prefill CRUD — same block shape as pages; delete guarded. |
|
|
138
138
|
| `get_page_meta(handle)` / `update_page_meta(handle, fields)` | Per-site singles: `Meta` (SEO), `Navigation` (navbar incl. `nav` menu link + buttons), `Footer` (incl. `socials` rows + `nav`). |
|
|
139
|
-
| `get_settings` / `update_settings(fields)` | Global FrameworC settings: navbar options + custom SCSS
|
|
139
|
+
| `get_settings` / `update_settings(fields)` | Global FrameworC settings: navbar options + global custom SCSS (`styles_globalScss`). Integration secrets are not exposed. |
|
|
140
|
+
| `get_site_settings` / `update_site_settings(fields)` | Per-site FrameworC settings: custom SCSS for one site (`siteScss`), compiled after the global SCSS. |
|
|
140
141
|
| `get_block_schema(name)` | Field schema + usage notes for one block (live from the CMS). |
|
|
141
142
|
| `list_media(folder?, type?, sort?, limit?, offset?)` | Browse the media library. Returns files with their `path` — write that into a mediafinder field. |
|
|
142
143
|
| `search_media(q, folder?, type?, ...)` | Find library files by name across all folders. Every whitespace-separated word must appear in the path. |
|
package/dist/api-client.js
CHANGED
|
@@ -199,6 +199,13 @@ class ApiClient {
|
|
|
199
199
|
updateSettings(fields) {
|
|
200
200
|
return this.request("/settings", { method: "PATCH", body: JSON.stringify({ fields }) });
|
|
201
201
|
}
|
|
202
|
+
// --- FrameworC settings (per site) -------------------------------------
|
|
203
|
+
getSiteSettings(siteId) {
|
|
204
|
+
return this.request(this.q("/site-settings", siteId));
|
|
205
|
+
}
|
|
206
|
+
updateSiteSettings(fields, siteId) {
|
|
207
|
+
return this.request("/site-settings", { method: "PATCH", body: this.b({ fields }, siteId) });
|
|
208
|
+
}
|
|
202
209
|
// --- singles ----------------------------------------------------------
|
|
203
210
|
getSingle(handle, siteId) {
|
|
204
211
|
return this.request(this.q(`/singles/${encodeURIComponent(handle)}`, siteId));
|
package/dist/catalogue.js
CHANGED
|
@@ -42,7 +42,7 @@ const baseBlock = [
|
|
|
42
42
|
{ name: "responsiveHide", type: "checkboxlist", enum: ["desktop", "tablet", "mobile"], comment: "Which devices to hide this block on." },
|
|
43
43
|
];
|
|
44
44
|
const sectionVariants = [
|
|
45
|
-
"halfAndHalf", "noText", "noImage", "textAndText", "img70", "text70",
|
|
45
|
+
"halfAndHalf", "imgBleed", "noText", "noImage", "textAndText", "img70", "text70",
|
|
46
46
|
"embedHalfAndHalf", "embed70", "embed30", "embedOnly",
|
|
47
47
|
];
|
|
48
48
|
exports.BLOCKS = [
|
|
@@ -56,6 +56,8 @@ exports.BLOCKS = [
|
|
|
56
56
|
{ name: "image", type: "media-single", comment: "Media library path. Declared mode:image, but an .mp4 is valid here when isVideoBg is on." },
|
|
57
57
|
{ name: "imageMobile", type: "media-single" },
|
|
58
58
|
{ name: "isVideoBg", type: "switch", default: false, comment: "If true, 'image' is treated as a video file and 'imageMobile' as poster." },
|
|
59
|
+
{ name: "fullVideo", type: "media-single", comment: "Only used when isVideoBg is on. Media library path to the full-length video; a play button opens it in a lightbox popup over the page." },
|
|
60
|
+
{ name: "fullVideoLabel", type: "text", comment: "Only used when isVideoBg is on. Label of the button that opens fullVideo." },
|
|
59
61
|
...buttonsMixin,
|
|
60
62
|
{ name: "fullHeight", type: "switch", default: true },
|
|
61
63
|
{ name: "contrast", type: "switch", default: false, comment: "Use contrast (light-on-dark) text for the headline when the background image is dark." },
|
|
@@ -66,11 +68,12 @@ exports.BLOCKS = [
|
|
|
66
68
|
content_group: "Section",
|
|
67
69
|
name: "Section",
|
|
68
70
|
description: "Text + image / embed in a configurable column layout. The most common content block.",
|
|
69
|
-
whenToUse: "Default choice for any paragraph + image content. Pick a variant that matches the proportion of text to visual. Use 'textAndText' for two text columns (e.g. a feature comparison). Use an 'embed*' variant when you have a YouTube video, map iframe, etc.",
|
|
71
|
+
whenToUse: "Default choice for any paragraph + image content. Pick a variant that matches the proportion of text to visual. Use 'imgBleed' for an image running to the edge of the viewport next to half-width text. Use 'textAndText' for two text columns (e.g. a feature comparison). Use an 'embed*' variant when you have a YouTube video, map iframe, etc.",
|
|
70
72
|
base: baseBlock,
|
|
71
73
|
content: [
|
|
72
74
|
{ name: "variant", type: "dropdown", enum: sectionVariants, default: "halfAndHalf" },
|
|
73
75
|
{ name: "reverse", type: "switch", default: false, hiddenWhen: { field: "variant", equals: ["noText", "noImage", "embedOnly"] }, comment: "Swap sides (image left vs right)." },
|
|
76
|
+
{ name: "reverseMobile", type: "switch", default: false, hiddenWhen: { field: "variant", equals: ["noText", "noImage", "embedOnly"] }, comment: "On mobile the columns stack; this flips their stacking order." },
|
|
74
77
|
{ name: "image", type: "media-multi", comment: "Stored as an ARRAY of paths (the blueprint sets no maxItems) but the theme renders only the FIRST item, so send [\"/photo.jpg\"].", hiddenWhen: { field: "variant", equals: ["noImage", "embedHalfAndHalf", "embedOnly", "embed30", "embed70", "textAndText"] } },
|
|
75
78
|
{ name: "embed", type: "codeeditor", shownWhen: { field: "variant", equals: ["embedHalfAndHalf", "embedOnly", "embed30", "embed70"] }, comment: "Raw embed HTML (iframe etc.)." },
|
|
76
79
|
...buttonsMixin,
|
package/dist/index.js
CHANGED
|
@@ -433,7 +433,7 @@ const tools = [
|
|
|
433
433
|
},
|
|
434
434
|
{
|
|
435
435
|
name: "get_settings",
|
|
436
|
-
description: "Read the FrameworC plugin settings exposed over the API: navbar layout options and the custom SCSS
|
|
436
|
+
description: "Read the FrameworC plugin settings exposed over the API: navbar layout options and the global custom SCSS. Settings are GLOBAL (not per multisite site); per-site SCSS lives in get_site_settings. Integration/secret settings are not accessible by design.",
|
|
437
437
|
inputSchema: {
|
|
438
438
|
type: "object",
|
|
439
439
|
properties: { site_url: { type: "string" } },
|
|
@@ -441,7 +441,7 @@ const tools = [
|
|
|
441
441
|
},
|
|
442
442
|
{
|
|
443
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),
|
|
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), styles_globalScss (SCSS compiled into every site before that site's own SCSS, so its $variables and mixins are usable there; SCSS that does not compile is rejected). 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
445
|
inputSchema: {
|
|
446
446
|
type: "object",
|
|
447
447
|
properties: {
|
|
@@ -451,6 +451,30 @@ const tools = [
|
|
|
451
451
|
required: ["fields"],
|
|
452
452
|
},
|
|
453
453
|
},
|
|
454
|
+
{
|
|
455
|
+
name: "get_site_settings",
|
|
456
|
+
description: "Read the per-site FrameworC settings: siteScss, the custom SCSS for one multisite site. It is compiled after the global styles_globalScss from get_settings, so it can use that SCSS's $variables and mixins.",
|
|
457
|
+
inputSchema: {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
site_url: { type: "string" },
|
|
461
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "update_site_settings",
|
|
467
|
+
description: "Update the per-site FrameworC settings. `fields` is a partial map; writable key: siteScss (SCSS for this site only, compiled after the global SCSS; SCSS that does not compile is rejected). Affects every page on that site, so confirm with the user first.",
|
|
468
|
+
inputSchema: {
|
|
469
|
+
type: "object",
|
|
470
|
+
properties: {
|
|
471
|
+
fields: { type: "object", description: "Field name => value map (partial)." },
|
|
472
|
+
site_url: { type: "string" },
|
|
473
|
+
site_id: { type: "integer", description: "Target multisite site id; defaults to the pinned CMS site." },
|
|
474
|
+
},
|
|
475
|
+
required: ["fields"],
|
|
476
|
+
},
|
|
477
|
+
},
|
|
454
478
|
{
|
|
455
479
|
name: "list_media",
|
|
456
480
|
description: "Browse the media library one folder at a time. Returns {folder, parent, folders:[{path,name,item_count}], files:[{path,name,file_type,extension,size,last_modified,url}], total_files, truncated}. To put a file on a page, write its `path` — the root-relative form with a leading slash, e.g. \"/logos/brand.svg\" — into a mediafinder field; never the `url`. This server CANNOT upload: if the file the user wants is not here, say so and ask them to add it in Backend > Media. Prefer search_media when you know part of the name. Note that `file_type` buckets follow the install's media config (SVG may report as image or as document), so treat `type` as a convenience filter rather than a guarantee. The library is global: site_id is accepted and ignored.",
|
|
@@ -770,6 +794,14 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
|
770
794
|
const r = await client.updateSettings(args.fields ?? {});
|
|
771
795
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
772
796
|
}
|
|
797
|
+
case "get_site_settings": {
|
|
798
|
+
const r = await client.getSiteSettings(site);
|
|
799
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
800
|
+
}
|
|
801
|
+
case "update_site_settings": {
|
|
802
|
+
const r = await client.updateSiteSettings(args.fields ?? {}, site);
|
|
803
|
+
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
|
804
|
+
}
|
|
773
805
|
case "get_page_meta": {
|
|
774
806
|
const r = await client.getSingle(String(args.handle), site);
|
|
775
807
|
return { content: [{ type: "text", text: JSON.stringify(r, null, 2) }] };
|
package/package.json
CHANGED