@noleemits/vision-builder-control-mcp 4.48.5 → 4.74.3
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/index.js +446 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -111,7 +111,7 @@ process.on('SIGINT', () => {
|
|
|
111
111
|
// CONFIG
|
|
112
112
|
// ================================================================
|
|
113
113
|
|
|
114
|
-
const VERSION = '4.
|
|
114
|
+
const VERSION = '4.74.3';
|
|
115
115
|
const MIN_PLUGIN_VERSION = '4.13.0'; // Minimum WP plugin version required by this MCP server
|
|
116
116
|
|
|
117
117
|
// ================================================================
|
|
@@ -2002,6 +2002,21 @@ function getToolDefinitions() {
|
|
|
2002
2002
|
description: 'Get all component style presets (saved overrides + resolved values with fallbacks). Shows which components have customizable styles.',
|
|
2003
2003
|
inputSchema: { type: 'object', properties: {} }
|
|
2004
2004
|
},
|
|
2005
|
+
{
|
|
2006
|
+
name: 'get_section_presets',
|
|
2007
|
+
description: 'List all available section layout presets (cols-2, cols-3, cols-4, sidebar-left, sidebar-right, split, stack). Returns preset key, label, description, supported engines (v3/v4), and child column count. Use these keys with the append_section preset param to scaffold gap-safe multi-column layouts without writing Elementor JSON. v3 = classic Container widget. v4 = atomic e-flexbox (Elementor 3.25+).',
|
|
2008
|
+
inputSchema: {
|
|
2009
|
+
type: 'object',
|
|
2010
|
+
properties: {
|
|
2011
|
+
engine: {
|
|
2012
|
+
type: 'string',
|
|
2013
|
+
enum: ['v3', 'v4'],
|
|
2014
|
+
description: 'Optional. Filter by engine. Omit to return all presets.',
|
|
2015
|
+
},
|
|
2016
|
+
},
|
|
2017
|
+
required: [],
|
|
2018
|
+
},
|
|
2019
|
+
},
|
|
2005
2020
|
{
|
|
2006
2021
|
name: 'set_component_presets',
|
|
2007
2022
|
description: 'Save style presets for a specific component. Empty values reset to defaults. Requires manage_options capability.',
|
|
@@ -2026,7 +2041,7 @@ function getToolDefinitions() {
|
|
|
2026
2041
|
type: 'object',
|
|
2027
2042
|
properties: {
|
|
2028
2043
|
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
2029
|
-
category: { type: 'string', description: 'Component category: heroes, intros, stats,
|
|
2044
|
+
category: { type: 'string', description: 'Component category: heroes, intros, stats, comparisons, ctas, teams, tabs, blocks, cards' },
|
|
2030
2045
|
name: { type: 'string', description: 'Component name (e.g., "hero-two-col-soft-blue", "stats-bar-4col-dark")' },
|
|
2031
2046
|
variables: { type: 'object', description: 'Variable values to populate (heading, description, buttons, etc.)' },
|
|
2032
2047
|
position: { type: 'string', description: 'Where to add: "append" (default), "prepend", or numeric index' },
|
|
@@ -2230,7 +2245,7 @@ function getToolDefinitions() {
|
|
|
2230
2245
|
},
|
|
2231
2246
|
{
|
|
2232
2247
|
name: 'update_element',
|
|
2233
|
-
description: 'Update any element\'s settings by its Elementor ID or path. Patch widget properties like hover_color, background_color, __globals__ overrides, text, link URLs, etc. Supports dot-notation for nested keys (e.g. "__globals__.hover_color"). Supports element_path as alternative to element_id (e.g. "sectionId/1/0" = section → child 1 → child 0). Use list_elements or export_page first to find the element ID and current settings. IMPORTANT: Prefer settings_json (JSON string) over settings (object) for reliable MCP transport. AUTO-INJECTED KEYS: background_background:"classic" auto-set when you set background_color; typography_typography:"custom" auto-set when you set font properties; flex_grow expands to all 3 required keys; grid column strings auto-wrapped in object format. ICON WIDGET: For view="default" use "icon_size" for dimensions. For view="stacked" or "framed" (circle/square background) use "size" instead — "icon_size" is silently ignored in stacked/framed mode. LOOP-GRID WIDGET: query settings use the prefix "post_query_" (post_query_post_type, post_query_orderby, post_query_order, post_query_posts_per_page) — NOT "posts_post_type". Setting "posts_post_type" is silently accepted but ignored. LOOP-CAROUSEL WIDGET: also uses "post_query_" prefix — NOT the older Elementor "query_" prefix. Both loop-grid and loop-carousel share the same query-control prefix; if you used "query_post_type" the write is silently accepted and ignored. Pull current settings via get_element first to confirm the prefix before patching. RACE CONDITION: when patching multiple elements on the SAME page in parallel, use batch_update_elements instead — parallel update_element calls can clobber each other. ATOMIC SCHEMA VALIDATION (v4.42.5+): writes that violate the Elementor V4 atomic schema are rejected up front (HTTP 400 invalid_atomic_setting) instead of silently corrupting the page. Currently enforced: e-heading.tag ∈ {h1..h6}, e-paragraph.tag ∈ {p, span}. If you need a non-heading visual element styled like an eyebrow, use e-paragraph (tag=p or span) — not e-heading with tag=div. NATIVE GRID/FLEX TIP: for container layout (grid columns, flex direction, gap), prefer set_container_layout — it produces WYSIWYG-editable native settings instead of forcing you to assemble the raw Elementor schema.',
|
|
2248
|
+
description: 'Update any element\'s settings by its Elementor ID or path. Patch widget properties like hover_color, background_color, __globals__ overrides, text, link URLs, etc. Supports dot-notation for nested keys (e.g. "__globals__.hover_color"). Supports element_path as alternative to element_id (e.g. "sectionId/1/0" = section → child 1 → child 0). Use list_elements or export_page first to find the element ID and current settings. IMPORTANT: Prefer settings_json (JSON string) over settings (object) for reliable MCP transport. AUTO-INJECTED KEYS: background_background:"classic" auto-set when you set background_color; typography_typography:"custom" auto-set when you set font properties; flex_grow expands to all 3 required keys; grid column strings auto-wrapped in object format. ICON WIDGET: For view="default" use "icon_size" for dimensions. For view="stacked" or "framed" (circle/square background) use "size" instead — "icon_size" is silently ignored in stacked/framed mode. LOOP-GRID WIDGET: query settings use the prefix "post_query_" (post_query_post_type, post_query_orderby, post_query_order, post_query_posts_per_page) — NOT "posts_post_type". Setting "posts_post_type" is silently accepted but ignored. LOOP-CAROUSEL WIDGET: also uses "post_query_" prefix — NOT the older Elementor "query_" prefix. Both loop-grid and loop-carousel share the same query-control prefix; if you used "query_post_type" the write is silently accepted and ignored. Pull current settings via get_element first to confirm the prefix before patching. RACE CONDITION: when patching multiple elements on the SAME page in parallel, use batch_update_elements instead — parallel update_element calls can clobber each other. ATOMIC SCHEMA VALIDATION (v4.42.5+): writes that violate the Elementor V4 atomic schema are rejected up front (HTTP 400 invalid_atomic_setting) instead of silently corrupting the page. Currently enforced: e-heading.tag ∈ {h1..h6}, e-paragraph.tag ∈ {p, span}. If you need a non-heading visual element styled like an eyebrow, use e-paragraph (tag=p or span) — not e-heading with tag=div. NATIVE GRID/FLEX TIP: for container layout (grid columns, flex direction, gap), prefer set_container_layout — it produces WYSIWYG-editable native settings instead of forcing you to assemble the raw Elementor schema. NOTE: On V4 atomic elements (e-flexbox, e-section, e-div-block, e-grid), visual props (background_color, padding, etc.) are written to settings but V4 renders from the styles array — they will not apply visually. The response will include a warnings[] array when this occurs. Use set_kit_global_css with [data-id="<id>"] as the workaround.',
|
|
2234
2249
|
inputSchema: {
|
|
2235
2250
|
type: 'object',
|
|
2236
2251
|
properties: {
|
|
@@ -2354,6 +2369,22 @@ function getToolDefinitions() {
|
|
|
2354
2369
|
required: ['source_page_id', 'element_id', 'target_page_id']
|
|
2355
2370
|
}
|
|
2356
2371
|
},
|
|
2372
|
+
{
|
|
2373
|
+
name: 'duplicate_children',
|
|
2374
|
+
description: 'Clone every child of a source container (deep clone + fresh Elementor IDs) and insert the clones into a target container. Defaults to the SAME container — exactly what a seamless CSS marquee/carousel loop needs (the track must hold two identical copies of its item set so it can translateX(-50%) and loop). Unlike copy_element (which only drops a clone at the page top level), this inserts INTO a nested container. Use list_elements to find the container ID. Pass times>1 to append multiple copies.',
|
|
2375
|
+
inputSchema: {
|
|
2376
|
+
type: 'object',
|
|
2377
|
+
properties: {
|
|
2378
|
+
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
2379
|
+
source_container_id: { type: 'string', description: 'Container whose children are cloned' },
|
|
2380
|
+
target_container_id: { type: 'string', description: 'Container to insert clones into. Defaults to source_container_id (duplicate in place).' },
|
|
2381
|
+
times: { type: 'number', description: 'Number of extra copies to append (default: 1)' },
|
|
2382
|
+
position: { type: 'string', description: '"append" (default) or "prepend"' },
|
|
2383
|
+
force: { type: 'boolean', description: 'Override edit locks (default: false)' }
|
|
2384
|
+
},
|
|
2385
|
+
required: ['page_id', 'source_container_id']
|
|
2386
|
+
}
|
|
2387
|
+
},
|
|
2357
2388
|
{
|
|
2358
2389
|
name: 'check_links',
|
|
2359
2390
|
description: 'Check all URLs on Elementor pages for broken links (404, 500, timeout). Results are cached per-URL for 24 hours — subsequent calls are instant for already-checked URLs. Use force_refresh=true to re-fetch everything.',
|
|
@@ -3082,7 +3113,7 @@ function getToolDefinitions() {
|
|
|
3082
3113
|
},
|
|
3083
3114
|
{
|
|
3084
3115
|
name: 'manage_redirects',
|
|
3085
|
-
description: 'Manage RankMath 301 redirects. Actions: "create" (add redirects), "list" (show all), "delete" (remove by ID). Source paths should NOT include
|
|
3116
|
+
description: 'Manage RankMath 301 redirects. Actions: "create" (add redirects), "list" (show all), "delete" (remove by ID). Source paths should NOT include any site subdirectory prefix (e.g. /old-slug/); destination paths SHOULD include the subdirectory if the site uses one (e.g. /subdir/new-slug/).',
|
|
3086
3117
|
inputSchema: {
|
|
3087
3118
|
type: 'object',
|
|
3088
3119
|
properties: {
|
|
@@ -3135,6 +3166,11 @@ function getToolDefinitions() {
|
|
|
3135
3166
|
description: 'Read-only WordPress database health audit. Reports: post revision count (+ top pages by revision), expired transient count, autoloaded options total size + top 10 largest, orphaned postmeta count. Includes actionable cleanup recommendations. No data is modified.',
|
|
3136
3167
|
inputSchema: { type: 'object', properties: {} }
|
|
3137
3168
|
},
|
|
3169
|
+
{
|
|
3170
|
+
name: 'audit_builder_footprint',
|
|
3171
|
+
description: 'Read-only builder/migration audit — scopes an Elementor → Gutenberg/Kadence migration in ONE call. Reports across ALL public post types: per-post-type split of elementor/gutenberg/classic; the complete Elementor rebuild worklist (every Elementor-built page/post/CPT item with section counts); ghost-data detection (items carrying _elementor_data but not flagged as Elementor); all elementor_library templates with their display conditions; orphaned-condition detection (conditions pointing at deleted posts); active theme and detected builders. No data is modified.',
|
|
3172
|
+
inputSchema: { type: 'object', properties: { post_types: { type: 'string', description: 'Optional comma-separated post type slugs to scope the breakdown (e.g. "post,page,services"). Default: all public types.' } } }
|
|
3173
|
+
},
|
|
3138
3174
|
{
|
|
3139
3175
|
name: 'audit_image_sizes',
|
|
3140
3176
|
description: 'Scan Elementor pages for images using oversized WordPress sizes ("full" or "large"). Reports element ID, widget type, current size, actual file dimensions, and recommended size. Use to identify images that could be replaced with smaller WP registered sizes (medium_large, medium) to reduce page weight.',
|
|
@@ -3597,17 +3633,19 @@ function getToolDefinitions() {
|
|
|
3597
3633
|
},
|
|
3598
3634
|
{
|
|
3599
3635
|
name: 'append_section',
|
|
3600
|
-
description: 'Append a section to an existing page or template
|
|
3636
|
+
description: 'Append a section to an existing page or template. Pass full section JSON OR use the preset param for instant gap-safe column layouts. PRESET SHORTHAND: pass preset="cols-3" (or cols-2, cols-4, sidebar-left, sidebar-right, split, stack) and the scaffold JSON is generated for you — no manual flex-grow JSON needed. The section param becomes optional when using preset; any settings in section are merged into the preset root (use it to set background_color, css_classes, _title, etc.). engine param selects v3 (classic Container) or v4 (atomic e-flexbox); auto-detected if omitted.',
|
|
3601
3637
|
inputSchema: {
|
|
3602
3638
|
type: 'object',
|
|
3603
3639
|
properties: {
|
|
3604
3640
|
target_id: { type: 'number', description: 'Page or template ID to append to' },
|
|
3605
|
-
section: { type: 'object', description: 'Full Elementor section object (must have elType and elements)' },
|
|
3641
|
+
section: { type: 'object', description: 'Full Elementor section object (must have elType and elements). Optional when preset is provided — only pass to override root settings (background, title, etc.).' },
|
|
3642
|
+
preset: { type: 'string', enum: ['cols-2', 'cols-3', 'cols-4', 'sidebar-left', 'sidebar-right', 'split', 'stack'], description: 'Layout preset name. When provided, section JSON is auto-generated. Columns are empty scaffolds — add content with add_element after.' },
|
|
3643
|
+
engine: { type: 'string', enum: ['v3', 'v4'], description: 'Elementor engine for preset. v3 = classic Container. v4 = atomic e-flexbox. Auto-detected from ELEMENTOR_VERSION if omitted.' },
|
|
3606
3644
|
position: { type: 'string', description: 'Where to place: "end" (default), "start", or numeric index' },
|
|
3607
3645
|
regenerate_ids: { type: 'boolean', description: 'Regenerate all element IDs to avoid collisions (default: true)' },
|
|
3608
3646
|
force: { type: 'boolean', description: 'Override edit locks (default: false)' }
|
|
3609
3647
|
},
|
|
3610
|
-
required: ['target_id'
|
|
3648
|
+
required: ['target_id']
|
|
3611
3649
|
}
|
|
3612
3650
|
},
|
|
3613
3651
|
{
|
|
@@ -3807,7 +3845,7 @@ function getToolDefinitions() {
|
|
|
3807
3845
|
properties: {
|
|
3808
3846
|
profile: {
|
|
3809
3847
|
type: 'object',
|
|
3810
|
-
description: 'Key-value pairs to update. Example: {"site_name": "
|
|
3848
|
+
description: 'Key-value pairs to update. Example: {"site_name": "Acme Inc.", "primary_color": "#2563EB"}'
|
|
3811
3849
|
}
|
|
3812
3850
|
},
|
|
3813
3851
|
required: ['profile']
|
|
@@ -3899,7 +3937,105 @@ function getToolDefinitions() {
|
|
|
3899
3937
|
applies_to: { type: 'string', enum: ['container', 'widget', 'heading', 'button', 'image', 'any'], description: 'Filter to classes that apply to this element type.' }
|
|
3900
3938
|
}
|
|
3901
3939
|
}
|
|
3902
|
-
}
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
name: 'list_element_classes',
|
|
3943
|
+
description: 'Show the CSS classes assigned to elements on a page. Handles both v4 atomic (settings.classes) and v3 legacy (settings.css_classes) formats. Also shows whether each class is registered in the Elementor Global Classes registry (so the editor can show it as a named chip). If element_id is omitted, scans all elements and returns only those that have at least one class.',
|
|
3944
|
+
inputSchema: {
|
|
3945
|
+
type: 'object',
|
|
3946
|
+
properties: {
|
|
3947
|
+
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
3948
|
+
element_id: { type: 'string', description: '(Optional) Specific element ID. If omitted, scans all elements on the page.' }
|
|
3949
|
+
},
|
|
3950
|
+
required: ['page_id']
|
|
3951
|
+
}
|
|
3952
|
+
},
|
|
3953
|
+
{
|
|
3954
|
+
name: 'repair_page_classes',
|
|
3955
|
+
description: 'Fix existing pages where element class values are stored as plain label strings instead of Elementor Global Classes registry IDs. Without this fix, the Elementor editor shows "local" chips instead of named class chips. Run once per page that was built before v4.66.1. Use dry_run: true to preview what would change before committing.',
|
|
3956
|
+
inputSchema: {
|
|
3957
|
+
type: 'object',
|
|
3958
|
+
properties: {
|
|
3959
|
+
page_id: { type: 'number', description: 'WordPress page ID to repair' },
|
|
3960
|
+
dry_run: { type: 'boolean', description: 'If true, reports what would change without writing (default: false)' }
|
|
3961
|
+
},
|
|
3962
|
+
required: ['page_id']
|
|
3963
|
+
}
|
|
3964
|
+
},
|
|
3965
|
+
{
|
|
3966
|
+
name: 'manage_element_classes',
|
|
3967
|
+
description: 'Add, remove, or replace CSS classes on an Elementor element. Auto-detects v4 atomic (settings.classes) vs v3 (settings.css_classes) format. Newly added classes are auto-registered as stubs in the Elementor Global Classes registry so the editor shows them as named chips rather than "local". Actions: "add" appends without duplicates, "remove" strips the named classes, "set" replaces all classes on the element.',
|
|
3968
|
+
inputSchema: {
|
|
3969
|
+
type: 'object',
|
|
3970
|
+
properties: {
|
|
3971
|
+
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
3972
|
+
element_id: { type: 'string', description: 'Elementor element ID to update' },
|
|
3973
|
+
action: { type: 'string', enum: ['add', 'remove', 'set'], description: '"add" appends without duplicates; "remove" strips the named classes; "set" replaces all classes' },
|
|
3974
|
+
classes: { type: 'array', items: { type: 'string' }, description: 'Class names (not registry IDs). e.g. ["nvbc-hero", "nvbc-band--dark"]' }
|
|
3975
|
+
},
|
|
3976
|
+
required: ['page_id', 'element_id', 'action', 'classes']
|
|
3977
|
+
}
|
|
3978
|
+
},
|
|
3979
|
+
{
|
|
3980
|
+
name: 'upsert_class',
|
|
3981
|
+
description: 'Define or update a site CSS class — stores metadata in the class registry AND writes the CSS body to the active kit\'s global CSS. Replaces the two-step workflow of patch_kit_global_css + manage_element_classes. The class appears in list_classes with source=[site]. Optional apply_to: attach the class to element IDs on specific pages in the same call. To update an existing class\'s CSS, call again with the same name — the kit CSS block is replaced, not appended. v4.69.0+.',
|
|
3982
|
+
inputSchema: {
|
|
3983
|
+
type: 'object',
|
|
3984
|
+
properties: {
|
|
3985
|
+
name: { type: 'string', description: 'CSS class name without leading dot. Lowercase, hyphens and underscores only. e.g. "ef-font-display"' },
|
|
3986
|
+
css: { type: 'string', description: 'CSS property declarations for the class body — no selector, no braces. e.g. "font-size: clamp(40px, 5vw, 72px); font-family: \'Cormorant Garamond\', serif;"' },
|
|
3987
|
+
description: { type: 'string', description: 'Human-readable description shown in list_classes output.' },
|
|
3988
|
+
category: { type: 'string', enum: ['hero', 'cards', 'layout', 'grid', 'spacing', 'typography', 'decorative', 'utility'], description: 'Category for list_classes grouping. Default: "utility".' },
|
|
3989
|
+
applies_to: { type: 'array', items: { type: 'string' }, description: 'Element types this class applies to. Default: ["any"].' },
|
|
3990
|
+
apply_to: {
|
|
3991
|
+
type: 'array',
|
|
3992
|
+
description: 'Optional: attach this class to these elements in the same call.',
|
|
3993
|
+
items: {
|
|
3994
|
+
type: 'object',
|
|
3995
|
+
properties: {
|
|
3996
|
+
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
3997
|
+
element_id: { type: 'string', description: 'Elementor element ID' }
|
|
3998
|
+
},
|
|
3999
|
+
required: ['page_id', 'element_id']
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
},
|
|
4003
|
+
required: ['name']
|
|
4004
|
+
}
|
|
4005
|
+
},
|
|
4006
|
+
{
|
|
4007
|
+
name: 'delete_class',
|
|
4008
|
+
description: 'Remove a site-custom class from the class registry AND remove its CSS block from the active kit\'s global CSS. Cannot delete plugin built-in classes (source=plugin). v4.69.0+.',
|
|
4009
|
+
inputSchema: {
|
|
4010
|
+
type: 'object',
|
|
4011
|
+
properties: {
|
|
4012
|
+
name: { type: 'string', description: 'CSS class name to remove (no leading dot).' }
|
|
4013
|
+
},
|
|
4014
|
+
required: ['name']
|
|
4015
|
+
}
|
|
4016
|
+
},
|
|
4017
|
+
{
|
|
4018
|
+
name: 'list_class_snapshots',
|
|
4019
|
+
description: 'List version-history snapshots of the CSS class system (registry + stylesheet). Each entry shows id, time, label, author, and whether it was auto-captured before a mutation. v4.74.0+.',
|
|
4020
|
+
inputSchema: { type: 'object', properties: {} }
|
|
4021
|
+
},
|
|
4022
|
+
{
|
|
4023
|
+
name: 'create_class_snapshot',
|
|
4024
|
+
description: 'Manually capture the current CSS class state (registry custom classes + stylesheet) as a restorable snapshot. No-op if nothing changed since the last snapshot. v4.74.0+.',
|
|
4025
|
+
inputSchema: {
|
|
4026
|
+
type: 'object',
|
|
4027
|
+
properties: { label: { type: 'string', description: 'Human label for this snapshot, e.g. "before ef-* migration".' } },
|
|
4028
|
+
}
|
|
4029
|
+
},
|
|
4030
|
+
{
|
|
4031
|
+
name: 'restore_class_snapshot',
|
|
4032
|
+
description: 'Restore the CSS class system to a previous snapshot (by id from list_class_snapshots). The current state is auto-snapshotted first, so restore is itself reversible. v4.74.0+.',
|
|
4033
|
+
inputSchema: {
|
|
4034
|
+
type: 'object',
|
|
4035
|
+
properties: { id: { type: 'string', description: 'Snapshot id from list_class_snapshots.' } },
|
|
4036
|
+
required: ['id']
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
3903
4039
|
];
|
|
3904
4040
|
}
|
|
3905
4041
|
|
|
@@ -4168,6 +4304,22 @@ async function handleToolCall(name, args) {
|
|
|
4168
4304
|
return ok(out);
|
|
4169
4305
|
}
|
|
4170
4306
|
|
|
4307
|
+
case 'get_section_presets': {
|
|
4308
|
+
try {
|
|
4309
|
+
const r = await apiCall('/section-presets');
|
|
4310
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
4311
|
+
const list = Array.isArray(r) ? r : [];
|
|
4312
|
+
if (list.length === 0) return ok('No section presets found.');
|
|
4313
|
+
const filtered = args.engine ? list.filter(p => p.engines.includes(args.engine)) : list;
|
|
4314
|
+
const lines = filtered.map(p =>
|
|
4315
|
+
`• ${p.key.padEnd(16)} ${p.label} (${p.engines.join('/')}, ${p.children} col${p.children !== 1 ? 's' : ''})\n ${p.description}`
|
|
4316
|
+
);
|
|
4317
|
+
return ok(`Available section layout presets:\n\n${lines.join('\n\n')}\n\nUse with: append_section(target_id, preset="<key>", engine="v3|v4")`);
|
|
4318
|
+
} catch (err) {
|
|
4319
|
+
return ok(`Failed: ${err.message}`);
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4171
4323
|
case 'set_component_presets': {
|
|
4172
4324
|
const r = await apiCall('/component-presets', 'POST', {
|
|
4173
4325
|
component_id: args.component_id,
|
|
@@ -4715,6 +4867,25 @@ async function handleToolCall(name, args) {
|
|
|
4715
4867
|
return ok(msg);
|
|
4716
4868
|
}
|
|
4717
4869
|
|
|
4870
|
+
case 'duplicate_children': {
|
|
4871
|
+
const body = {
|
|
4872
|
+
page_id: args.page_id,
|
|
4873
|
+
source_container_id: args.source_container_id,
|
|
4874
|
+
};
|
|
4875
|
+
if (args.target_container_id) body.target_container_id = args.target_container_id;
|
|
4876
|
+
if (args.times) body.times = args.times;
|
|
4877
|
+
if (args.position) body.position = args.position;
|
|
4878
|
+
if (args.force) body.force = true;
|
|
4879
|
+
const r = await apiCall('/duplicate-children', 'POST', body);
|
|
4880
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
4881
|
+
let msg = `Children duplicated!\n`;
|
|
4882
|
+
msg += `Source container: ${r.source_container_id} (${r.children_per_copy} children)\n`;
|
|
4883
|
+
msg += `Target container: ${r.target_container_id}\n`;
|
|
4884
|
+
msg += `Copies appended: ${r.times} → ${r.total_cloned} new elements (${r.position})\n`;
|
|
4885
|
+
msg += `New IDs: ${(r.new_ids || []).join(', ')}`;
|
|
4886
|
+
return ok(msg);
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4718
4889
|
case 'check_links': {
|
|
4719
4890
|
const params = new URLSearchParams();
|
|
4720
4891
|
if (args.page_id) params.set('page_id', args.page_id);
|
|
@@ -5672,6 +5843,70 @@ async function handleToolCall(name, args) {
|
|
|
5672
5843
|
return ok(out);
|
|
5673
5844
|
}
|
|
5674
5845
|
|
|
5846
|
+
case 'audit_builder_footprint': {
|
|
5847
|
+
const params = new URLSearchParams();
|
|
5848
|
+
if (args.post_types) params.set('post_types', args.post_types);
|
|
5849
|
+
const qs = params.toString() ? `?${params}` : '';
|
|
5850
|
+
const r = await apiCall(`/audit-builder-footprint${qs}`);
|
|
5851
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
5852
|
+
const s = r.summary;
|
|
5853
|
+
let out = `=== BUILDER FOOTPRINT / MIGRATION AUDIT ===\n`;
|
|
5854
|
+
out += `Active theme: ${r.active_theme.name} (template: ${r.active_theme.template})\n`;
|
|
5855
|
+
const activeBuilders = Object.entries(r.builders).filter(([, v]) => v).map(([k]) => k);
|
|
5856
|
+
out += `Builders detected: ${activeBuilders.join(', ')}\n`;
|
|
5857
|
+
out += `\nElementor items to rebuild: ${s.elementor_items_total}`;
|
|
5858
|
+
const byType = Object.entries(s.elementor_by_type || {}).map(([t, c]) => `${c} ${t}`).join(', ');
|
|
5859
|
+
if (byType) out += ` (${byType})`;
|
|
5860
|
+
out += `\nTemplates: ${s.templates_total} (${s.site_wide_templates} site-wide)\n`;
|
|
5861
|
+
out += `Ghost-data items: ${s.ghost_data_items} | Orphaned conditions: ${s.orphaned_conditions}\n`;
|
|
5862
|
+
|
|
5863
|
+
out += `\n--- Builder split by post type ---\n`;
|
|
5864
|
+
r.by_post_type.forEach(t => {
|
|
5865
|
+
out += ` ${t.label} (${t.post_type}): ${t.total} total — ${t.elementor} elementor / ${t.gutenberg} gutenberg / ${t.classic} classic\n`;
|
|
5866
|
+
});
|
|
5867
|
+
|
|
5868
|
+
if (r.elementor_items.length) {
|
|
5869
|
+
out += `\n--- Elementor rebuild worklist ---\n`;
|
|
5870
|
+
r.elementor_items.forEach(it => {
|
|
5871
|
+
out += ` [${it.id}] ${it.title} (${it.type}, ${it.status}) — ${it.sections} section(s)\n`;
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
if (r.ghost_data.length) {
|
|
5876
|
+
out += `\n--- Ghost Elementor data (converted, stale data left behind) ---\n`;
|
|
5877
|
+
r.ghost_data.forEach(g => {
|
|
5878
|
+
out += ` [${g.id}] ${g.title} (${g.type}) — ${g.sections} section(s) of stale data\n`;
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
if (r.templates.length) {
|
|
5883
|
+
out += `\n--- Elementor templates ---\n`;
|
|
5884
|
+
r.templates.forEach(tp => {
|
|
5885
|
+
const flags = [];
|
|
5886
|
+
if (tp.site_wide) flags.push('SITE-WIDE');
|
|
5887
|
+
if (tp.orphaned_conditions.length) flags.push(`${tp.orphaned_conditions.length} ORPHANED`);
|
|
5888
|
+
const flagStr = flags.length ? ` [${flags.join(', ')}]` : '';
|
|
5889
|
+
out += ` [${tp.id}] ${tp.title} (${tp.template_type})${flagStr}\n`;
|
|
5890
|
+
if (tp.conditions.length) out += ` conditions: ${tp.conditions.join('; ')}\n`;
|
|
5891
|
+
if (tp.orphaned_conditions.length) out += ` orphaned: ${tp.orphaned_conditions.join('; ')}\n`;
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
|
|
5895
|
+
out += `\n--- DB health ---\n`;
|
|
5896
|
+
out += ` Orphaned postmeta: ${r.db_health.orphaned_postmeta} | Revisions: ${r.db_health.revisions}\n`;
|
|
5897
|
+
if (r.db_health.elementor_autoload_options?.length) {
|
|
5898
|
+
out += ` Elementor autoloaded options:\n`;
|
|
5899
|
+
r.db_health.elementor_autoload_options.forEach(o => { out += ` ${o.size_kb} KB — ${o.name}\n`; });
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
if (r.recommendations?.length) {
|
|
5903
|
+
out += `\n--- Recommendations ---\n`;
|
|
5904
|
+
r.recommendations.forEach(rec => { out += ` • ${rec}\n`; });
|
|
5905
|
+
}
|
|
5906
|
+
out += `\nNote: ${r.note}`;
|
|
5907
|
+
return ok(out);
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5675
5910
|
case 'audit_image_sizes': {
|
|
5676
5911
|
const params = new URLSearchParams();
|
|
5677
5912
|
if (args.page_id) params.set('page_id', args.page_id);
|
|
@@ -6311,6 +6546,7 @@ async function handleToolCall(name, args) {
|
|
|
6311
6546
|
if (args.quality) body.quality = args.quality;
|
|
6312
6547
|
const r = await apiCall('/pixelvault/generate', 'POST', body);
|
|
6313
6548
|
if (r.error || r.code) return ok(`Generation failed: ${r.detail || r.message || r.error} [${r.category || ''}]`);
|
|
6549
|
+
if (!r.batch_id) return ok('Generation failed: PixelVault returned no batch_id. Cannot poll. Check PixelVault queue endpoint response.');
|
|
6314
6550
|
let out = `=== IMAGE GENERATION STARTED ===\n`;
|
|
6315
6551
|
out += `Batch ID: ${r.batch_id}\n`;
|
|
6316
6552
|
out += `Status: ${r.status || 'generating'}\n`;
|
|
@@ -7270,6 +7506,26 @@ async function handleToolCall(name, args) {
|
|
|
7270
7506
|
return ok(`Front page set to "${r.front_page_title}" (ID: ${r.front_page_id})${r.blog_page_id ? `\nBlog page: ${r.blog_page_id}` : ''}`);
|
|
7271
7507
|
}
|
|
7272
7508
|
|
|
7509
|
+
case 'list_class_snapshots': {
|
|
7510
|
+
const r = await apiCall('/class-snapshots');
|
|
7511
|
+
const snaps = r.snapshots || [];
|
|
7512
|
+
if (!snaps.length) return ok('No class snapshots yet.');
|
|
7513
|
+
let msg = `Class snapshots (newest first):\n`;
|
|
7514
|
+
for (const s of snaps) {
|
|
7515
|
+
const when = new Date(s.time * 1000).toISOString().replace('T', ' ').slice(0, 16);
|
|
7516
|
+
msg += ` ${s.id}\n ${when} · ${s.label} · by ${s.author}${s.auto ? ' · auto' : ''}\n ${s.custom_count} custom classes${s.has_stylesheet ? ' · custom stylesheet' : ' · default stylesheet'}\n`;
|
|
7517
|
+
}
|
|
7518
|
+
return ok(msg);
|
|
7519
|
+
}
|
|
7520
|
+
case 'create_class_snapshot': {
|
|
7521
|
+
const r = await apiCall('/class-snapshots', 'POST', { label: args.label || '' });
|
|
7522
|
+
return ok(r.created ? `Snapshot created: ${r.id}` : `Not created: ${r.reason || 'unknown'}`);
|
|
7523
|
+
}
|
|
7524
|
+
case 'restore_class_snapshot': {
|
|
7525
|
+
const r = await apiCall(`/class-snapshots/${encodeURIComponent(args.id)}/restore`, 'POST');
|
|
7526
|
+
return ok(r.restored ? `Restored snapshot ${args.id}. (Prior state was auto-snapshotted first.)` : `Restore failed: ${r.message || r.code || 'unknown'}`);
|
|
7527
|
+
}
|
|
7528
|
+
|
|
7273
7529
|
case 'list_classes': {
|
|
7274
7530
|
const params = new URLSearchParams();
|
|
7275
7531
|
if (args.category) params.set('category', args.category);
|
|
@@ -7294,10 +7550,191 @@ async function handleToolCall(name, args) {
|
|
|
7294
7550
|
msg += ` .${c.name} ${src}\n`;
|
|
7295
7551
|
msg += ` ${c.description}\n`;
|
|
7296
7552
|
msg += ` Applies to: ${tags}\n`;
|
|
7553
|
+
if (c.source === 'site' && c.css) {
|
|
7554
|
+
const preview = c.css.split('\n')[0].trim();
|
|
7555
|
+
msg += ` CSS: ${preview}${c.css.includes('\n') ? ' …' : ''}\n`;
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
msg += '\n';
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
return ok(msg);
|
|
7562
|
+
}
|
|
7563
|
+
|
|
7564
|
+
case 'upsert_class': {
|
|
7565
|
+
// 1. Save class definition to registry (with optional css field)
|
|
7566
|
+
const regBody = {
|
|
7567
|
+
name: args.name,
|
|
7568
|
+
description: args.description || '',
|
|
7569
|
+
category: args.category || 'utility',
|
|
7570
|
+
applies_to: args.applies_to || ['any'],
|
|
7571
|
+
};
|
|
7572
|
+
if (args.css) regBody.css = args.css;
|
|
7573
|
+
|
|
7574
|
+
const r = await apiCall('/class-registry/custom', 'POST', regBody);
|
|
7575
|
+
if (r.error || r.code) return ok(`Failed to save class definition: ${r.message || r.error}`);
|
|
7576
|
+
|
|
7577
|
+
let msg = `Class .${args.name} saved to registry.\n`;
|
|
7578
|
+
|
|
7579
|
+
// 2. Write CSS block to kit global CSS when css is provided
|
|
7580
|
+
if (args.css) {
|
|
7581
|
+
const startMarker = `/* nvbc-class: .${args.name} */`;
|
|
7582
|
+
const endMarker = `/* /nvbc-class: .${args.name} */`;
|
|
7583
|
+
const cssBlock = `${startMarker}\n.${args.name} {\n ${args.css.trim()}\n}\n${endMarker}`;
|
|
7584
|
+
|
|
7585
|
+
const kitCss = await apiCall('/kit-global-css');
|
|
7586
|
+
const existing = kitCss.css || '';
|
|
7587
|
+
|
|
7588
|
+
const escStart = startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7589
|
+
const escEnd = endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7590
|
+
const blockRe = new RegExp(escStart + '[\\s\\S]*?' + escEnd);
|
|
7591
|
+
|
|
7592
|
+
const newCss = blockRe.test(existing)
|
|
7593
|
+
? existing.replace(blockRe, cssBlock)
|
|
7594
|
+
: (existing.trimEnd() + '\n\n' + cssBlock + '\n');
|
|
7595
|
+
|
|
7596
|
+
const cssResult = await apiCall('/kit-global-css', 'POST', { css: newCss });
|
|
7597
|
+
msg += cssResult.success
|
|
7598
|
+
? `CSS block written to kit global CSS for .${args.name}.\n`
|
|
7599
|
+
: `Warning: class metadata saved but kit CSS write failed: ${cssResult.message || 'unknown'}\n`;
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7602
|
+
// 3. Attach to elements if apply_to provided
|
|
7603
|
+
if (args.apply_to?.length) {
|
|
7604
|
+
for (const target of args.apply_to) {
|
|
7605
|
+
const r2 = await apiCall(`/pages/${target.page_id}/element-classes`, 'POST', {
|
|
7606
|
+
element_id: target.element_id,
|
|
7607
|
+
action: 'add',
|
|
7608
|
+
classes: [args.name],
|
|
7609
|
+
});
|
|
7610
|
+
msg += r2.success !== false
|
|
7611
|
+
? `Added .${args.name} to element ${target.element_id} on page ${target.page_id}.\n`
|
|
7612
|
+
: `Failed to add to element ${target.element_id} on page ${target.page_id}: ${r2.message || 'unknown'}\n`;
|
|
7297
7613
|
}
|
|
7614
|
+
}
|
|
7615
|
+
|
|
7616
|
+
return ok(msg);
|
|
7617
|
+
}
|
|
7618
|
+
|
|
7619
|
+
case 'delete_class': {
|
|
7620
|
+
// 1. Remove from registry
|
|
7621
|
+
const delResult = await apiCall(`/class-registry/custom/${encodeURIComponent(args.name)}`, 'DELETE');
|
|
7622
|
+
if (delResult.code || delResult.error) {
|
|
7623
|
+
return ok(`Failed to remove class from registry: ${delResult.message || delResult.error}`);
|
|
7624
|
+
}
|
|
7625
|
+
|
|
7626
|
+
let msg = `Class .${args.name} removed from registry.\n`;
|
|
7627
|
+
|
|
7628
|
+
// 2. Remove CSS block from kit global CSS
|
|
7629
|
+
const startMarker = `/* nvbc-class: .${args.name} */`;
|
|
7630
|
+
const endMarker = `/* /nvbc-class: .${args.name} */`;
|
|
7631
|
+
const escStart = startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7632
|
+
const escEnd = endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7633
|
+
const blockRe = new RegExp('\\n*' + escStart + '[\\s\\S]*?' + escEnd + '\\n*');
|
|
7634
|
+
|
|
7635
|
+
const kitCss = await apiCall('/kit-global-css');
|
|
7636
|
+
const existing = kitCss.css || '';
|
|
7637
|
+
|
|
7638
|
+
if (blockRe.test(existing)) {
|
|
7639
|
+
const newCss = existing.replace(blockRe, '\n').replace(/\n{3,}/g, '\n\n');
|
|
7640
|
+
const cssResult = await apiCall('/kit-global-css', 'POST', { css: newCss });
|
|
7641
|
+
msg += cssResult.success
|
|
7642
|
+
? `CSS block for .${args.name} removed from kit global CSS.\n`
|
|
7643
|
+
: `Warning: registry entry deleted but kit CSS removal failed: ${cssResult.message || 'unknown'}\n`;
|
|
7644
|
+
} else {
|
|
7645
|
+
msg += `No managed CSS block found for .${args.name} in kit global CSS (may have been set manually — remove it there if needed).\n`;
|
|
7646
|
+
}
|
|
7647
|
+
|
|
7648
|
+
return ok(msg);
|
|
7649
|
+
}
|
|
7650
|
+
|
|
7651
|
+
case 'repair_page_classes': {
|
|
7652
|
+
const r = await apiCall(`/pages/${args.page_id}/repair-element-classes`, 'POST', {
|
|
7653
|
+
dry_run: args.dry_run ?? false,
|
|
7654
|
+
});
|
|
7655
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
7656
|
+
|
|
7657
|
+
let msg = `${r.dry_run ? '[DRY RUN] ' : ''}Page ${r.page_id} — ${r.message}\n`;
|
|
7658
|
+
if (r.classes?.length) {
|
|
7659
|
+
msg += `\nClasses affected:\n`;
|
|
7660
|
+
for (const cls of r.classes) msg += ` .${cls}\n`;
|
|
7661
|
+
}
|
|
7662
|
+
return ok(msg);
|
|
7663
|
+
}
|
|
7664
|
+
|
|
7665
|
+
case 'list_element_classes': {
|
|
7666
|
+
const params = new URLSearchParams();
|
|
7667
|
+
if (args.element_id) params.set('element_id', args.element_id);
|
|
7668
|
+
|
|
7669
|
+
const r = await apiCall(`/pages/${args.page_id}/element-classes?${params}`);
|
|
7670
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
7671
|
+
|
|
7672
|
+
if (args.element_id) {
|
|
7673
|
+
const el = r.element;
|
|
7674
|
+
let msg = `=== Element: ${el.element_id} ===\n`;
|
|
7675
|
+
msg += `Type: ${el.elType}${el.widgetType ? '/' + el.widgetType : ''}`;
|
|
7676
|
+
if (el.title) msg += ` "${el.title}"`;
|
|
7298
7677
|
msg += '\n';
|
|
7678
|
+
|
|
7679
|
+
if (el.v4_classes.length) {
|
|
7680
|
+
msg += `\nV4 Atomic Classes (${el.v4_classes.length}):\n`;
|
|
7681
|
+
for (const cls of el.v4_classes) msg += ` .${cls}\n`;
|
|
7682
|
+
}
|
|
7683
|
+
if (el.v3_classes.length) {
|
|
7684
|
+
msg += `\nV3 Classes (${el.v3_classes.length}):\n`;
|
|
7685
|
+
for (const cls of el.v3_classes) msg += ` .${cls}\n`;
|
|
7686
|
+
}
|
|
7687
|
+
if (!el.v4_classes.length && !el.v3_classes.length) {
|
|
7688
|
+
msg += '\nNo classes assigned.\n';
|
|
7689
|
+
}
|
|
7690
|
+
if (el.registered.length) {
|
|
7691
|
+
msg += `\nRegistered in Global Classes:\n`;
|
|
7692
|
+
for (const reg of el.registered) {
|
|
7693
|
+
msg += ` .${reg.label} [${reg.id}]${reg.has_css ? ' (has CSS)' : ' (stub only)'}\n`;
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
if (el.unregistered.length) {
|
|
7697
|
+
msg += `\nNot in Global Classes registry (no editor chip):\n`;
|
|
7698
|
+
for (const cls of el.unregistered) msg += ` .${cls}\n`;
|
|
7699
|
+
}
|
|
7700
|
+
return ok(msg);
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7703
|
+
// Page scan
|
|
7704
|
+
if (!r.elements?.length) {
|
|
7705
|
+
return ok(`No elements with classes found on page ${args.page_id}.`);
|
|
7706
|
+
}
|
|
7707
|
+
|
|
7708
|
+
let msg = `=== Element Classes — Page ${args.page_id} (${r.count} elements with classes) ===\n\n`;
|
|
7709
|
+
for (const el of r.elements) {
|
|
7710
|
+
const all = [...(el.v4_classes || []), ...(el.v3_classes || [])];
|
|
7711
|
+
const typeStr = el.elType + (el.widgetType ? '/' + el.widgetType : '');
|
|
7712
|
+
const titleStr = el.title ? ` "${el.title}"` : '';
|
|
7713
|
+
msg += `${el.element_id} [${typeStr}]${titleStr}\n`;
|
|
7714
|
+
msg += ` ${all.join(' ')}\n`;
|
|
7299
7715
|
}
|
|
7716
|
+
return ok(msg);
|
|
7717
|
+
}
|
|
7718
|
+
|
|
7719
|
+
case 'manage_element_classes': {
|
|
7720
|
+
const r = await apiCall(`/pages/${args.page_id}/element-classes`, 'POST', {
|
|
7721
|
+
element_id: args.element_id,
|
|
7722
|
+
action: args.action,
|
|
7723
|
+
classes: args.classes,
|
|
7724
|
+
});
|
|
7725
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
7300
7726
|
|
|
7727
|
+
const el = r.element;
|
|
7728
|
+
const all = el ? [...(el.v4_classes || []), ...(el.v3_classes || [])] : r.classes;
|
|
7729
|
+
|
|
7730
|
+
let msg = `Classes ${r.action}d on ${r.element_id} (${r.format})\n`;
|
|
7731
|
+
msg += `Classes now: ${all.length ? all.join(', ') : '(none)'}\n`;
|
|
7732
|
+
if (el?.registered?.length) {
|
|
7733
|
+
msg += `Registered: ${el.registered.map(x => x.label).join(', ')}\n`;
|
|
7734
|
+
}
|
|
7735
|
+
if (el?.unregistered?.length) {
|
|
7736
|
+
msg += `Auto-registered stubs: ${el.unregistered.join(', ')}\n`;
|
|
7737
|
+
}
|
|
7301
7738
|
return ok(msg);
|
|
7302
7739
|
}
|
|
7303
7740
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noleemits/vision-builder-control-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.74.3",
|
|
4
4
|
"description": "Vision Builder Control MCP server - design token-driven page builder tools for WordPress/Elementor websites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|