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 CHANGED
@@ -1,216 +1,231 @@
1
- # @yourorg/frameworc-mcp
2
-
3
- A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude / Cursor / opencode / other AI chat clients create and edit pages built with the **FrameworC** OctoberCMS plugin. One MCP process serves any number of OctCMS installs — you tell the chat which site to work on by calling `use_site`.
4
-
5
- ## What it does
6
-
7
- - Composes 15 prebuilt FrameworC blocks (`Header`, `Section`, `Tiles`, `Slider`, `Tabs`, `Accordion`, `Form`, `Gallery`, `Downloads`, `Columns`, `Prefill`, `BlogList`, `MenuBlock`, `ImageStrip`, `InstaFeed`) into pages
8
- - Lists existing pages and their full builder JSON
9
- - Creates pages, adds / updates / removes / reorders individual blocks
10
- - Resolves referenced `Form` / `Menu` / `Prefill` entries via dedicated list tools
11
- - 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
12
- - Draft by default — created pages have `is_enabled = false`; the human flips the switch in the OctCMS backend after assigning images
13
-
14
- The chat-side flow (text → blocks) is **LLM-native**: you paste a markdown page (or upload `.docx` / `.pdf` and instruct Claude to convert it to markdown), Claude reads the `frameworc://blocks` resource once, segments the content into blocks, calls `list_forms` / `list_menus` / `list_prefills` as needed, then calls `create_page` with the assembled JSON. The MCP simply forwards authenticated HTTPS calls to the OctCMS install.
15
-
16
- ## Architecture
17
-
18
- ```
19
- chat client (Claude Desktop / opencode / Cursor / Cline / Continue / ...)
20
- │ stdio JSON-RPC
21
- ▼
22
- node dist/index.js (local, spawned by the chat client)
23
- reads ~/.config/frameworc/sites.json (auto-created on first run; hot-reloaded)
24
- │ HTTPS + Authorization: Bearer <token resolved by URL>
25
- ▼
26
- any OctCMS install /api/mcp/v1/* (provided by the FrameworC plugin v1.10.0+)
27
- ```
28
-
29
- No Docker, no ToolHive, no remote gateway, no second auth layer. Each OctCMS install has its own bearer token stored in its own `FrameworcSetting → Integration → MCP API token`, and your laptop keeps the URL → token map in one local file.
30
-
31
- ## Install
32
-
33
- ```bash
34
- git clone <your-gitlab-url>/frameworc-mcp.git
35
- cd frameworc-mcp
36
- npm install
37
- npm run build
38
- ```
39
-
40
- (Optional) publish to your GitLab group's npm registry so colleagues can `npx` it without a clone — see [Publishing](#publishing) below.
41
-
42
- ## Claude Desktop config
43
-
44
- Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Linux: `~/.config/Claude/`):
45
-
46
- ```json
47
- {
48
- "mcpServers": {
49
- "frameworc": {
50
- "command": "npx",
51
- "args": ["-y", "@yourorg/frameworc-mcp"]
52
- }
53
- }
54
- }
55
- ```
56
-
57
- If you cloned instead of `npx`-installed, point `args` to your local `dist/index.js`:
58
-
59
- ```json
60
- {
61
- "mcpServers": {
62
- "frameworc": {
63
- "command": "node",
64
- "args": ["/absolute/path/to/frameworc-mcp/dist/index.js"]
65
- }
66
- }
67
- }
68
- ```
69
-
70
- No env vars. Restart Claude → the `frameworc` tools appear. On first start the MCP creates `~/.config/frameworc/sites.json` with a template; edit it with your real sites (see below).
71
-
72
- ## Per-site token file — `~/.config/frameworc/sites.json`
73
-
74
- The MCP stores one bearer token per OctCMS install in this file on your laptop. It's the only credential store. Format:
75
-
76
- ```json
77
- {
78
- "sites": [
79
- { "label": "Client A prod", "url": "https://clienta.test", "token": "AAA..." },
80
- { "label": "Client A staging", "url": "https://staging.clienta.test", "token": "BBB..." },
81
- { "label": "Myco", "url": "https://myco.example", "token": "CCC..." }
82
- ]
83
- }
84
- ```
85
-
86
- - `label` is optional, shown by the `list_sites` tool so the chat can pick a site by name when you say "use the myco site".
87
- - `url` is matched after normalising trailing slashes — `https://x.test/` and `https://x.test` are the same.
88
- - `token` is sent as `Authorization: Bearer <token>` on every HTTP call to that site. Never committed to git.
89
-
90
- The file is created automatically on first MCP startup (with a template and `0600` perms). Edits are picked up via stat-on-every-tool-call hot-reload — no MCP or chat-client restart needed.
91
-
92
- If the file is missing or empty, `use_site` errors with a pointer to where to add it. The `list_sites` tool enumerates configured sites without exposing tokens.
93
-
94
- ### Adding a new OctCMS site
95
-
96
- 1. Spin up the new OctCMS install with the FrameworC plugin (v1.10.0 or newer).
97
- 2. Backend → Settings → FrameworC → Integration → **MCP API token** → paste a freshly generated random string (e.g. `openssl rand -hex 32`) → Save.
98
- 3. Edit `~/.config/frameworc/sites.json` on your laptop, add one entry: `{ "label": "New Client", "url": "https://newsite.test", "token": "<that string>" }`.
99
- 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.
100
-
101
- No env var, no chat-client config edit, no ToolHive touch, no repo push. The whole token map lives in one file on your machine.
102
-
103
- ### Security
104
-
105
- - The file should be `0600` (auto-set on first creation). If you `chmod` it looser, the MCP prints a stderr warning on startup.
106
- - A leak of the file compromises every site listed in it. Treat it like an SSH private key — back it up, rotate tokens periodically, never commit it to git.
107
- - One-off override: `use_site("https://X", "token-string")` lets you pass a token inline (without storing it) for the duration of the chat session. Useful for testing a token before saving it.
108
-
109
- ## Tools (v1)
110
-
111
- | Tool | Description |
112
- |---|---|
113
- | `use_site(url, site_token?)` | Pin the target OctCMS site for the session. Token resolved from `~/.config/frameworc/sites.json` by URL, or supplied via the optional `site_token` arg. |
114
- | `list_sites` | List configured sites (label + URL only, no tokens) from `sites.json`. |
115
- | `list_pages` | List all pages on the pinned site. |
116
- | `get_page(id)` | Full page JSON (page meta + builder blocks + nested repeaters). |
117
- | `create_page(payload)` | Create a draft page. `payload = { page: {...}, builder: [...] }`. |
118
- | `update_page(id, payload)` | Edit page meta, or full-rebuild the builder array. |
119
- | `delete_page(id)` | Soft-delete a page. |
120
- | `add_block(page_id, block, position?)` | Append (or insert at `position`) a block. |
121
- | `update_block(page_id, block_id, block)` | Replace one block by row id. |
122
- | `remove_block(page_id, block_id)` | Remove one block by row id. |
123
- | `reorder_blocks(page_id, order)` | Reorder blocks (order = array of all row ids in new order). |
124
- | `list_forms` | Form Tailor entries for the `Form` block's `form_entry`. |
125
- | `list_menus` | Menu Tailor entries for the `MenuBlock`'s `content.menu_entry`. |
126
- | `list_prefills` | Prefill Tailor entries for the `Prefill` block's `content.prefill_entry`. |
127
- | `get_block_schema(name)` | Field schema + usage notes for one block. |
128
-
129
- ## Resource
130
-
131
- - `frameworc://blocks` — reference for all 15 block types (fields, defaults, enums, conditional visibility, nested repeaters, reference fields). The chat reads this once per session; you don't invoke it manually.
132
-
133
- ## Page-content JSON shape
134
-
135
- `create_page` / `update_page` `payload`:
136
-
137
- ```json
138
- {
139
- "page": {
140
- "title": "Contact",
141
- "slug": "contact",
142
- "fullslug": "contact",
143
- "is_enabled": false,
144
- "metaTitle": "",
145
- "metaDescription": "",
146
- "menuStyle": "solid",
147
- "menuHide": "no"
148
- },
149
- "builder": [
150
- {
151
- "content_group": "Header",
152
- "base": {
153
- "blockId": "kontakt",
154
- "headline": "<h1>Contact us</h1>",
155
- "elevated": false,
156
- "containerWidth": "default",
157
- "backgroundColor": "default",
158
- "customCssClass": [],
159
- "responsiveHide": []
160
- },
161
- "content": {
162
- "image": "",
163
- "imageMobile": "",
164
- "isVideoBg": false,
165
- "buttonLabel1": "",
166
- "buttonLink1": "",
167
- "buttonBlank1": false,
168
- "fullHeight": true,
169
- "contrast": false,
170
- "overlay": false
171
- }
172
- },
173
- {
174
- "content_group": "Form",
175
- "base": { "blockId": "formular", "headline": "<h2>Write us</h2>" },
176
- "form_entry": 7,
177
- "content": { "variant": "default" }
178
- }
179
- ]
180
- }
181
- ```
182
-
183
- ### Storage encoding (what the MCP understands)
184
-
185
- - Media fields (`image`, `imageMobile`, `backgroundImage`, `buttonIcon1..4`, `ogImage`, `images`, `file`) **must be empty** in the JSON — they are rejected if non-empty. Fill them in the OctCMS backend.
186
- - `switch` fields accept booleans; the API normalises to `"1"` / `"0"` for storage.
187
- - `customCssClass` + `responsiveHide` are arrays of strings.
188
- - Multi-`mediafinder` fields (`images` for `Gallery` / `ImageStrip`) are arrays of path strings — but must be empty `[]` per the rule above.
189
- - `entries` links are integers: `form_entry` (block level for `Form`), `content.menu_entry` (for `MenuBlock`), `content.prefill_entry` (for `Prefill`).
190
- - `Slider` accepts an optional `content.breakpoints = { tablet: number, mobile: number }`.
191
- - `Columns` blocks have `content.columns = [{ blockId, builder: [...blocks] }]` — recursive: each column's `builder` follows the exact same shape as the top-level `builder`.
192
-
193
- ## Catalogue drift
194
-
195
- The hand-authored block catalogue lives in `src/catalogue.ts`. It mirrors `plugins/crscompany/frameworc/blueprints/Blocks/*.yaml` + `Mixins/Buttons.yaml` + `Mixins/Buttons2.yaml` + `BaseBlock.yaml`. If a block blueprint changes (new field, new enum value, removed field), update `src/catalogue.ts` to match and rebuild. No generator script — kept manual because blocks change rarely per the FrameworC convention.
196
-
197
- ## Publishing
198
-
199
- To publish to your GitLab group's npm registry:
200
-
201
- 1. Edit `package.json`:
202
- - Replace `@yourorg` in `name` and `publishConfig["@yourorg:registry"]` with your actual GitLab scope/group.
203
- - Replace `<YOUR-PROJECT-ID>` in `publishConfig["@yourorg:registry"]` with the numeric project id of the `frameworc-mcp` GitLab repo.
204
- 2. Create a GitLab deploy token / project access token with `api` + `write_registry` scope. Add a `.npmrc` at the registry host:
205
-
206
- ```
207
- @yourorg:registry=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/
208
- //gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/:_authToken=<TOKEN>
209
- ```
210
- 3. `npm publish`.
211
-
212
- Colleagues then use `npx -y @yourorg/frameworc-mcp` in their `claude_desktop_config.json` (no clone needed).
213
-
214
- ## License
215
-
1
+ # @yourorg/frameworc-mcp
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude / Cursor / opencode / other AI chat clients create and edit pages built with the **FrameworC** OctoberCMS plugin. One MCP process serves any number of OctCMS installs — you tell the chat which site to work on by calling `use_site`.
4
+
5
+ ## What it does
6
+
7
+ - Composes 15 prebuilt FrameworC blocks (`Header`, `Section`, `Tiles`, `Slider`, `Tabs`, `Accordion`, `Form`, `Gallery`, `Downloads`, `Columns`, `Prefill`, `BlogList`, `MenuBlock`, `ImageStrip`, `InstaFeed`) into pages
8
+ - Lists existing pages and their full builder JSON
9
+ - Creates pages, adds / updates / removes / reorders individual blocks — on pages and on Prefill entries
10
+ - Full CRUD for `Form` entries (incl. their field rows), `Menu` entries (incl. the navigation tree) and `Prefill` entries (incl. their builder blocks)
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 global FrameworC settings (navbar options, SCSS variables; integration secrets are not accessible by design)
13
+ - Multisite-aware: every content tool takes/pins a `site_id`; page and prefill translations are linked for the language switcher
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
+ - Draft by default — created pages have `is_enabled = false`; the human flips the switch in the OctCMS backend after assigning images
16
+
17
+ The chat-side flow (text → blocks) is **LLM-native**: you paste a markdown page (or upload `.docx` / `.pdf` and instruct Claude to convert it to markdown), Claude reads the `frameworc://blocks` resource once, segments the content into blocks, calls `list_forms` / `list_menus` / `list_prefills` as needed, then calls `create_page` with the assembled JSON. The MCP simply forwards authenticated HTTPS calls to the OctCMS install.
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ chat client (Claude Desktop / opencode / Cursor / Cline / Continue / ...)
23
+ │ stdio JSON-RPC
24
+ ▼
25
+ node dist/index.js (local, spawned by the chat client)
26
+ reads ~/.config/frameworc/sites.json (auto-created on first run; hot-reloaded)
27
+ │ HTTPS + Authorization: Bearer <token resolved by URL>
28
+ ▼
29
+ any OctCMS install /api/mcp/v1/* (provided by the crscompany/frameworcmcp plugin v1.1.0+)
30
+ ```
31
+
32
+ No Docker, no ToolHive, no remote gateway, no second auth layer. Each OctCMS install has its own bearer token stored in its backend under `Settings → FrameworC → MCP API`, and your laptop keeps the URL → token map in one local file.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ git clone <your-gitlab-url>/frameworc-mcp.git
38
+ cd frameworc-mcp
39
+ npm install
40
+ npm run build
41
+ ```
42
+
43
+ (Optional) publish to your GitLab group's npm registry so colleagues can `npx` it without a clone — see [Publishing](#publishing) below.
44
+
45
+ ## Claude Desktop config
46
+
47
+ Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Linux: `~/.config/Claude/`):
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "frameworc": {
53
+ "command": "npx",
54
+ "args": ["-y", "@yourorg/frameworc-mcp"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ If you cloned instead of `npx`-installed, point `args` to your local `dist/index.js`:
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "frameworc": {
66
+ "command": "node",
67
+ "args": ["/absolute/path/to/frameworc-mcp/dist/index.js"]
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ No env vars. Restart Claude → the `frameworc` tools appear. On first start the MCP creates `~/.config/frameworc/sites.json` with a template; edit it with your real sites (see below).
74
+
75
+ ## Per-site token file — `~/.config/frameworc/sites.json`
76
+
77
+ The MCP stores one bearer token per OctCMS install in this file on your laptop. It's the only credential store. Format:
78
+
79
+ ```json
80
+ {
81
+ "sites": [
82
+ { "label": "Client A prod", "url": "https://clienta.test", "token": "AAA..." },
83
+ { "label": "Client A staging", "url": "https://staging.clienta.test", "token": "BBB..." },
84
+ { "label": "Myco", "url": "https://myco.example", "token": "CCC..." }
85
+ ]
86
+ }
87
+ ```
88
+
89
+ - `label` is optional, shown by the `list_sites` tool so the chat can pick a site by name when you say "use the myco site".
90
+ - `url` is matched after normalising trailing slashes — `https://x.test/` and `https://x.test` are the same.
91
+ - `token` is sent as `Authorization: Bearer <token>` on every HTTP call to that site. Never committed to git.
92
+
93
+ The file is created automatically on first MCP startup (with a template and `0600` perms). Edits are picked up via stat-on-every-tool-call hot-reload — no MCP or chat-client restart needed.
94
+
95
+ If the file is missing or empty, `use_site` errors with a pointer to where to add it. The `list_sites` tool enumerates configured sites without exposing tokens.
96
+
97
+ ### Adding a new OctCMS site
98
+
99
+ 1. Spin up the new OctCMS install with the FrameworC suite incl. the `crscompany/frameworcmcp` plugin (v1.1.0 or newer).
100
+ 2. Backend → Settings → FrameworC → **MCP API** → paste a freshly generated random string (e.g. `openssl rand -hex 32`) → Save.
101
+ 3. Edit `~/.config/frameworc/sites.json` on your laptop, add one entry: `{ "label": "New Client", "url": "https://newsite.test", "token": "<that string>" }`.
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.
103
+
104
+ No env var, no chat-client config edit, no ToolHive touch, no repo push. The whole token map lives in one file on your machine.
105
+
106
+ ### Security
107
+
108
+ - The file should be `0600` (auto-set on first creation). If you `chmod` it looser, the MCP prints a stderr warning on startup.
109
+ - A leak of the file compromises every site listed in it. Treat it like an SSH private key — back it up, rotate tokens periodically, never commit it to git.
110
+ - One-off override: `use_site("https://X", "token-string")` lets you pass a token inline (without storing it) for the duration of the chat session. Useful for testing a token before saving it.
111
+
112
+ ## Tools (0.4.0)
113
+
114
+ | Tool | Description |
115
+ |---|---|
116
+ | `use_site(url, site_token?)` | Pin the target OctCMS site for the session. Token resolved from `~/.config/frameworc/sites.json` by URL, or supplied via the optional `site_token` arg. |
117
+ | `list_sites` | List configured sites (label + URL only, no tokens) from `sites.json`. |
118
+ | `list_cms_sites` | List the multisite sites (languages) inside the pinned install. |
119
+ | `use_cms_site(site_id)` | Pin the multisite site for subsequent calls (null = primary). |
120
+ | `list_pages` | List all pages on the pinned site. |
121
+ | `get_page(id)` | Full page JSON (page meta + builder blocks + nested repeaters). |
122
+ | `create_page(payload)` | Create a draft page. `payload = { page: {...}, builder: [...] }`. |
123
+ | `update_page(id, payload)` | Edit page meta, or full-rebuild the builder array (prefer the per-block tools). |
124
+ | `delete_page(id)` | Soft-delete a page. |
125
+ | `create_translation(id, target_site_id, prefill?)` | Linked sibling of a page (or Prefill with `prefill:true`) on another multisite site. |
126
+ | `add_block(page_id \| prefill_id, block, position?)` | Append (or insert at `position`) a block. |
127
+ | `update_block(page_id \| prefill_id, block_id, block)` | Replace one block by row id (new id returned; human-assigned media carried over unless the type changes). |
128
+ | `remove_block(page_id \| prefill_id, block_id)` | Remove one block by row id. |
129
+ | `reorder_blocks(page_id \| prefill_id, order)` | Reorder blocks (order = array of all row ids in new order). |
130
+ | `extract_block_to_prefill(page_id, block_id, title)` | Move a page block into a new Prefill entry (lossless, media survives) and reference it in place. |
131
+ | `list_forms` / `get_form(id)` | Form entries; `get_form` includes the `fwcFields` rows. |
132
+ | `get_form_schema` | Live field-group catalogue for authoring forms. |
133
+ | `create_form` / `update_form` / `delete_form` | Form CRUD. `fwcFields` rows: `{group, label, name, required, width, ...}`; delete guarded unless `force:true`. |
134
+ | `list_menus` / `get_menu(id)` | Menu entries; `get_menu` includes the navigation tree. |
135
+ | `create_menu` / `update_menu` / `delete_menu` | Menu CRUD. Tree items `{title, url \| {page_id}, anchor, blank, children}`, max 2 levels; delete guarded. |
136
+ | `list_prefills` / `get_prefill(id)` | Prefill entries; `get_prefill` includes the builder blocks. |
137
+ | `create_prefill` / `update_prefill` / `delete_prefill` | Prefill CRUD — same block shape as pages; delete guarded. |
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 variables. Integration secrets are not exposed. |
140
+ | `get_block_schema(name)` | Field schema + usage notes for one block (live from the CMS). |
141
+
142
+ Form and Menu entries have no translation linking — create them per site by passing `site_id`.
143
+
144
+ ## Resource
145
+
146
+ - `frameworc://blocks` — reference for all 15 block types (fields, defaults, enums, conditional visibility, nested repeaters, reference fields). The chat reads this once per session; you don't invoke it manually.
147
+
148
+ ## Page-content JSON shape
149
+
150
+ `create_page` / `update_page` `payload`:
151
+
152
+ ```json
153
+ {
154
+ "page": {
155
+ "title": "Contact",
156
+ "slug": "contact",
157
+ "fullslug": "contact",
158
+ "is_enabled": false,
159
+ "metaTitle": "",
160
+ "metaDescription": "",
161
+ "menuStyle": "solid",
162
+ "menuHide": "no"
163
+ },
164
+ "builder": [
165
+ {
166
+ "content_group": "Header",
167
+ "base": {
168
+ "blockId": "kontakt",
169
+ "headline": "<h1>Contact us</h1>",
170
+ "elevated": false,
171
+ "containerWidth": "default",
172
+ "backgroundColor": "default",
173
+ "customCssClass": [],
174
+ "responsiveHide": []
175
+ },
176
+ "content": {
177
+ "image": "",
178
+ "imageMobile": "",
179
+ "isVideoBg": false,
180
+ "buttonLabel1": "",
181
+ "buttonLink1": "",
182
+ "buttonBlank1": false,
183
+ "fullHeight": true,
184
+ "contrast": false,
185
+ "overlay": false
186
+ }
187
+ },
188
+ {
189
+ "content_group": "Form",
190
+ "base": { "blockId": "formular", "headline": "<h2>Write us</h2>" },
191
+ "form": 7,
192
+ "content": { "variant": "default" }
193
+ }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ ### Storage encoding (what the MCP understands)
199
+
200
+ - Media fields (`image`, `imageMobile`, `backgroundImage`, `buttonIcon1..4`, `ogImage`, `images`, `file`) **must be empty** in the JSON — they are rejected if non-empty. Fill them in the OctCMS backend.
201
+ - `switch` fields accept booleans; the API normalises to `"1"` / `"0"` for storage.
202
+ - `customCssClass` + `responsiveHide` are arrays of strings.
203
+ - Multi-`mediafinder` fields (`images` for `Gallery` / `ImageStrip`) are arrays of path strings — but must be empty `[]` per the rule above.
204
+ - `entries` links are integers (or `{id: n}`): `form` (block level for `Form`), `content.menu` (for `MenuBlock`), `content.block` (for `Prefill`). Reads return them as `{id, title}`.
205
+ - `Slider` accepts an optional `content.breakpoints = { tablet: number, mobile: number }`.
206
+ - `Columns` blocks have `content.columns = [{ blockId, builder: [...blocks] }]` — recursive: each column's `builder` follows the exact same shape as the top-level `builder`.
207
+
208
+ ## Catalogue drift
209
+
210
+ The hand-authored block catalogue lives in `src/catalogue.ts`. It mirrors `plugins/crscompany/frameworc/blueprints/Blocks/*.yaml` + `Mixins/Buttons.yaml` + `Mixins/Buttons2.yaml` + `BaseBlock.yaml`. If a block blueprint changes (new field, new enum value, removed field), update `src/catalogue.ts` to match and rebuild. No generator script — kept manual because blocks change rarely per the FrameworC convention.
211
+
212
+ ## Publishing
213
+
214
+ To publish to your GitLab group's npm registry:
215
+
216
+ 1. Edit `package.json`:
217
+ - Replace `@yourorg` in `name` and `publishConfig["@yourorg:registry"]` with your actual GitLab scope/group.
218
+ - Replace `<YOUR-PROJECT-ID>` in `publishConfig["@yourorg:registry"]` with the numeric project id of the `frameworc-mcp` GitLab repo.
219
+ 2. Create a GitLab deploy token / project access token with `api` + `write_registry` scope. Add a `.npmrc` at the registry host:
220
+
221
+ ```
222
+ @yourorg:registry=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/
223
+ //gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/:_authToken=<TOKEN>
224
+ ```
225
+ 3. `npm publish`.
226
+
227
+ Colleagues then use `npx -y @yourorg/frameworc-mcp` in their `claude_desktop_config.json` (no clone needed).
228
+
229
+ ## License
230
+
216
231
  MIT.