mcp-magento-cms 1.0.3 → 1.2.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 +173 -82
- package/package.json +4 -3
- package/src/assignStores.js +150 -0
- package/src/index.js +840 -89
- package/src/magentoClient.js +95 -4
- package/src/pageBuilder.js +66 -68
- package/src/pageBuilderElements.js +789 -0
- package/src/pageBuilderRepair.js +266 -0
- package/src/pageBuilderRules.js +127 -0
- package/src/pageBuilderValidate.js +164 -0
- package/src/projectRules.js +95 -0
package/src/index.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* (standard MCP stdio transport, used by Cursor); auto-switches to
|
|
6
6
|
* Content-Length framing if the client sends framed messages.
|
|
7
7
|
*
|
|
8
|
-
* Tools: magento_health,
|
|
9
|
-
* Page Builder:
|
|
8
|
+
* Tools: magento_health, compose/validate/repair Page Builder, CMS CRUD
|
|
9
|
+
* Page Builder: all Magento_PageBuilder native content types via compose_pagebuilder
|
|
10
10
|
*
|
|
11
11
|
* Env: MAGENTO_BASE_URL, MAGENTO_ACCESS_TOKEN (or tools/mcp-magento-cms/.env)
|
|
12
12
|
*/
|
|
@@ -16,17 +16,33 @@ import path from 'node:path';
|
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import {
|
|
18
18
|
composeFromFields,
|
|
19
|
+
composeDocument,
|
|
19
20
|
generateIdentifier,
|
|
20
21
|
validatePageBuilderHtml,
|
|
22
|
+
PAGEBUILDER_ELEMENT_TYPES,
|
|
23
|
+
describeElements,
|
|
21
24
|
} from './pageBuilder.js';
|
|
25
|
+
import { validatePageBuilderContent } from './pageBuilderValidate.js';
|
|
26
|
+
import { repairPageBuilderContent } from './pageBuilderRepair.js';
|
|
27
|
+
import {
|
|
28
|
+
PAGEBUILDER_RULES_SUMMARY,
|
|
29
|
+
CMS_THEME_CONVENTIONS_SUMMARY,
|
|
30
|
+
SHARED_RULES_SUMMARY,
|
|
31
|
+
SHARED_RULES_DETAIL,
|
|
32
|
+
} from './pageBuilderRules.js';
|
|
33
|
+
import { loadProjectRules } from './projectRules.js';
|
|
22
34
|
import { MagentoClient, loadDotEnv } from './magentoClient.js';
|
|
35
|
+
import {
|
|
36
|
+
assignCmsStores,
|
|
37
|
+
resolveStoreIds,
|
|
38
|
+
} from './assignStores.js';
|
|
23
39
|
|
|
24
40
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
41
|
loadDotEnv();
|
|
26
42
|
|
|
27
43
|
const SERVER_INFO = {
|
|
28
44
|
name: 'magento-cms',
|
|
29
|
-
version: '1.
|
|
45
|
+
version: '1.2.0',
|
|
30
46
|
};
|
|
31
47
|
|
|
32
48
|
// ---------------------------------------------------------------------------
|
|
@@ -86,21 +102,73 @@ function client() {
|
|
|
86
102
|
return MagentoClient.fromEnv();
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
|
|
105
|
+
/** Magento 2.4.8 cmsPage REST returns `active`; older docs use `is_active`. */
|
|
106
|
+
function entityIsActive(entity) {
|
|
107
|
+
if (entity?.active !== undefined && entity?.active !== null) {
|
|
108
|
+
return entity.active;
|
|
109
|
+
}
|
|
110
|
+
if (entity?.is_active !== undefined && entity?.is_active !== null) {
|
|
111
|
+
return entity.is_active;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function summarizeEntity(entity, type, editUrl, extras = {}) {
|
|
117
|
+
const content = String(entity?.content || '');
|
|
90
118
|
return {
|
|
91
119
|
type,
|
|
92
120
|
id: entity?.id ?? entity?.page_id ?? entity?.block_id ?? null,
|
|
93
121
|
title: entity?.title ?? null,
|
|
94
122
|
identifier: entity?.identifier ?? null,
|
|
95
|
-
is_active: entity
|
|
96
|
-
content_preview:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
entity.content.includes('data-content-type="row"'),
|
|
123
|
+
is_active: entityIsActive(entity),
|
|
124
|
+
content_preview: content.slice(0, 280),
|
|
125
|
+
content_length: content.length,
|
|
126
|
+
has_pagebuilder: content.includes('data-content-type="row"'),
|
|
100
127
|
admin_edit_url: editUrl,
|
|
128
|
+
...extras,
|
|
101
129
|
};
|
|
102
130
|
}
|
|
103
131
|
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolve CMS content from args:
|
|
135
|
+
* 1) sections[] → composeDocument (full Page Builder tree)
|
|
136
|
+
* 2) html → raw master-format HTML
|
|
137
|
+
* 3) content (+headline) → simple Row→Text
|
|
138
|
+
*/
|
|
139
|
+
function resolvePageBuilderContent(args = {}) {
|
|
140
|
+
if (Array.isArray(args.sections) && args.sections.length) {
|
|
141
|
+
const doc = composeDocument(args.sections);
|
|
142
|
+
return {
|
|
143
|
+
html: doc.html,
|
|
144
|
+
mode: 'sections',
|
|
145
|
+
used_types: doc.used_types,
|
|
146
|
+
allow_full_bleed: doc.allow_full_bleed,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (args.html != null && String(args.html).trim()) {
|
|
150
|
+
return {
|
|
151
|
+
html: String(args.html),
|
|
152
|
+
mode: 'html',
|
|
153
|
+
used_types: [],
|
|
154
|
+
allow_full_bleed: args.allow_full_bleed === true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (args.content != null && String(args.content).length) {
|
|
158
|
+
return {
|
|
159
|
+
html: composeFromFields({
|
|
160
|
+
content: args.content,
|
|
161
|
+
headline: args.headline,
|
|
162
|
+
subheadline: args.subheadline,
|
|
163
|
+
}),
|
|
164
|
+
mode: 'simple_text',
|
|
165
|
+
used_types: ['row', 'text'],
|
|
166
|
+
allow_full_bleed: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
104
172
|
const TOOLS = [
|
|
105
173
|
{
|
|
106
174
|
name: 'magento_health',
|
|
@@ -108,152 +176,776 @@ const TOOLS = [
|
|
|
108
176
|
'Check Magento REST connectivity and Integration token (reads CMS pages list).',
|
|
109
177
|
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
110
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: 'pagebuilder_rules',
|
|
181
|
+
description:
|
|
182
|
+
'Return Page Builder hard rules plus optional store project rules (media paths, theme tokens) from MAGENTO_CMS_PROJECT_RULES. Read before editing CMS HTML or media.',
|
|
183
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'list_section_types',
|
|
187
|
+
description:
|
|
188
|
+
'List supported Page Builder composers and raw-HTML tools for CMS pages/blocks.',
|
|
189
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'search_cms',
|
|
193
|
+
description:
|
|
194
|
+
'Search CMS pages and/or blocks by identifier (exact or like).',
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: 'object',
|
|
197
|
+
properties: {
|
|
198
|
+
identifier: { type: 'string', description: 'URL key / identifier' },
|
|
199
|
+
type: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'page | block | both (default both)',
|
|
202
|
+
},
|
|
203
|
+
condition_type: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: 'eq (default) or like',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ['identifier'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
111
211
|
{
|
|
112
212
|
name: 'get_cms_page',
|
|
113
|
-
description:
|
|
213
|
+
description:
|
|
214
|
+
'Get a Magento CMS page by page_id. Optionally include full content and Page Builder validation.',
|
|
114
215
|
inputSchema: {
|
|
115
216
|
type: 'object',
|
|
116
217
|
properties: {
|
|
117
|
-
page_id: { type: 'number', description: 'CMS page ID, e.g.
|
|
218
|
+
page_id: { type: 'number', description: 'CMS page ID, e.g. 124' },
|
|
219
|
+
include_content: {
|
|
220
|
+
type: 'boolean',
|
|
221
|
+
description: 'If true, include full Page Builder HTML',
|
|
222
|
+
},
|
|
223
|
+
validate: {
|
|
224
|
+
type: 'boolean',
|
|
225
|
+
description: 'If true, run Page Builder stage-safety validation',
|
|
226
|
+
},
|
|
118
227
|
},
|
|
119
228
|
required: ['page_id'],
|
|
120
229
|
},
|
|
121
230
|
},
|
|
122
231
|
{
|
|
232
|
+
name: 'get_cms_block',
|
|
233
|
+
description:
|
|
234
|
+
'Get a Magento CMS block by block_id. Optionally include full content and validation.',
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: 'object',
|
|
237
|
+
properties: {
|
|
238
|
+
block_id: { type: 'number' },
|
|
239
|
+
include_content: { type: 'boolean' },
|
|
240
|
+
validate: { type: 'boolean' },
|
|
241
|
+
},
|
|
242
|
+
required: ['block_id'],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'validate_pagebuilder',
|
|
247
|
+
description:
|
|
248
|
+
'Validate Page Builder HTML for admin stage safety (orphan styles, full-bleed, missing inners/widths). Pass html, or page_id / block_id to load from Magento. Use allow_full_bleed=true for intentional edge-to-edge heroes.',
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
html: { type: 'string', description: 'Raw Page Builder HTML' },
|
|
253
|
+
page_id: { type: 'number' },
|
|
254
|
+
block_id: { type: 'number' },
|
|
255
|
+
allow_full_bleed: {
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
description: 'Treat full-bleed as warning instead of error (default false)',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'repair_pagebuilder',
|
|
264
|
+
description:
|
|
265
|
+
'Repair Page Builder HTML so admin stage can open: remove orphan styles, fix full-bleed→full-width with inner, add column widths, strip SVG. Pass html to preview, or page_id/block_id with apply=true to save.',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
html: { type: 'string' },
|
|
270
|
+
page_id: { type: 'number' },
|
|
271
|
+
block_id: { type: 'number' },
|
|
272
|
+
apply: {
|
|
273
|
+
type: 'boolean',
|
|
274
|
+
description: 'If true and page_id/block_id set, save repaired HTML to Magento',
|
|
275
|
+
},
|
|
276
|
+
strip_svg: { type: 'boolean', description: 'Default true' },
|
|
277
|
+
fix_full_bleed: { type: 'boolean', description: 'Default true' },
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'set_pagebuilder_html',
|
|
283
|
+
description:
|
|
284
|
+
'Save RAW Page Builder HTML to a CMS page or block WITHOUT rebuilding as simple_text. Use for complex layouts (Banner+button, columns, full-bleed hero). Validates before save unless force=true. For intentional full-bleed heroes set allow_full_bleed=true (and repair_first keeps bleed).',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
page_id: { type: 'number' },
|
|
289
|
+
block_id: { type: 'number' },
|
|
290
|
+
html: { type: 'string', description: 'Full Page Builder master-format HTML' },
|
|
291
|
+
force: {
|
|
292
|
+
type: 'boolean',
|
|
293
|
+
description: 'Save even if validation reports errors (default false)',
|
|
294
|
+
},
|
|
295
|
+
repair_first: {
|
|
296
|
+
type: 'boolean',
|
|
297
|
+
description: 'Run repair_pagebuilder before save (default true)',
|
|
298
|
+
},
|
|
299
|
+
allow_full_bleed: {
|
|
300
|
+
type: 'boolean',
|
|
301
|
+
description:
|
|
302
|
+
'When true, full-bleed rows are warnings not errors, and repair will NOT convert them to full-width (default false)',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
required: ['html'],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'compose_pagebuilder',
|
|
310
|
+
description:
|
|
311
|
+
'Compose Magento Page Builder master-format HTML from a structured sections[] tree covering ALL native content types (row, columns, text, heading, image, buttons, banner, html, divider, video, map, tabs, slider, products, block). Does not save unless page_id/block_id provided. Prefer this over project PHP composers.',
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: 'object',
|
|
314
|
+
properties: {
|
|
315
|
+
sections: {
|
|
316
|
+
type: 'array',
|
|
317
|
+
description:
|
|
318
|
+
'Tree of {type, ...props, children?}. Root items are usually row. See list_section_types.',
|
|
319
|
+
},
|
|
320
|
+
page_id: {
|
|
321
|
+
type: 'number',
|
|
322
|
+
description: 'Optional: save composed HTML to this CMS page',
|
|
323
|
+
},
|
|
324
|
+
block_id: {
|
|
325
|
+
type: 'number',
|
|
326
|
+
description: 'Optional: save composed HTML to this CMS block',
|
|
327
|
+
},
|
|
328
|
+
validate: {
|
|
329
|
+
type: 'boolean',
|
|
330
|
+
description: 'Validate stage-safety (default true)',
|
|
331
|
+
},
|
|
332
|
+
repair_first: {
|
|
333
|
+
type: 'boolean',
|
|
334
|
+
description: 'When saving, run repair first (default true)',
|
|
335
|
+
},
|
|
336
|
+
allow_full_bleed: {
|
|
337
|
+
type: 'boolean',
|
|
338
|
+
description:
|
|
339
|
+
'Allow full-bleed rows on validate/save. Auto-true if sections include full-bleed row unless set false.',
|
|
340
|
+
},
|
|
341
|
+
force: {
|
|
342
|
+
type: 'boolean',
|
|
343
|
+
description: 'Save even if validation fails (default false)',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
required: ['sections'],
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
123
350
|
name: 'create_cms_page',
|
|
124
351
|
description:
|
|
125
|
-
'Create a Magento CMS page
|
|
352
|
+
'Create a Magento CMS page. Pass sections[] for full Page Builder trees, html for raw master HTML, or content for simple Row→Text. Defaults: page_layout cms-full-width (Page — Full Width), All Store Views (store_id 0). Pass page_layout / store_ids only when the user asks for something different.',
|
|
126
353
|
inputSchema: {
|
|
127
354
|
type: 'object',
|
|
128
355
|
properties: {
|
|
129
356
|
title: { type: 'string', description: 'Page title' },
|
|
357
|
+
sections: {
|
|
358
|
+
type: 'array',
|
|
359
|
+
description: 'Page Builder element tree (preferred for complex pages)',
|
|
360
|
+
},
|
|
361
|
+
html: {
|
|
362
|
+
type: 'string',
|
|
363
|
+
description: 'Raw Page Builder master-format HTML',
|
|
364
|
+
},
|
|
130
365
|
content: {
|
|
131
366
|
type: 'string',
|
|
132
|
-
description: 'Plain text or simple HTML
|
|
367
|
+
description: 'Plain text or simple HTML → Row→Text (legacy)',
|
|
133
368
|
},
|
|
134
369
|
identifier: {
|
|
135
370
|
type: 'string',
|
|
136
371
|
description: 'URL key; auto from title if omitted',
|
|
137
372
|
},
|
|
138
|
-
headline: { type: 'string', description: 'Optional H2
|
|
373
|
+
headline: { type: 'string', description: 'Optional H2 when using content' },
|
|
139
374
|
is_active: { type: 'boolean', description: 'Default true' },
|
|
140
|
-
page_layout: { type: 'string', description: 'Default
|
|
375
|
+
page_layout: { type: 'string', description: 'Default cms-full-width (Page — Full Width); pass page_layout only when the user specifies a different layout' },
|
|
376
|
+
allow_full_bleed: { type: 'boolean' },
|
|
377
|
+
store_ids: {
|
|
378
|
+
type: 'array',
|
|
379
|
+
description:
|
|
380
|
+
'Store view IDs to assign. Default [0] = All Store Views. Pass specific IDs only when the user asks for particular store(s).',
|
|
381
|
+
items: { type: 'number' },
|
|
382
|
+
},
|
|
141
383
|
},
|
|
142
|
-
required: ['title'
|
|
384
|
+
required: ['title'],
|
|
143
385
|
},
|
|
144
386
|
},
|
|
145
387
|
{
|
|
146
388
|
name: 'update_cms_page',
|
|
147
389
|
description:
|
|
148
|
-
'Update
|
|
390
|
+
'Update CMS page metadata and/or content. Prefer sections[] or html for complex layouts (same as compose_pagebuilder / set_pagebuilder_html). content alone still rebuilds as Row→Text.',
|
|
149
391
|
inputSchema: {
|
|
150
392
|
type: 'object',
|
|
151
393
|
properties: {
|
|
152
394
|
page_id: { type: 'number', description: 'CMS page ID to update' },
|
|
153
395
|
title: { type: 'string' },
|
|
396
|
+
sections: { type: 'array' },
|
|
397
|
+
html: { type: 'string' },
|
|
154
398
|
content: { type: 'string' },
|
|
155
399
|
headline: { type: 'string' },
|
|
156
400
|
identifier: { type: 'string' },
|
|
157
401
|
is_active: { type: 'boolean' },
|
|
158
|
-
page_layout: { type: 'string' },
|
|
402
|
+
page_layout: { type: 'string', description: 'Omit to keep existing; on create default is cms-full-width — only set when the user asks for a different layout' },
|
|
403
|
+
allow_full_bleed: { type: 'boolean' },
|
|
404
|
+
store_ids: {
|
|
405
|
+
type: 'array',
|
|
406
|
+
description:
|
|
407
|
+
'Optional store view IDs. Omit to leave assignment unchanged; pass [0] for All Store Views or specific IDs when the user asks.',
|
|
408
|
+
items: { type: 'number' },
|
|
409
|
+
},
|
|
159
410
|
},
|
|
160
411
|
required: ['page_id'],
|
|
161
412
|
},
|
|
162
413
|
},
|
|
163
|
-
{
|
|
164
|
-
name: 'get_cms_block',
|
|
165
|
-
description: 'Get a Magento CMS block by block_id.',
|
|
166
|
-
inputSchema: {
|
|
167
|
-
type: 'object',
|
|
168
|
-
properties: {
|
|
169
|
-
block_id: { type: 'number' },
|
|
170
|
-
},
|
|
171
|
-
required: ['block_id'],
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
414
|
{
|
|
175
415
|
name: 'create_cms_block',
|
|
176
416
|
description:
|
|
177
|
-
'Create a Magento CMS block
|
|
417
|
+
'Create a Magento CMS block. Pass sections[] (full PB tree), html, or content (Row→Text).',
|
|
178
418
|
inputSchema: {
|
|
179
419
|
type: 'object',
|
|
180
420
|
properties: {
|
|
181
421
|
title: { type: 'string' },
|
|
422
|
+
sections: { type: 'array' },
|
|
423
|
+
html: { type: 'string' },
|
|
182
424
|
content: { type: 'string' },
|
|
183
425
|
identifier: { type: 'string' },
|
|
184
426
|
headline: { type: 'string' },
|
|
185
427
|
is_active: { type: 'boolean' },
|
|
428
|
+
allow_full_bleed: { type: 'boolean' },
|
|
429
|
+
store_ids: {
|
|
430
|
+
type: 'array',
|
|
431
|
+
description:
|
|
432
|
+
'Store view IDs to assign. Default [0] = All Store Views. Pass specific IDs only when the user asks for particular store(s).',
|
|
433
|
+
items: { type: 'number' },
|
|
434
|
+
},
|
|
186
435
|
},
|
|
187
|
-
required: ['title'
|
|
436
|
+
required: ['title'],
|
|
188
437
|
},
|
|
189
438
|
},
|
|
190
439
|
{
|
|
191
440
|
name: 'update_cms_block',
|
|
192
441
|
description:
|
|
193
|
-
'Update
|
|
442
|
+
'Update CMS block metadata and/or content via sections[], html, or content (Row→Text).',
|
|
194
443
|
inputSchema: {
|
|
195
444
|
type: 'object',
|
|
196
445
|
properties: {
|
|
197
446
|
block_id: { type: 'number' },
|
|
198
447
|
title: { type: 'string' },
|
|
448
|
+
sections: { type: 'array' },
|
|
449
|
+
html: { type: 'string' },
|
|
199
450
|
content: { type: 'string' },
|
|
200
451
|
headline: { type: 'string' },
|
|
201
452
|
identifier: { type: 'string' },
|
|
202
453
|
is_active: { type: 'boolean' },
|
|
454
|
+
allow_full_bleed: { type: 'boolean' },
|
|
455
|
+
store_ids: {
|
|
456
|
+
type: 'array',
|
|
457
|
+
description:
|
|
458
|
+
'Optional store view IDs. Omit to leave assignment unchanged; pass [0] for All Store Views or specific IDs when the user asks.',
|
|
459
|
+
items: { type: 'number' },
|
|
460
|
+
},
|
|
203
461
|
},
|
|
204
462
|
required: ['block_id'],
|
|
205
463
|
},
|
|
206
464
|
},
|
|
207
|
-
{
|
|
208
|
-
name: 'list_section_types',
|
|
209
|
-
description:
|
|
210
|
-
'List supported Page Builder section types (MVP: simple_text).',
|
|
211
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
212
|
-
},
|
|
213
465
|
];
|
|
214
466
|
|
|
467
|
+
async function loadHtmlFromArgs(magento, args) {
|
|
468
|
+
if (args.html !== undefined && String(args.html || '').length) {
|
|
469
|
+
return { html: String(args.html), source: 'html' };
|
|
470
|
+
}
|
|
471
|
+
if (args.page_id) {
|
|
472
|
+
const page = await magento.getPage(args.page_id);
|
|
473
|
+
return {
|
|
474
|
+
html: String(page.content || ''),
|
|
475
|
+
source: 'page',
|
|
476
|
+
entity: page,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (args.block_id) {
|
|
480
|
+
const block = await magento.getBlock(args.block_id);
|
|
481
|
+
return {
|
|
482
|
+
html: String(block.content || ''),
|
|
483
|
+
source: 'block',
|
|
484
|
+
entity: block,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
throw new Error('Provide html, page_id, or block_id');
|
|
488
|
+
}
|
|
489
|
+
|
|
215
490
|
async function callTool(name, args = {}) {
|
|
216
491
|
switch (name) {
|
|
217
492
|
case 'magento_health': {
|
|
218
493
|
const magento = client();
|
|
219
494
|
const result = await magento.health();
|
|
495
|
+
const project = loadProjectRules();
|
|
220
496
|
return toolOk({
|
|
221
497
|
...result,
|
|
222
|
-
message:
|
|
498
|
+
message:
|
|
499
|
+
'Magento REST OK. CMS content: use this MCP only (no PHP compose/deploy). Call pagebuilder_rules first, then compose_pagebuilder / create_* / set_pagebuilder_html + validate/repair.',
|
|
500
|
+
pagebuilder_rules: PAGEBUILDER_RULES_SUMMARY,
|
|
501
|
+
cms_theme_conventions: CMS_THEME_CONVENTIONS_SUMMARY,
|
|
502
|
+
project_rules: project.rules,
|
|
503
|
+
project_rules_source: project.source,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
case 'pagebuilder_rules': {
|
|
507
|
+
const project = loadProjectRules();
|
|
508
|
+
return toolOk({
|
|
509
|
+
rules: [...SHARED_RULES_SUMMARY, ...project.rules],
|
|
510
|
+
pagebuilder_rules: PAGEBUILDER_RULES_SUMMARY,
|
|
511
|
+
cms_theme_conventions: CMS_THEME_CONVENTIONS_SUMMARY,
|
|
512
|
+
project_rules: project.rules,
|
|
513
|
+
project_rules_source: project.source,
|
|
514
|
+
detail: {
|
|
515
|
+
...SHARED_RULES_DETAIL,
|
|
516
|
+
...project.detail,
|
|
517
|
+
},
|
|
223
518
|
});
|
|
224
519
|
}
|
|
520
|
+
case 'list_section_types':
|
|
521
|
+
return toolOk({
|
|
522
|
+
elements: describeElements(),
|
|
523
|
+
element_types: PAGEBUILDER_ELEMENT_TYPES,
|
|
524
|
+
section_types: [
|
|
525
|
+
{
|
|
526
|
+
type: 'sections_tree',
|
|
527
|
+
description:
|
|
528
|
+
'Preferred: pass sections[] to compose_pagebuilder / create_cms_page / create_cms_block. Nested {type, children} using any Magento_PageBuilder native type.',
|
|
529
|
+
params: ['sections[]'],
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
type: 'simple_text',
|
|
533
|
+
description:
|
|
534
|
+
'Legacy: Page Builder Row (contained) → Text via content/headline fields.',
|
|
535
|
+
params: ['content', 'headline?', 'subheadline?'],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
type: 'raw_pagebuilder_html',
|
|
539
|
+
description:
|
|
540
|
+
'Pass full master-format HTML via set_pagebuilder_html or html= on create/update.',
|
|
541
|
+
params: [
|
|
542
|
+
'html',
|
|
543
|
+
'page_id|block_id',
|
|
544
|
+
'repair_first?',
|
|
545
|
+
'force?',
|
|
546
|
+
'allow_full_bleed?',
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
type: 'banner_poster_cta',
|
|
551
|
+
description:
|
|
552
|
+
'sections pattern: row → banner (poster, show_button=always, link_url, background_image, content_html).',
|
|
553
|
+
params: [
|
|
554
|
+
'background_image',
|
|
555
|
+
'content_html',
|
|
556
|
+
'button_text',
|
|
557
|
+
'link_url',
|
|
558
|
+
'show_button=always',
|
|
559
|
+
'allow_full_bleed?',
|
|
560
|
+
],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
type: 'hero_plus_body',
|
|
564
|
+
description:
|
|
565
|
+
'sections pattern: row(full-bleed|full-width)+banner hero, then row(contained|full-width) body with heading/text/columns/image/buttons.',
|
|
566
|
+
params: [
|
|
567
|
+
'hero_image',
|
|
568
|
+
'content_html',
|
|
569
|
+
'button_text?',
|
|
570
|
+
'link_url?',
|
|
571
|
+
'body children',
|
|
572
|
+
'css_classes?',
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
type: 'column_cards',
|
|
577
|
+
description:
|
|
578
|
+
'sections pattern: columns → each column Image+Text(+Buttons). Prefer Image over SVG-in-Text.',
|
|
579
|
+
params: ['column_count', 'cards[]'],
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
maintenance_tools: [
|
|
583
|
+
'compose_pagebuilder',
|
|
584
|
+
'validate_pagebuilder',
|
|
585
|
+
'repair_pagebuilder',
|
|
586
|
+
'pagebuilder_rules',
|
|
587
|
+
'search_cms',
|
|
588
|
+
],
|
|
589
|
+
note: PAGEBUILDER_RULES_SUMMARY.join(' '),
|
|
590
|
+
});
|
|
591
|
+
case 'search_cms': {
|
|
592
|
+
const magento = client();
|
|
593
|
+
const type = (args.type || 'both').toLowerCase();
|
|
594
|
+
const condition = (args.condition_type || 'eq').toLowerCase();
|
|
595
|
+
const out = { identifier: args.identifier, pages: [], blocks: [] };
|
|
596
|
+
if (type === 'page' || type === 'both') {
|
|
597
|
+
const res = await magento.searchPagesByIdentifier(
|
|
598
|
+
args.identifier,
|
|
599
|
+
condition
|
|
600
|
+
);
|
|
601
|
+
out.pages = (res.items || []).map((p) =>
|
|
602
|
+
summarizeEntity(p, 'page', magento.pageEditUrl(p.id))
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (type === 'block' || type === 'both') {
|
|
606
|
+
const res = await magento.searchBlocksByIdentifier(
|
|
607
|
+
args.identifier,
|
|
608
|
+
condition
|
|
609
|
+
);
|
|
610
|
+
out.blocks = (res.items || []).map((b) =>
|
|
611
|
+
summarizeEntity(b, 'block', magento.blockEditUrl(b.id))
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
return toolOk(out);
|
|
615
|
+
}
|
|
225
616
|
case 'get_cms_page': {
|
|
226
617
|
const magento = client();
|
|
227
618
|
const page = await magento.getPage(args.page_id);
|
|
619
|
+
const extras = {};
|
|
620
|
+
if (args.include_content) extras.content = page.content;
|
|
621
|
+
if (args.validate) {
|
|
622
|
+
extras.validation = validatePageBuilderContent(String(page.content || ''));
|
|
623
|
+
}
|
|
228
624
|
return toolOk(
|
|
229
|
-
summarizeEntity(
|
|
625
|
+
summarizeEntity(
|
|
626
|
+
page,
|
|
627
|
+
'page',
|
|
628
|
+
magento.pageEditUrl(page.id || args.page_id),
|
|
629
|
+
extras
|
|
630
|
+
)
|
|
230
631
|
);
|
|
231
632
|
}
|
|
633
|
+
case 'get_cms_block': {
|
|
634
|
+
const magento = client();
|
|
635
|
+
const block = await magento.getBlock(args.block_id);
|
|
636
|
+
const extras = {};
|
|
637
|
+
if (args.include_content) extras.content = block.content;
|
|
638
|
+
if (args.validate) {
|
|
639
|
+
extras.validation = validatePageBuilderContent(
|
|
640
|
+
String(block.content || '')
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return toolOk(
|
|
644
|
+
summarizeEntity(
|
|
645
|
+
block,
|
|
646
|
+
'block',
|
|
647
|
+
magento.blockEditUrl(block.id || args.block_id),
|
|
648
|
+
extras
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
case 'validate_pagebuilder': {
|
|
653
|
+
const magento = client();
|
|
654
|
+
const loaded = await loadHtmlFromArgs(magento, args);
|
|
655
|
+
const validation = validatePageBuilderContent(loaded.html, {
|
|
656
|
+
allowFullBleed: args.allow_full_bleed === true,
|
|
657
|
+
});
|
|
658
|
+
return toolOk({
|
|
659
|
+
source: loaded.source,
|
|
660
|
+
page_id: args.page_id || null,
|
|
661
|
+
block_id: args.block_id || null,
|
|
662
|
+
identifier: loaded.entity?.identifier || null,
|
|
663
|
+
allow_full_bleed: args.allow_full_bleed === true,
|
|
664
|
+
...validation,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
case 'repair_pagebuilder': {
|
|
668
|
+
const magento = client();
|
|
669
|
+
const loaded = await loadHtmlFromArgs(magento, args);
|
|
670
|
+
const repaired = repairPageBuilderContent(loaded.html, {
|
|
671
|
+
stripSvg: args.strip_svg,
|
|
672
|
+
fixFullBleed: args.fix_full_bleed,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
let saved = null;
|
|
676
|
+
if (args.apply) {
|
|
677
|
+
if (args.page_id) {
|
|
678
|
+
const existing = loaded.entity || (await magento.getPage(args.page_id));
|
|
679
|
+
await magento.updatePage(args.page_id, {
|
|
680
|
+
title: existing.title,
|
|
681
|
+
identifier: existing.identifier,
|
|
682
|
+
content: repaired.content,
|
|
683
|
+
content_heading: existing.content_heading ?? '',
|
|
684
|
+
page_layout: existing.page_layout || 'cms-full-width',
|
|
685
|
+
is_active: entityIsActive(existing),
|
|
686
|
+
meta_title: existing.meta_title || existing.title,
|
|
687
|
+
meta_description: existing.meta_description || '',
|
|
688
|
+
});
|
|
689
|
+
saved = { type: 'page', id: args.page_id };
|
|
690
|
+
} else if (args.block_id) {
|
|
691
|
+
const existing =
|
|
692
|
+
loaded.entity || (await magento.getBlock(args.block_id));
|
|
693
|
+
await magento.updateBlock(args.block_id, {
|
|
694
|
+
title: existing.title,
|
|
695
|
+
identifier: existing.identifier,
|
|
696
|
+
content: repaired.content,
|
|
697
|
+
is_active: entityIsActive(existing),
|
|
698
|
+
});
|
|
699
|
+
saved = { type: 'block', id: args.block_id };
|
|
700
|
+
} else {
|
|
701
|
+
throw new Error('apply=true requires page_id or block_id');
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return toolOk({
|
|
706
|
+
changed: repaired.changed,
|
|
707
|
+
actions: repaired.actions,
|
|
708
|
+
before_ok: repaired.before.ok,
|
|
709
|
+
after_ok: repaired.after.ok,
|
|
710
|
+
before_errors: repaired.before.errors,
|
|
711
|
+
after_errors: repaired.after.errors,
|
|
712
|
+
after_warnings: repaired.after.warnings,
|
|
713
|
+
saved,
|
|
714
|
+
// Only return repaired HTML when not applied (preview) or explicitly small
|
|
715
|
+
content_length: repaired.content.length,
|
|
716
|
+
content_preview: repaired.content.slice(0, 400),
|
|
717
|
+
content: args.apply ? undefined : repaired.content,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
case 'set_pagebuilder_html': {
|
|
721
|
+
const magento = client();
|
|
722
|
+
if (!args.page_id && !args.block_id) {
|
|
723
|
+
throw new Error('set_pagebuilder_html requires page_id or block_id');
|
|
724
|
+
}
|
|
725
|
+
let html = String(args.html || '');
|
|
726
|
+
const repairFirst = args.repair_first !== false;
|
|
727
|
+
const allowFullBleed = args.allow_full_bleed === true;
|
|
728
|
+
let repairMeta = null;
|
|
729
|
+
if (repairFirst) {
|
|
730
|
+
const repaired = repairPageBuilderContent(html, {
|
|
731
|
+
stripSvg: true,
|
|
732
|
+
// Keep intentional full-bleed heroes (edge-to-edge Banner)
|
|
733
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
734
|
+
});
|
|
735
|
+
html = repaired.content;
|
|
736
|
+
repairMeta = {
|
|
737
|
+
actions: repaired.actions,
|
|
738
|
+
after_ok: repaired.after.ok,
|
|
739
|
+
after_errors: repaired.after.errors,
|
|
740
|
+
fix_full_bleed: allowFullBleed ? false : true,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
const validation = validatePageBuilderContent(html, {
|
|
744
|
+
allowFullBleed,
|
|
745
|
+
});
|
|
746
|
+
if (!validation.ok && !args.force) {
|
|
747
|
+
throw new Error(
|
|
748
|
+
`Page Builder validation failed (pass force=true to save anyway): ${validation.errors.join(
|
|
749
|
+
' | '
|
|
750
|
+
)}`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
if (args.page_id) {
|
|
754
|
+
const existing = await magento.getPage(args.page_id);
|
|
755
|
+
await magento.updatePage(args.page_id, {
|
|
756
|
+
title: existing.title,
|
|
757
|
+
identifier: existing.identifier,
|
|
758
|
+
content: html,
|
|
759
|
+
content_heading: existing.content_heading ?? '',
|
|
760
|
+
page_layout: existing.page_layout || 'cms-full-width',
|
|
761
|
+
is_active: entityIsActive(existing),
|
|
762
|
+
meta_title: existing.meta_title || existing.title,
|
|
763
|
+
meta_description: existing.meta_description || '',
|
|
764
|
+
});
|
|
765
|
+
return toolOk({
|
|
766
|
+
result: 'saved',
|
|
767
|
+
type: 'page',
|
|
768
|
+
id: args.page_id,
|
|
769
|
+
content_length: html.length,
|
|
770
|
+
allow_full_bleed: allowFullBleed,
|
|
771
|
+
validation,
|
|
772
|
+
repair: repairMeta,
|
|
773
|
+
admin_edit_url: magento.pageEditUrl(args.page_id),
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
const existing = await magento.getBlock(args.block_id);
|
|
777
|
+
await magento.updateBlock(args.block_id, {
|
|
778
|
+
title: existing.title,
|
|
779
|
+
identifier: existing.identifier,
|
|
780
|
+
content: html,
|
|
781
|
+
is_active: entityIsActive(existing),
|
|
782
|
+
});
|
|
783
|
+
return toolOk({
|
|
784
|
+
result: 'saved',
|
|
785
|
+
type: 'block',
|
|
786
|
+
id: args.block_id,
|
|
787
|
+
content_length: html.length,
|
|
788
|
+
allow_full_bleed: allowFullBleed,
|
|
789
|
+
validation,
|
|
790
|
+
repair: repairMeta,
|
|
791
|
+
admin_edit_url: magento.blockEditUrl(args.block_id),
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
case 'compose_pagebuilder': {
|
|
795
|
+
const doc = composeDocument(args.sections);
|
|
796
|
+
let html = doc.html;
|
|
797
|
+
const allowFullBleed =
|
|
798
|
+
args.allow_full_bleed === false
|
|
799
|
+
? false
|
|
800
|
+
: args.allow_full_bleed === true || doc.allow_full_bleed;
|
|
801
|
+
const doValidate = args.validate !== false;
|
|
802
|
+
let repairMeta = null;
|
|
803
|
+
if (args.page_id || args.block_id) {
|
|
804
|
+
const repairFirst = args.repair_first !== false;
|
|
805
|
+
if (repairFirst) {
|
|
806
|
+
const repaired = repairPageBuilderContent(html, {
|
|
807
|
+
stripSvg: true,
|
|
808
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
809
|
+
});
|
|
810
|
+
html = repaired.content;
|
|
811
|
+
repairMeta = {
|
|
812
|
+
actions: repaired.actions,
|
|
813
|
+
after_ok: repaired.after.ok,
|
|
814
|
+
after_errors: repaired.after.errors,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const validation = doValidate
|
|
819
|
+
? validatePageBuilderContent(html, { allowFullBleed })
|
|
820
|
+
: null;
|
|
821
|
+
if (doValidate && validation && !validation.ok && !args.force) {
|
|
822
|
+
if (!args.page_id && !args.block_id) {
|
|
823
|
+
return toolOk({
|
|
824
|
+
result: 'composed',
|
|
825
|
+
saved: false,
|
|
826
|
+
used_types: doc.used_types,
|
|
827
|
+
allow_full_bleed: allowFullBleed,
|
|
828
|
+
validation,
|
|
829
|
+
content_length: html.length,
|
|
830
|
+
html,
|
|
831
|
+
note: 'Validation reported errors — HTML still returned. Pass force=true with page_id/block_id to save anyway.',
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
throw new Error(
|
|
835
|
+
`Page Builder validation failed (pass force=true to save anyway): ${validation.errors.join(
|
|
836
|
+
' | '
|
|
837
|
+
)}`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (args.page_id || args.block_id) {
|
|
842
|
+
const magento = client();
|
|
843
|
+
if (args.page_id) {
|
|
844
|
+
const existing = await magento.getPage(args.page_id);
|
|
845
|
+
await magento.updatePage(args.page_id, {
|
|
846
|
+
title: existing.title,
|
|
847
|
+
identifier: existing.identifier,
|
|
848
|
+
content: html,
|
|
849
|
+
content_heading: existing.content_heading ?? '',
|
|
850
|
+
page_layout: existing.page_layout || 'cms-full-width',
|
|
851
|
+
is_active: entityIsActive(existing),
|
|
852
|
+
meta_title: existing.meta_title || existing.title,
|
|
853
|
+
meta_description: existing.meta_description || '',
|
|
854
|
+
});
|
|
855
|
+
return toolOk({
|
|
856
|
+
result: 'saved',
|
|
857
|
+
type: 'page',
|
|
858
|
+
id: args.page_id,
|
|
859
|
+
used_types: doc.used_types,
|
|
860
|
+
allow_full_bleed: allowFullBleed,
|
|
861
|
+
validation,
|
|
862
|
+
repair: repairMeta,
|
|
863
|
+
content_length: html.length,
|
|
864
|
+
admin_edit_url: magento.pageEditUrl(args.page_id),
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
const existing = await magento.getBlock(args.block_id);
|
|
868
|
+
await magento.updateBlock(args.block_id, {
|
|
869
|
+
title: existing.title,
|
|
870
|
+
identifier: existing.identifier,
|
|
871
|
+
content: html,
|
|
872
|
+
is_active: entityIsActive(existing),
|
|
873
|
+
});
|
|
874
|
+
return toolOk({
|
|
875
|
+
result: 'saved',
|
|
876
|
+
type: 'block',
|
|
877
|
+
id: args.block_id,
|
|
878
|
+
used_types: doc.used_types,
|
|
879
|
+
allow_full_bleed: allowFullBleed,
|
|
880
|
+
validation,
|
|
881
|
+
repair: repairMeta,
|
|
882
|
+
content_length: html.length,
|
|
883
|
+
admin_edit_url: magento.blockEditUrl(args.block_id),
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return toolOk({
|
|
888
|
+
result: 'composed',
|
|
889
|
+
saved: false,
|
|
890
|
+
used_types: doc.used_types,
|
|
891
|
+
allow_full_bleed: allowFullBleed,
|
|
892
|
+
validation,
|
|
893
|
+
content_length: html.length,
|
|
894
|
+
html,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
232
897
|
case 'create_cms_page': {
|
|
233
898
|
const magento = client();
|
|
234
899
|
const identifier =
|
|
235
900
|
(args.identifier || '').trim() || generateIdentifier(args.title);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
901
|
+
const resolved = resolvePageBuilderContent(args);
|
|
902
|
+
if (!resolved) {
|
|
903
|
+
throw new Error(
|
|
904
|
+
'Provide sections[], html, or content to create a CMS page'
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
let pbHtml = resolved.html;
|
|
908
|
+
const allowFullBleed =
|
|
909
|
+
args.allow_full_bleed === true || resolved.allow_full_bleed === true;
|
|
910
|
+
if (resolved.mode === 'sections' || resolved.mode === 'html') {
|
|
911
|
+
const repaired = repairPageBuilderContent(pbHtml, {
|
|
912
|
+
stripSvg: true,
|
|
913
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
914
|
+
});
|
|
915
|
+
pbHtml = repaired.content;
|
|
916
|
+
}
|
|
917
|
+
const validation = validatePageBuilderContent(pbHtml, {
|
|
918
|
+
allowFullBleed,
|
|
239
919
|
});
|
|
920
|
+
if (!validation.ok) {
|
|
921
|
+
throw new Error(
|
|
922
|
+
`Page Builder validation failed: ${validation.errors.join(' | ')}`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
240
925
|
if (!validatePageBuilderHtml(pbHtml)) {
|
|
241
|
-
throw new Error('Generated Page Builder HTML failed
|
|
926
|
+
throw new Error('Generated Page Builder HTML failed basic check');
|
|
242
927
|
}
|
|
243
928
|
const page = await magento.createPage({
|
|
244
929
|
title: args.title,
|
|
245
930
|
identifier,
|
|
246
931
|
content: pbHtml,
|
|
247
932
|
content_heading: '',
|
|
248
|
-
page_layout: args.page_layout || '
|
|
933
|
+
page_layout: args.page_layout || 'cms-full-width',
|
|
249
934
|
is_active: args.is_active !== false,
|
|
250
|
-
store_id: [0],
|
|
251
935
|
meta_title: args.title,
|
|
252
936
|
meta_description: '',
|
|
253
937
|
});
|
|
254
938
|
const id = page?.id ?? page?.page_id;
|
|
939
|
+
const storeIds = resolveStoreIds(args);
|
|
940
|
+
const storeAssign = assignCmsStores('page', Number(id), storeIds);
|
|
255
941
|
return toolOk({
|
|
256
942
|
result: 'created',
|
|
943
|
+
mode: resolved.mode,
|
|
944
|
+
used_types: resolved.used_types,
|
|
945
|
+
allow_full_bleed: allowFullBleed,
|
|
946
|
+
validation,
|
|
947
|
+
store_ids: storeIds,
|
|
948
|
+
store_assign: storeAssign,
|
|
257
949
|
...summarizeEntity(page, 'page', magento.pageEditUrl(id)),
|
|
258
950
|
full_content_length: pbHtml.length,
|
|
259
951
|
});
|
|
@@ -268,13 +960,31 @@ async function callTool(name, args = {}) {
|
|
|
268
960
|
generateIdentifier(title);
|
|
269
961
|
|
|
270
962
|
let content = existing.content;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
963
|
+
let mode = 'metadata_only';
|
|
964
|
+
let used_types = [];
|
|
965
|
+
let validation = null;
|
|
966
|
+
const resolved = resolvePageBuilderContent(args);
|
|
967
|
+
if (resolved) {
|
|
968
|
+
mode = resolved.mode;
|
|
969
|
+
used_types = resolved.used_types;
|
|
970
|
+
content = resolved.html;
|
|
971
|
+
const allowFullBleed =
|
|
972
|
+
args.allow_full_bleed === true || resolved.allow_full_bleed === true;
|
|
973
|
+
if (mode === 'sections' || mode === 'html') {
|
|
974
|
+
const repaired = repairPageBuilderContent(content, {
|
|
975
|
+
stripSvg: true,
|
|
976
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
977
|
+
});
|
|
978
|
+
content = repaired.content;
|
|
979
|
+
}
|
|
980
|
+
validation = validatePageBuilderContent(content, { allowFullBleed });
|
|
981
|
+
if (!validation.ok) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Page Builder validation failed: ${validation.errors.join(' | ')}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
276
986
|
if (!validatePageBuilderHtml(content)) {
|
|
277
|
-
throw new Error('Generated Page Builder HTML failed
|
|
987
|
+
throw new Error('Generated Page Builder HTML failed basic check');
|
|
278
988
|
}
|
|
279
989
|
}
|
|
280
990
|
|
|
@@ -283,48 +993,76 @@ async function callTool(name, args = {}) {
|
|
|
283
993
|
identifier,
|
|
284
994
|
content,
|
|
285
995
|
content_heading: existing.content_heading ?? '',
|
|
286
|
-
page_layout: args.page_layout || existing.page_layout || '
|
|
996
|
+
page_layout: args.page_layout || existing.page_layout || 'cms-full-width',
|
|
287
997
|
is_active:
|
|
288
|
-
args.is_active !== undefined
|
|
998
|
+
args.is_active !== undefined
|
|
999
|
+
? args.is_active
|
|
1000
|
+
: entityIsActive(existing),
|
|
289
1001
|
meta_title: existing.meta_title || title,
|
|
290
1002
|
meta_description: existing.meta_description || '',
|
|
291
|
-
store_id: existing.store_id || [0],
|
|
292
1003
|
});
|
|
293
1004
|
const id = page?.id ?? args.page_id;
|
|
1005
|
+
let storeAssign = null;
|
|
1006
|
+
let storeIds = null;
|
|
1007
|
+
if (args.store_ids !== undefined) {
|
|
1008
|
+
storeIds = resolveStoreIds(args);
|
|
1009
|
+
storeAssign = assignCmsStores('page', Number(id), storeIds);
|
|
1010
|
+
}
|
|
294
1011
|
return toolOk({
|
|
295
1012
|
result: 'updated',
|
|
1013
|
+
mode,
|
|
1014
|
+
used_types,
|
|
1015
|
+
validation,
|
|
1016
|
+
store_ids: storeIds,
|
|
1017
|
+
store_assign: storeAssign,
|
|
296
1018
|
...summarizeEntity(page, 'page', magento.pageEditUrl(id)),
|
|
297
1019
|
full_content_length: String(content || '').length,
|
|
298
1020
|
});
|
|
299
1021
|
}
|
|
300
|
-
case 'get_cms_block': {
|
|
301
|
-
const magento = client();
|
|
302
|
-
const block = await magento.getBlock(args.block_id);
|
|
303
|
-
return toolOk(
|
|
304
|
-
summarizeEntity(block, 'block', magento.blockEditUrl(block.id || args.block_id))
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
1022
|
case 'create_cms_block': {
|
|
308
1023
|
const magento = client();
|
|
309
1024
|
const identifier =
|
|
310
1025
|
(args.identifier || '').trim() || generateIdentifier(args.title);
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
1026
|
+
const resolved = resolvePageBuilderContent(args);
|
|
1027
|
+
if (!resolved) {
|
|
1028
|
+
throw new Error(
|
|
1029
|
+
'Provide sections[], html, or content to create a CMS block'
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
let pbHtml = resolved.html;
|
|
1033
|
+
const allowFullBleed =
|
|
1034
|
+
args.allow_full_bleed === true || resolved.allow_full_bleed === true;
|
|
1035
|
+
if (resolved.mode === 'sections' || resolved.mode === 'html') {
|
|
1036
|
+
const repaired = repairPageBuilderContent(pbHtml, {
|
|
1037
|
+
stripSvg: true,
|
|
1038
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
1039
|
+
});
|
|
1040
|
+
pbHtml = repaired.content;
|
|
1041
|
+
}
|
|
1042
|
+
const validation = validatePageBuilderContent(pbHtml, {
|
|
1043
|
+
allowFullBleed,
|
|
314
1044
|
});
|
|
315
|
-
if (!
|
|
316
|
-
throw new Error(
|
|
1045
|
+
if (!validation.ok) {
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`Page Builder validation failed: ${validation.errors.join(' | ')}`
|
|
1048
|
+
);
|
|
317
1049
|
}
|
|
318
1050
|
const block = await magento.createBlock({
|
|
319
1051
|
title: args.title,
|
|
320
1052
|
identifier,
|
|
321
1053
|
content: pbHtml,
|
|
322
1054
|
is_active: args.is_active !== false,
|
|
323
|
-
store_id: [0],
|
|
324
1055
|
});
|
|
325
1056
|
const id = block?.id ?? block?.block_id;
|
|
1057
|
+
const storeIds = resolveStoreIds(args);
|
|
1058
|
+
const storeAssign = assignCmsStores('block', Number(id), storeIds);
|
|
326
1059
|
return toolOk({
|
|
327
1060
|
result: 'created',
|
|
1061
|
+
mode: resolved.mode,
|
|
1062
|
+
used_types: resolved.used_types,
|
|
1063
|
+
validation,
|
|
1064
|
+
store_ids: storeIds,
|
|
1065
|
+
store_assign: storeAssign,
|
|
328
1066
|
...summarizeEntity(block, 'block', magento.blockEditUrl(id)),
|
|
329
1067
|
});
|
|
330
1068
|
}
|
|
@@ -338,13 +1076,28 @@ async function callTool(name, args = {}) {
|
|
|
338
1076
|
generateIdentifier(title);
|
|
339
1077
|
|
|
340
1078
|
let content = existing.content;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1079
|
+
let mode = 'metadata_only';
|
|
1080
|
+
let used_types = [];
|
|
1081
|
+
let validation = null;
|
|
1082
|
+
const resolved = resolvePageBuilderContent(args);
|
|
1083
|
+
if (resolved) {
|
|
1084
|
+
mode = resolved.mode;
|
|
1085
|
+
used_types = resolved.used_types;
|
|
1086
|
+
content = resolved.html;
|
|
1087
|
+
const allowFullBleed =
|
|
1088
|
+
args.allow_full_bleed === true || resolved.allow_full_bleed === true;
|
|
1089
|
+
if (mode === 'sections' || mode === 'html') {
|
|
1090
|
+
const repaired = repairPageBuilderContent(content, {
|
|
1091
|
+
stripSvg: true,
|
|
1092
|
+
fixFullBleed: allowFullBleed ? false : true,
|
|
1093
|
+
});
|
|
1094
|
+
content = repaired.content;
|
|
1095
|
+
}
|
|
1096
|
+
validation = validatePageBuilderContent(content, { allowFullBleed });
|
|
1097
|
+
if (!validation.ok) {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
`Page Builder validation failed: ${validation.errors.join(' | ')}`
|
|
1100
|
+
);
|
|
348
1101
|
}
|
|
349
1102
|
}
|
|
350
1103
|
|
|
@@ -353,27 +1106,25 @@ async function callTool(name, args = {}) {
|
|
|
353
1106
|
identifier,
|
|
354
1107
|
content,
|
|
355
1108
|
is_active:
|
|
356
|
-
args.is_active !== undefined ? args.is_active : existing
|
|
357
|
-
store_id: existing.store_id || [0],
|
|
1109
|
+
args.is_active !== undefined ? args.is_active : entityIsActive(existing),
|
|
358
1110
|
});
|
|
359
1111
|
const id = block?.id ?? args.block_id;
|
|
1112
|
+
let storeAssign = null;
|
|
1113
|
+
let storeIds = null;
|
|
1114
|
+
if (args.store_ids !== undefined) {
|
|
1115
|
+
storeIds = resolveStoreIds(args);
|
|
1116
|
+
storeAssign = assignCmsStores('block', Number(id), storeIds);
|
|
1117
|
+
}
|
|
360
1118
|
return toolOk({
|
|
361
1119
|
result: 'updated',
|
|
1120
|
+
mode,
|
|
1121
|
+
used_types,
|
|
1122
|
+
validation,
|
|
1123
|
+
store_ids: storeIds,
|
|
1124
|
+
store_assign: storeAssign,
|
|
362
1125
|
...summarizeEntity(block, 'block', magento.blockEditUrl(id)),
|
|
363
1126
|
});
|
|
364
1127
|
}
|
|
365
|
-
case 'list_section_types':
|
|
366
|
-
return toolOk({
|
|
367
|
-
section_types: [
|
|
368
|
-
{
|
|
369
|
-
type: 'simple_text',
|
|
370
|
-
description:
|
|
371
|
-
'Page Builder Row (contained) → Text (WYSIWYG). Default for pasted content.',
|
|
372
|
-
params: ['content', 'headline?', 'subheadline?'],
|
|
373
|
-
},
|
|
374
|
-
],
|
|
375
|
-
note: 'More types will be added as element templates are documented.',
|
|
376
|
-
});
|
|
377
1128
|
default:
|
|
378
1129
|
throw new Error(`Unknown tool: ${name}`);
|
|
379
1130
|
}
|