@noleemits/vision-builder-control-mcp 4.109.3 → 4.114.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/index.js +193 -28
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -111,9 +111,14 @@ process.on('SIGINT', () => {
|
|
|
111
111
|
// CONFIG
|
|
112
112
|
// ================================================================
|
|
113
113
|
|
|
114
|
-
const VERSION = '4.
|
|
114
|
+
const VERSION = '4.114.0';
|
|
115
115
|
const MIN_PLUGIN_VERSION = '4.13.0'; // Minimum WP plugin version required by this MCP server
|
|
116
116
|
|
|
117
|
+
// v4.110.0: shared styling philosophy appended to every build/style tool so the
|
|
118
|
+
// model is reminded, at the point of use, to use the framework the plugin already
|
|
119
|
+
// ships instead of inventing CSS. The decision is by CATEGORY, not a blanket order.
|
|
120
|
+
const CSS_PHILOSOPHY = ' ── STYLING RULE (we are NOT looking for shortcuts): SPACING/padding → apply the shipped `nvbc-space-*` clamp class (native padding has no clamp; the class IS the framework default). COLOR → bind the saved Elementor GLOBAL (never a hardcoded hex, never a color class). LAYOUT (grid columns, flex direction, gap, background, hover, border) → NATIVE container settings (e.g. set_container_layout) so the client can edit it in the UI. FLUID TYPE size → the shipped `nvbc-font-*` clamp class. IRREDUCIBLE decoration only (pseudo-elements, image crop, keyframes) → register a class via `upsert_class` (LAST resort). NEVER write ad-hoc CSS into Elementor custom_css / Site Settings → Custom CSS. Where EL4 ATOMIC elements are present, prefer atomic elements (shipped classes apply via the class array). Reuse list_classes + get_design_tokens before creating anything new.';
|
|
121
|
+
|
|
117
122
|
// ================================================================
|
|
118
123
|
// PARAMETER HELPERS
|
|
119
124
|
// ================================================================
|
|
@@ -2213,7 +2218,7 @@ function getToolDefinitions() {
|
|
|
2213
2218
|
},
|
|
2214
2219
|
{
|
|
2215
2220
|
name: 'set_container_layout',
|
|
2216
|
-
description: 'Set a container\'s layout (flex/grid) from a clean intent — prefer this over raw update_element when you want WYSIWYG-editable native Elementor layout instead of custom_css workarounds. Translates {mode, columns, gap, responsive} into the full Elementor schema (container_type, grid_columns_grid object, gap object shape, _tablet/_mobile variants, etc.). Modes: "grid" (uses Elementor native CSS Grid — best for card layouts), "flex-row" (horizontal flex), "flex-column" (vertical stack). Defaults: gap=24px, responsive=auto (tablet halves columns, mobile = 1 col). Pass responsive: false to skip responsive variants. Only operates on containers (rejected with invalid_target if you target a widget). The resulting layout shows up in Elementor\'s Layout panel as if a human set it, so non-technical editors can modify it through the UI. v4.44.0+. WIDTH SEMANTICS (v4.86.0+): containers default to content_width:"full" — no boxed `.e-con-inner` wrapper, flex-row children lay out side-by-side, and `.your-class > .e-con` CSS hits direct children. Pass content_width:"boxed" for a centered max-width container; combine with boxed_width to set the cap (Elementor\'s default cap is 1140px when unset). Common gotcha: a boxed container with a child carrying _element_custom_width in px greater than the boxed cap will overflow at every viewport — pre-flight with validate_section, which now flags this (child_width_exceeds_parent, flex_row_fixed_widths_overflow). For atomic v4 e-flexbox, width is set via a different settings shape — use add_element preset:"cols-N" for ready-made full-width column rows.',
|
|
2221
|
+
description: 'Set a container\'s layout (flex/grid) from a clean intent — prefer this over raw update_element when you want WYSIWYG-editable native Elementor layout instead of custom_css workarounds. Translates {mode, columns, gap, responsive} into the full Elementor schema (container_type, grid_columns_grid object, gap object shape, _tablet/_mobile variants, etc.). Modes: "grid" (uses Elementor native CSS Grid — best for card layouts), "flex-row" (horizontal flex), "flex-column" (vertical stack). Defaults: gap=24px, responsive=auto (tablet halves columns, mobile = 1 col). Pass responsive: false to skip responsive variants. Only operates on containers (rejected with invalid_target if you target a widget). The resulting layout shows up in Elementor\'s Layout panel as if a human set it, so non-technical editors can modify it through the UI. v4.44.0+. WIDTH SEMANTICS (v4.86.0+): containers default to content_width:"full" — no boxed `.e-con-inner` wrapper, flex-row children lay out side-by-side, and `.your-class > .e-con` CSS hits direct children. Pass content_width:"boxed" for a centered max-width container; combine with boxed_width to set the cap (Elementor\'s default cap is 1140px when unset). Common gotcha: a boxed container with a child carrying _element_custom_width in px greater than the boxed cap will overflow at every viewport — pre-flight with validate_section, which now flags this (child_width_exceeds_parent, flex_row_fixed_widths_overflow). For atomic v4 e-flexbox, width is set via a different settings shape — use add_element preset:"cols-N" for ready-made full-width column rows.' + CSS_PHILOSOPHY,
|
|
2217
2222
|
inputSchema: {
|
|
2218
2223
|
type: 'object',
|
|
2219
2224
|
properties: {
|
|
@@ -2259,7 +2264,7 @@ function getToolDefinitions() {
|
|
|
2259
2264
|
},
|
|
2260
2265
|
{
|
|
2261
2266
|
name: 'update_element',
|
|
2262
|
-
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.',
|
|
2267
|
+
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.' + CSS_PHILOSOPHY,
|
|
2263
2268
|
inputSchema: {
|
|
2264
2269
|
type: 'object',
|
|
2265
2270
|
properties: {
|
|
@@ -2275,7 +2280,8 @@ function getToolDefinitions() {
|
|
|
2275
2280
|
type: 'string',
|
|
2276
2281
|
description: 'Settings as a JSON string (preferred over settings object for reliable MCP transport). Example: \'{"hover_color":"#FFFFFF","__globals__":{"hover_color":""}}\''
|
|
2277
2282
|
},
|
|
2278
|
-
force: { type: 'boolean', description: 'Override edit locks (default: false)' }
|
|
2283
|
+
force: { type: 'boolean', description: 'Override edit locks (default: false)' },
|
|
2284
|
+
confirm: { type: 'boolean', description: 'Required only when setting custom_css on the element (ad-hoc CSS). Without confirm:true that write is REFUSED with guidance — prefer a class via upsert_class. All other settings are unaffected.' }
|
|
2279
2285
|
},
|
|
2280
2286
|
required: ['page_id']
|
|
2281
2287
|
}
|
|
@@ -2458,7 +2464,7 @@ function getToolDefinitions() {
|
|
|
2458
2464
|
},
|
|
2459
2465
|
{
|
|
2460
2466
|
name: 'create_post',
|
|
2461
|
-
description: 'Create a WordPress post, page, or CPT. Supports content, excerpt, taxonomies, featured image, parent linking (by slug or ID), Elementor setup, and initial RankMath SEO data.',
|
|
2467
|
+
description: 'Create a WordPress post, page, or CPT. Supports content, excerpt, taxonomies, featured image, parent linking (by slug or ID), an explicit publish date (for migrations), Elementor setup, and initial RankMath SEO data.',
|
|
2462
2468
|
inputSchema: {
|
|
2463
2469
|
type: 'object',
|
|
2464
2470
|
properties: {
|
|
@@ -2467,6 +2473,8 @@ function getToolDefinitions() {
|
|
|
2467
2473
|
content: { type: 'string', description: 'Post content (block HTML or plain text)' },
|
|
2468
2474
|
excerpt: { type: 'string', description: 'Post excerpt' },
|
|
2469
2475
|
status: { type: 'string', enum: ['draft', 'publish', 'private', 'pending'], description: 'Post status (default: draft)' },
|
|
2476
|
+
date: { type: 'string', description: 'Explicit publish date in site-local time, "YYYY-MM-DD HH:MM:SS" (or any strtotime-parseable string). Use to PRESERVE original dates when migrating posts — omit and the post gets the current date.' },
|
|
2477
|
+
date_gmt: { type: 'string', description: 'Optional explicit UTC date. If omitted, derived from `date` via the site timezone.' },
|
|
2470
2478
|
slug: { type: 'string', description: 'URL slug (auto-generated if omitted)' },
|
|
2471
2479
|
parent: { type: 'number', description: 'Parent post ID (for hierarchical types). Use this OR parent_slug.' },
|
|
2472
2480
|
parent_slug: { type: 'string', description: 'Parent slug — looked up against the same post_type. Saves a follow-up update_post call.' },
|
|
@@ -2771,16 +2779,27 @@ function getToolDefinitions() {
|
|
|
2771
2779
|
},
|
|
2772
2780
|
{
|
|
2773
2781
|
name: 'display_conditions',
|
|
2774
|
-
description: 'Get or set Elementor Theme Builder display conditions on a template (header, footer, single, archive). Common conditions: "include/general" (entire site), "include/singular/page" (all pages), "include/singular/post" (all posts), "include/archive" (all archives).',
|
|
2782
|
+
description: 'Get or set Elementor Theme Builder display conditions on a template (header, footer, single, archive). Common conditions: "include/general" (entire site), "include/singular/page" (all pages), "include/singular/post" (all posts), "include/archive" (all archives), "include/singular/{cpt}", "include/archive/{cpt}_archive". Conditions are VALIDATED against Elementor\'s registered conditions — a typo\'d slug returns condition_warnings (advisory) instead of silently no-op\'ing; pass strict:true to hard-refuse. Run list_display_conditions for the exact valid strings. On set, site caches are purged and a ?nocache verify hint is returned.',
|
|
2775
2783
|
inputSchema: {
|
|
2776
2784
|
type: 'object',
|
|
2777
2785
|
properties: {
|
|
2778
2786
|
template_id: { type: 'number', description: 'Template ID (from list_templates)' },
|
|
2779
|
-
conditions: { type: 'array', items: { type: 'string' }, description: 'Array of condition strings. Omit to read current conditions.' }
|
|
2787
|
+
conditions: { type: 'array', items: { type: 'string' }, description: 'Array of condition strings. Omit to read current conditions.' },
|
|
2788
|
+
strict: { type: 'boolean', description: 'If true, refuse the write when any condition does not match a registered Elementor condition. Default false (persist + warn).' }
|
|
2780
2789
|
},
|
|
2781
2790
|
required: ['template_id']
|
|
2782
2791
|
}
|
|
2783
2792
|
},
|
|
2793
|
+
{
|
|
2794
|
+
name: 'list_display_conditions',
|
|
2795
|
+
description: 'List the EXACT valid Theme Builder display-condition strings registered on this site (Elementor Pro), optionally filtered by post_type substring. Use before display_conditions to avoid silent no-ops — e.g. a CPT archive is "include/archive/{cpt}_archive", not "include/archive/{cpt}". Returns a fallback note when Pro is unavailable.',
|
|
2796
|
+
inputSchema: {
|
|
2797
|
+
type: 'object',
|
|
2798
|
+
properties: {
|
|
2799
|
+
post_type: { type: 'string', description: 'Optional substring filter (e.g. "team_member") to narrow to one type\'s singular + archive conditions.' }
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
},
|
|
2784
2803
|
|
|
2785
2804
|
// ── Nav menus (v4.29.0) ──
|
|
2786
2805
|
{
|
|
@@ -2888,7 +2907,7 @@ function getToolDefinitions() {
|
|
|
2888
2907
|
},
|
|
2889
2908
|
{
|
|
2890
2909
|
name: 'migrate_extracted_md',
|
|
2891
|
-
description: 'One-shot migration: take an extract-format markdown chunk and create or update a CPT post with cleaned HTML body, featured image, parent linking, and RankMath SEO. Idempotent: re-running with the same slug+post_type updates instead of duplicating. Pass md_content (preferred) or md_path. Use hero_basename to look up the featured image by filename instead of attachment ID.',
|
|
2910
|
+
description: 'One-shot migration: take an extract-format OR standard markdown chunk and create or update a CPT post with cleaned HTML body, featured image, parent linking, and RankMath SEO. Idempotent: re-running with the same slug+post_type updates instead of duplicating. md_content MUST be a STRING (markdown/plain text/clean HTML) — passing an array/object is now rejected with an error (it previously produced a silent "<p>Array</p>" corrupt write). Pass md_content (preferred) or md_path (string file path). Use hero_basename to look up the featured image by filename instead of attachment ID. For plain prose, ordinary markdown works; extract-format adds frontmatter blocks (## H1, **Meta description:**, etc.) that are parsed into title/SEO and stripped from the body.',
|
|
2892
2911
|
inputSchema: {
|
|
2893
2912
|
type: 'object',
|
|
2894
2913
|
properties: {
|
|
@@ -3073,7 +3092,7 @@ function getToolDefinitions() {
|
|
|
3073
3092
|
// ── Element Operations (v3.6.0) ──
|
|
3074
3093
|
{
|
|
3075
3094
|
name: 'add_element',
|
|
3076
|
-
description: 'Add a widget or container into a parent container on a page. Pass the full Elementor element JSON (elType, widgetType, settings, etc.). If no ID is provided, one will be auto-generated. Use position to insert at a specific index. NESTED COLUMN PRESET (v4.86.0): instead of element, pass preset="cols-3" (or cols-2, cols-4, sidebar-left, sidebar-right, split, stack) to drop a gap-safe column row AS A CHILD of parent_id. The scaffold ships content_width:full on the row AND every column, so no boxed `.e-con-inner` wrapper is injected and flex-row children never stack — this is the reusable fix for multi-column rows inside an existing section. Columns are empty; fill them with further add_element calls. GUARDRAIL: classic Elementor section/column elType is rejected (use containers); pass legacy:true to override.',
|
|
3095
|
+
description: 'Add a widget or container into a parent container on a page. Pass the full Elementor element JSON (elType, widgetType, settings, etc.). If no ID is provided, one will be auto-generated. Use position to insert at a specific index. NESTED COLUMN PRESET (v4.86.0): instead of element, pass preset="cols-3" (or cols-2, cols-4, sidebar-left, sidebar-right, split, stack) to drop a gap-safe column row AS A CHILD of parent_id. The scaffold ships content_width:full on the row AND every column, so no boxed `.e-con-inner` wrapper is injected and flex-row children never stack — this is the reusable fix for multi-column rows inside an existing section. Columns are empty; fill them with further add_element calls. GUARDRAIL: classic Elementor section/column elType is rejected (use containers); pass legacy:true to override. DYNAMIC CONTAINER LINK: Elementor does NOT render a clickable <a> for a container whose link is a dynamic tag (e.g. post-url) — static container links work, but for a dynamic clickable card put the post-url link on a heading/button widget inside the card (you\'ll get a warning if you set a dynamic container link).' + CSS_PHILOSOPHY,
|
|
3077
3096
|
inputSchema: {
|
|
3078
3097
|
type: 'object',
|
|
3079
3098
|
properties: {
|
|
@@ -3089,6 +3108,24 @@ function getToolDefinitions() {
|
|
|
3089
3108
|
required: ['page_id', 'parent_id']
|
|
3090
3109
|
}
|
|
3091
3110
|
},
|
|
3111
|
+
{
|
|
3112
|
+
name: 'add_dynamic_field',
|
|
3113
|
+
description: 'Add a dynamic-data widget to a loop-item / single / archive template — a STANDARD widget (heading/text-editor/image) carrying the correct Elementor __dynamic__ tag. Use this INSTEAD of the theme-post-* widgets (theme-post-title/-excerpt/-featured-image), which are unreliable when authored via REST (they render placeholders). Types: title→heading+post-title, excerpt→text-editor+post-excerpt, content→text-editor+post-content, featured_image→image+post-featured-image, acf→heading+acf (pass field = the ACF field key/name). The widget renders the current post\'s data inside loop/single context.',
|
|
3114
|
+
inputSchema: {
|
|
3115
|
+
type: 'object',
|
|
3116
|
+
properties: {
|
|
3117
|
+
page_id: { type: 'number', description: 'Template (or page) ID to add into' },
|
|
3118
|
+
parent_id: { type: 'string', description: 'Elementor ID of the parent container' },
|
|
3119
|
+
type: { type: 'string', enum: ['title', 'excerpt', 'content', 'featured_image', 'acf'], description: 'Which post field to bind.' },
|
|
3120
|
+
field: { type: 'string', description: 'For type=acf: the ACF field key or name to bind.' },
|
|
3121
|
+
tag: { type: 'string', description: 'Override the dynamic tag name (advanced — e.g. a custom registered tag).' },
|
|
3122
|
+
settings: { type: 'object', description: 'Extra widget settings to merge (e.g. typography, header_size, link). additionalProperties.', additionalProperties: true },
|
|
3123
|
+
position: { type: 'number', description: 'Optional 0-based insert index. Omit to append.' },
|
|
3124
|
+
force: { type: 'boolean', description: 'Override edit locks.' }
|
|
3125
|
+
},
|
|
3126
|
+
required: ['page_id', 'parent_id', 'type']
|
|
3127
|
+
}
|
|
3128
|
+
},
|
|
3092
3129
|
{
|
|
3093
3130
|
name: 'get_element',
|
|
3094
3131
|
description: 'Get a single element\'s full settings and metadata by its Elementor ID. Returns elType, widgetType, all settings, and a summary of children (for containers). Use this to inspect an element before updating it.',
|
|
@@ -3190,7 +3227,7 @@ function getToolDefinitions() {
|
|
|
3190
3227
|
},
|
|
3191
3228
|
{
|
|
3192
3229
|
name: 'convert_to_gutenberg',
|
|
3193
|
-
description: 'Phase 2 migration tool — convert an
|
|
3230
|
+
description: 'Phase 2 migration tool — convert an ELEMENTOR page/post\'s content to native core Gutenberg blocks. SCOPE: Elementor pages ONLY (reads _elementor_data); a classic/HTML post returns not_elementor — there is no automated classic-HTML → blocks path (store clean semantic HTML in post_content, it renders as a Classic block). NON-DESTRUCTIVE: the source is never modified. mode=preview (default) returns the block markup; mode=ir returns the intermediate representation (node list) for debugging; mode=new_draft creates a fresh draft and returns its edit URL. The new draft mirrors the source post type by default (post→post, page→page); override with target_type. Rich text-editor content that is not a single paragraph falls back to a lossless wp:html block. The output is pure core blocks — converted content does NOT depend on this plugin to render.',
|
|
3194
3231
|
inputSchema: { type: 'object', properties: {
|
|
3195
3232
|
page_id: { type: 'integer', description: 'Source Elementor page/post ID to convert.' },
|
|
3196
3233
|
mode: { type: 'string', enum: ['preview', 'ir', 'new_draft'], description: 'preview (default) | ir | new_draft' },
|
|
@@ -3409,13 +3446,14 @@ function getToolDefinitions() {
|
|
|
3409
3446
|
},
|
|
3410
3447
|
{
|
|
3411
3448
|
name: 'set_kit_global_css',
|
|
3412
|
-
description: '
|
|
3449
|
+
description: 'ESCAPE HATCH — AVOID. Writes ad-hoc CSS into the active kit\'s custom_css (Site Settings → Custom CSS): invisible to the client, unversioned, non-portable, and the #1 source of class-soup drift. This is NOT where component/utility styling belongs. Before using this, exhaust the styling rule: SPACING → nvbc-space-* class; COLOR → saved global; LAYOUT → native (set_container_layout); reusable decoration → register a class via upsert_class (page-scoped, plugin-owned). Legitimate uses are narrow (e.g. element-targeted [data-id] fix for V4 atomic visual props that the styles array can\'t reach). Modes: "replace" (default), "append", "prepend". Always dry_run=true first.',
|
|
3413
3450
|
inputSchema: {
|
|
3414
3451
|
type: 'object',
|
|
3415
3452
|
properties: {
|
|
3416
3453
|
css: { type: 'string', description: 'CSS text. Will be wrapped in <style> by Elementor — do not include style tags.' },
|
|
3417
3454
|
mode: { type: 'string', enum: ['replace', 'append', 'prepend'], description: 'How to combine with existing CSS (default: replace).' },
|
|
3418
|
-
dry_run: { type: 'boolean', description: 'Preview without saving (default: true).' }
|
|
3455
|
+
dry_run: { type: 'boolean', description: 'Preview without saving (default: true).' },
|
|
3456
|
+
confirm: { type: 'boolean', description: 'Required to actually write ad-hoc CSS here. Without confirm:true the save is REFUSED with guidance — this is the escape hatch; prefer upsert_class. Only set true for genuinely irreducible CSS with user approval.' }
|
|
3419
3457
|
},
|
|
3420
3458
|
required: ['css']
|
|
3421
3459
|
}
|
|
@@ -3438,7 +3476,7 @@ function getToolDefinitions() {
|
|
|
3438
3476
|
},
|
|
3439
3477
|
{
|
|
3440
3478
|
name: 'patch_kit_global_css',
|
|
3441
|
-
description: 'Surgically patch the active kit\'s custom_css with operations (remove_rule, remove_selector_from_rules, find_replace). Avoids the full-payload round-trip of set_kit_global_css. Always dry_run=true first. v4.45.0+.',
|
|
3479
|
+
description: 'Surgically patch the active kit\'s custom_css with operations (remove_rule, remove_selector_from_rules, find_replace). Best use is REMOVING ad-hoc CSS (cleanup), not adding it — adding component/utility styling here is the escape hatch that causes class-soup; prefer upsert_class (page-scoped, plugin-owned) for any new styling. Avoids the full-payload round-trip of set_kit_global_css. Always dry_run=true first. v4.45.0+.',
|
|
3442
3480
|
inputSchema: {
|
|
3443
3481
|
type: 'object',
|
|
3444
3482
|
required: ['operations'],
|
|
@@ -3451,6 +3489,10 @@ function getToolDefinitions() {
|
|
|
3451
3489
|
dry_run: {
|
|
3452
3490
|
type: 'boolean',
|
|
3453
3491
|
description: 'Preview without saving (default: true). Set false to apply.'
|
|
3492
|
+
},
|
|
3493
|
+
confirm: {
|
|
3494
|
+
type: 'boolean',
|
|
3495
|
+
description: 'Required only for a find_replace op that AUTHORS new CSS (non-empty replace). remove_* cleanup ops never need it. Without confirm:true such a write is REFUSED — prefer upsert_class for new styling.'
|
|
3454
3496
|
}
|
|
3455
3497
|
}
|
|
3456
3498
|
}
|
|
@@ -3475,14 +3517,15 @@ function getToolDefinitions() {
|
|
|
3475
3517
|
},
|
|
3476
3518
|
{
|
|
3477
3519
|
name: 'get_widget_schema',
|
|
3478
|
-
description: 'Read the full settings schema for one widget or element type — every accepted control with type, label, default, options, responsive flag, and section grouping. For atomic V4 widgets, returns the serialized props_schema instead (different system). Use to avoid having to read Elementor source. Pass section to scope to one control section, search to filter by control-name substring, or keys_only=true for just the names. v4.44.3+.',
|
|
3520
|
+
description: 'Read the full settings schema for one widget or element type — every accepted control with type, label, default, options, responsive flag, and section grouping. For atomic V4 widgets, returns the serialized props_schema instead (different system). Use to avoid having to read Elementor source. By DEFAULT the universal Advanced-tab control groups (margin/padding/motion-effects/transform/background/border/masking/responsive/custom-css/attributes) are EXCLUDED — they are ~200-300 identical boilerplate controls on every widget; pass include_common=true to include them. Pass section to scope to one control section (overrides the common filter), search to filter by control-name substring, or keys_only=true for just the names. v4.44.3+.',
|
|
3479
3521
|
inputSchema: {
|
|
3480
3522
|
type: 'object',
|
|
3481
3523
|
properties: {
|
|
3482
3524
|
widget_type: { type: 'string', description: 'Widget or element name (e.g. "heading", "e-heading", "container", "image", "button"). Use list_widget_types to enumerate.' },
|
|
3483
|
-
section: { type: 'string', description: 'Restrict to controls inside this control section (e.g. "section_title_style").' },
|
|
3484
|
-
search: { type: 'string', description: 'Substring filter on control name.' },
|
|
3485
|
-
keys_only: { type: 'boolean', description: 'Return only control name + type per entry (slim payload). Default: false.' }
|
|
3525
|
+
section: { type: 'string', description: 'Restrict to controls inside this control section (e.g. "section_title_style"). Overrides the default common-section filter.' },
|
|
3526
|
+
search: { type: 'string', description: 'Substring filter on control name. Overrides the default common-section filter.' },
|
|
3527
|
+
keys_only: { type: 'boolean', description: 'Return only control name + type per entry (slim payload). Default: false.' },
|
|
3528
|
+
include_common: { type: 'boolean', description: 'Include the universal Advanced-tab boilerplate controls (transform/effects/background/responsive/custom-css/etc.). Default: false — they are hidden to save tokens.' }
|
|
3486
3529
|
},
|
|
3487
3530
|
required: ['widget_type']
|
|
3488
3531
|
}
|
|
@@ -3674,9 +3717,24 @@ function getToolDefinitions() {
|
|
|
3674
3717
|
required: ['source_template_id', 'title']
|
|
3675
3718
|
}
|
|
3676
3719
|
},
|
|
3720
|
+
{
|
|
3721
|
+
name: 'create_template',
|
|
3722
|
+
description: 'Create a BLANK Theme Builder template in one call — then fill it with append_section / add_element. Atomically sets post_type=elementor_library, the elementor_library_type TAXONOMY TERM (required for loop context — setting only the meta silently fails for loop-item), the _elementor_template_type meta, and builder edit-mode. Use this instead of a raw wp/v2/elementor_library POST (which omits the taxonomy term and silently produces a loop template with no post context). For a template BASED ON an existing design, use clone_template instead. v4.111.0.',
|
|
3723
|
+
inputSchema: {
|
|
3724
|
+
type: 'object',
|
|
3725
|
+
properties: {
|
|
3726
|
+
type: { type: 'string', enum: ['loop-item', 'single', 'single-post', 'single-page', 'archive', 'header', 'footer', 'popup', 'section', 'page', 'search-results', 'error-404', 'product', 'product-archive'], description: 'Theme Builder document type. loop-item = the card template a loop-grid repeats; single/archive = full-page templates; etc.' },
|
|
3727
|
+
title: { type: 'string', description: 'Template title (e.g. "Team Member Loop Item")' },
|
|
3728
|
+
slug: { type: 'string', description: 'Optional URL slug. Auto-derived from title if omitted.' },
|
|
3729
|
+
conditions: { type: 'array', items: { type: 'string' }, description: 'Optional display conditions, e.g. ["include/singular/team_member"] or ["include/archive/team_member_archive"]. Omit for loop-item (it is referenced by a loop-grid, not by condition).' },
|
|
3730
|
+
status: { type: 'string', enum: ['publish', 'draft', 'private', 'pending'], description: 'Post status (default: publish)' }
|
|
3731
|
+
},
|
|
3732
|
+
required: ['type', 'title']
|
|
3733
|
+
}
|
|
3734
|
+
},
|
|
3677
3735
|
{
|
|
3678
3736
|
name: 'append_section',
|
|
3679
|
-
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. WIDTH SEMANTICS: section root containers default to content_width="full" (no boxed `.e-con-inner` wrapper) — flex-row children lay out side-by-side; pass content_width="boxed" + numeric boxed_width for a centered max-width section. When a section has content_width="boxed", any child with _element_custom_width in px greater than boxed_width will overflow horizontally — run validate_section before submitting to catch this (rules child_width_exceeds_parent + flex_row_fixed_widths_overflow, v4.95.0+).',
|
|
3737
|
+
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. WIDTH SEMANTICS: section root containers default to content_width="full" (no boxed `.e-con-inner` wrapper) — flex-row children lay out side-by-side; pass content_width="boxed" + numeric boxed_width for a centered max-width section. When a section has content_width="boxed", any child with _element_custom_width in px greater than boxed_width will overflow horizontally — run validate_section before submitting to catch this (rules child_width_exceeds_parent + flex_row_fixed_widths_overflow, v4.95.0+).' + CSS_PHILOSOPHY,
|
|
3680
3738
|
inputSchema: {
|
|
3681
3739
|
type: 'object',
|
|
3682
3740
|
properties: {
|
|
@@ -3989,7 +4047,7 @@ function getToolDefinitions() {
|
|
|
3989
4047
|
// ── Class Registry ──
|
|
3990
4048
|
{
|
|
3991
4049
|
name: 'list_classes',
|
|
3992
|
-
description: 'List registered CSS classes in the NVBC class registry.
|
|
4050
|
+
description: 'List registered CSS classes in the NVBC class registry — the shipped framework. CHECK THIS FIRST before writing any CSS: it includes the `nvbc-space-*` clamp spacing classes and `nvbc-font-*` clamp type classes (the correct default for spacing/fluid type, since native has no clamp), plus utility primitives. Reusing one of these is the right move, not a shortcut. Returns name, description, category, applies_to, scope, status. Apply via update_element / manage_element_classes. By default DRAFT site classes are hidden — pass include_drafts:true to see them. Pass page_id to filter site classes to those that are global OR scoped to that page (built-ins always pass). v4.92.0+ adds scope/status filtering; pre-v4.92 site classes are treated as scope=global + status=published.',
|
|
3993
4051
|
inputSchema: {
|
|
3994
4052
|
type: 'object',
|
|
3995
4053
|
properties: {
|
|
@@ -4040,7 +4098,7 @@ function getToolDefinitions() {
|
|
|
4040
4098
|
},
|
|
4041
4099
|
{
|
|
4042
4100
|
name: 'upsert_class',
|
|
4043
|
-
description: 'Define or update a site CSS class —
|
|
4101
|
+
description: 'Define or update a site CSS class — THE correct, plugin-owned home for any new CSS (the LAST resort after native settings, saved globals, and existing shipped classes have been ruled out — see the styling rule). Stores its metadata AND CSS body in the NVBC class registry. NVBC renders the CSS page-scoped (only on pages that use the class); it is NOT written to the Elementor Kit custom_css, so it stays versioned, portable, and client-visible. Always prefer this over set_kit_global_css / element custom_css for ad-hoc styling. The class appears in list_classes with source=[site] and in the editor Browse picker. Optional apply_to: attach the class to element IDs on specific pages in the same call. Call again with the same name to update (css/rules are replaced). v4.76.0+. v4.92.0 adds scope (global vs page) and status (published vs draft). v4.97.0 adds specificity (auto|layout|utility) so layout/visual classes win against .elementor .e-con default styles instead of losing on specificity. v4.98.0 adds rules — the recommended way to author classes that need pseudo-elements, hover states, or any multi-rule structure (keeps the plugin the owner of all class CSS instead of forcing the user to drop ad-hoc CSS into Elementor).',
|
|
4044
4102
|
inputSchema: {
|
|
4045
4103
|
type: 'object',
|
|
4046
4104
|
properties: {
|
|
@@ -4221,8 +4279,9 @@ async function handleToolCall(name, args) {
|
|
|
4221
4279
|
|
|
4222
4280
|
case 'capability_check': {
|
|
4223
4281
|
try {
|
|
4224
|
-
const r = await apiCall('/health?include_routes=1');
|
|
4282
|
+
const r = await apiCall('/health?include_routes=1&include_identity=1');
|
|
4225
4283
|
const routes = r.routes || {};
|
|
4284
|
+
const id = r.identity || null;
|
|
4226
4285
|
const exposedPaths = new Set(Object.keys(routes));
|
|
4227
4286
|
|
|
4228
4287
|
// Required-route map: which backend route each MCP tool depends on.
|
|
@@ -4263,6 +4322,23 @@ async function handleToolCall(name, args) {
|
|
|
4263
4322
|
let msg = `=== CAPABILITY CHECK ===\n`;
|
|
4264
4323
|
msg += `Plugin: v${r.version}\n`;
|
|
4265
4324
|
msg += `MCP server: v${VERSION}\n`;
|
|
4325
|
+
|
|
4326
|
+
// v4.110.0: identity probe — the fastest way to spot a stale/wrong app
|
|
4327
|
+
// password or a user with no role on this subsite (every write 401s
|
|
4328
|
+
// while routes/version look healthy).
|
|
4329
|
+
if (id) {
|
|
4330
|
+
if (id.user_id > 0) {
|
|
4331
|
+
const roles = (id.roles || []).join(', ') || 'no roles';
|
|
4332
|
+
msg += `Authenticated as: ${id.login} (#${id.user_id}) — ${roles}\n`;
|
|
4333
|
+
const caps = Object.entries(id.caps || {}).map(([c, v]) => `${v ? '✅' : '❌'} ${c}`).join(' ');
|
|
4334
|
+
msg += `Capabilities: ${caps}\n`;
|
|
4335
|
+
if (!id.caps?.edit_posts) {
|
|
4336
|
+
msg += `⚠️ This user CANNOT edit_posts — content writes will 401/403 despite healthy routes. Check the role on THIS (sub)site.\n`;
|
|
4337
|
+
}
|
|
4338
|
+
} else {
|
|
4339
|
+
msg += `⚠️ Authenticated as: NOBODY (user_id 0). The app password is missing/stale/wrong, or the user has no account on this site. Every write will return "Sorry, you are not allowed to do that".\n`;
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4266
4342
|
msg += `Total routes registered: ${Object.keys(routes).length}\n\n`;
|
|
4267
4343
|
|
|
4268
4344
|
if (missing.length === 0) {
|
|
@@ -4798,6 +4874,7 @@ async function handleToolCall(name, args) {
|
|
|
4798
4874
|
if (args.element_id) body.element_id = args.element_id;
|
|
4799
4875
|
if (args.element_path) body.element_path = args.element_path;
|
|
4800
4876
|
if (args.force) body.force = true;
|
|
4877
|
+
if (args.confirm !== undefined) body.confirm = args.confirm;
|
|
4801
4878
|
if (!body.element_id && !body.element_path) {
|
|
4802
4879
|
return ok('Failed: Provide element_id or element_path.');
|
|
4803
4880
|
}
|
|
@@ -5178,12 +5255,13 @@ async function handleToolCall(name, args) {
|
|
|
5178
5255
|
const body = { title: args.title };
|
|
5179
5256
|
const passthrough = ['post_type', 'content', 'excerpt', 'status', 'slug',
|
|
5180
5257
|
'parent', 'parent_slug', 'taxonomies', 'featured_image_id',
|
|
5181
|
-
'featured_image_basename', 'elementor', 'seo', 'dedupe_by_title'
|
|
5258
|
+
'featured_image_basename', 'elementor', 'seo', 'dedupe_by_title',
|
|
5259
|
+
'date', 'date_gmt'];
|
|
5182
5260
|
for (const k of passthrough) if (args[k] !== undefined) body[k] = args[k];
|
|
5183
5261
|
const r = await apiCall('/posts', 'POST', body);
|
|
5184
5262
|
if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
|
|
5185
5263
|
const prefix = r.was_existing ? 'Post already existed (dedupe hit)' : 'Post created';
|
|
5186
|
-
return ok(`${prefix}!\nID: ${r.id} | Type: ${r.type} | Status: ${r.status}\nTitle: ${r.title}\nSlug: ${r.slug}\nURL: ${r.url}\nEdit: ${r.edit_url}`);
|
|
5264
|
+
return ok(`${prefix}!\nID: ${r.id} | Type: ${r.type} | Status: ${r.status}\nTitle: ${r.title}\nSlug: ${r.slug}${r.date ? ` | Date: ${r.date}` : ''}\nURL: ${r.url}\nEdit: ${r.edit_url}`);
|
|
5187
5265
|
}
|
|
5188
5266
|
|
|
5189
5267
|
case 'get_post_by_slug': {
|
|
@@ -5513,10 +5591,22 @@ async function handleToolCall(name, args) {
|
|
|
5513
5591
|
|
|
5514
5592
|
case 'display_conditions': {
|
|
5515
5593
|
if (args.conditions) {
|
|
5516
|
-
const
|
|
5517
|
-
|
|
5518
|
-
});
|
|
5519
|
-
|
|
5594
|
+
const body = { conditions: args.conditions };
|
|
5595
|
+
if (args.strict !== undefined) body.strict = args.strict;
|
|
5596
|
+
const r = await apiCall(`/templates/${args.template_id}/display-conditions`, 'POST', body);
|
|
5597
|
+
if (r.code || r.error) {
|
|
5598
|
+
let m = `Failed: ${r.message || r.error}`;
|
|
5599
|
+
if (Array.isArray(r.warnings) && r.warnings.length) m += `\n - ${r.warnings.join('\n - ')}`;
|
|
5600
|
+
if (r.hint) m += `\n${r.hint}`;
|
|
5601
|
+
return ok(m);
|
|
5602
|
+
}
|
|
5603
|
+
let msg = `Set ${r.conditions.length} condition(s) on "${r.title}" (ID: ${r.template_id}):\n${r.conditions.join('\n')}`;
|
|
5604
|
+
if (Array.isArray(r.condition_warnings) && r.condition_warnings.length) {
|
|
5605
|
+
msg += `\n⚠️ CONDITION WARNINGS (persisted anyway — these may not match anything):\n - ${r.condition_warnings.join('\n - ')}\n Run list_display_conditions for valid strings.`;
|
|
5606
|
+
}
|
|
5607
|
+
if (r.caches_purged && r.caches_purged.length) msg += `\nCaches purged: ${r.caches_purged.join(', ')}`;
|
|
5608
|
+
if (r.verify_hint) msg += `\n${r.verify_hint}`;
|
|
5609
|
+
return ok(msg);
|
|
5520
5610
|
} else {
|
|
5521
5611
|
const r = await apiCall(`/templates/${args.template_id}/display-conditions`);
|
|
5522
5612
|
const conds = r.conditions && r.conditions.length ? r.conditions.join('\n') : '(none)';
|
|
@@ -5524,6 +5614,18 @@ async function handleToolCall(name, args) {
|
|
|
5524
5614
|
}
|
|
5525
5615
|
}
|
|
5526
5616
|
|
|
5617
|
+
case 'list_display_conditions': {
|
|
5618
|
+
const params = new URLSearchParams();
|
|
5619
|
+
if (args.post_type) params.set('post_type', args.post_type);
|
|
5620
|
+
const r = await apiCall(`/templates/conditions/available?${params.toString()}`);
|
|
5621
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
5622
|
+
if (!r.available) return ok(`Condition listing unavailable.\n${r.note}`);
|
|
5623
|
+
let out = `Valid display conditions (${r.count}${r.post_type ? `, filtered: "${r.post_type}"` : ''}):\n`;
|
|
5624
|
+
out += r.conditions.map(c => ` ${c}`).join('\n');
|
|
5625
|
+
out += `\n\n${r.note}`;
|
|
5626
|
+
return ok(out);
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5527
5629
|
case 'list_nav_menus': {
|
|
5528
5630
|
const r = await apiCall('/menus');
|
|
5529
5631
|
if (!r.menus || !r.menus.length) return ok('No nav menus found.');
|
|
@@ -5763,7 +5865,43 @@ async function handleToolCall(name, args) {
|
|
|
5763
5865
|
if (args.force) body.force = true;
|
|
5764
5866
|
const r = await apiCall(`/pages/${args.page_id}/add-element`, 'POST', body);
|
|
5765
5867
|
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
5766
|
-
|
|
5868
|
+
let addMsg = `Element added!\nPage: ${r.page_id}\nParent: ${r.parent_id}\nNew element ID: ${r.element_id}${r.position !== null && r.position !== undefined ? `\nPosition: ${r.position}` : ' (appended)'}`;
|
|
5869
|
+
if (Array.isArray(r.warnings) && r.warnings.length) addMsg += `\n⚠️ ${r.warnings.join('\n⚠️ ')}`;
|
|
5870
|
+
return ok(addMsg);
|
|
5871
|
+
}
|
|
5872
|
+
|
|
5873
|
+
case 'add_dynamic_field': {
|
|
5874
|
+
// Emit a standard widget + the correct __dynamic__ tag (the proven-rendering
|
|
5875
|
+
// form), instead of the unreliable theme-post-* widgets.
|
|
5876
|
+
const MAP = {
|
|
5877
|
+
title: { widgetType: 'heading', control: 'title', tag: 'post-title', base: { title: '', header_size: 'h2' } },
|
|
5878
|
+
excerpt: { widgetType: 'text-editor', control: 'editor', tag: 'post-excerpt', base: { editor: '' } },
|
|
5879
|
+
content: { widgetType: 'text-editor', control: 'editor', tag: 'post-content', base: { editor: '' } },
|
|
5880
|
+
featured_image: { widgetType: 'image', control: 'image', tag: 'post-featured-image', base: { image: { url: '', id: '' } } },
|
|
5881
|
+
acf: { widgetType: 'heading', control: 'title', tag: 'acf', base: { title: '', header_size: 'h2' } },
|
|
5882
|
+
};
|
|
5883
|
+
const m = MAP[args.type];
|
|
5884
|
+
if (!m) return ok(`Failed: unknown type "${args.type}". Use one of: ${Object.keys(MAP).join(', ')}.`);
|
|
5885
|
+
if (args.type === 'acf' && !args.field) return ok('Failed: type=acf requires a `field` (the ACF field key/name).');
|
|
5886
|
+
|
|
5887
|
+
// Elementor dynamic-tag shortcode: [elementor-tag id="<7-char>" name="<tag>" settings="<urlencoded-json>"]
|
|
5888
|
+
const tagId = Array.from({ length: 7 }, () => 'abcdef0123456789'[Math.floor(Math.random() * 16)]).join('');
|
|
5889
|
+
const tagSettings = {};
|
|
5890
|
+
if (args.type === 'acf') tagSettings.key = args.field;
|
|
5891
|
+
const enc = encodeURIComponent(JSON.stringify(tagSettings));
|
|
5892
|
+
const dynamicValue = `[elementor-tag id="${tagId}" name="${args.tag || m.tag}" settings="${enc}"]`;
|
|
5893
|
+
|
|
5894
|
+
const settings = Object.assign({}, m.base, args.settings || {}, {
|
|
5895
|
+
__dynamic__: Object.assign({}, (args.settings && args.settings.__dynamic__) || {}, { [m.control]: dynamicValue }),
|
|
5896
|
+
});
|
|
5897
|
+
const element = { elType: 'widget', widgetType: m.widgetType, settings };
|
|
5898
|
+
|
|
5899
|
+
const body = { parent_id: args.parent_id, element };
|
|
5900
|
+
if (args.position !== undefined) body.position = args.position;
|
|
5901
|
+
if (args.force) body.force = true;
|
|
5902
|
+
const r = await apiCall(`/pages/${args.page_id}/add-element`, 'POST', body);
|
|
5903
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
5904
|
+
return ok(`Dynamic field added!\nType: ${args.type} → ${m.widgetType} widget bound to "${args.tag || m.tag}"${args.field ? ` (field: ${args.field})` : ''}\nElement ID: ${r.element_id} (parent ${r.parent_id})\nIt renders the current post's data in loop/single context.`);
|
|
5767
5905
|
}
|
|
5768
5906
|
|
|
5769
5907
|
case 'get_element': {
|
|
@@ -6581,6 +6719,7 @@ async function handleToolCall(name, args) {
|
|
|
6581
6719
|
const body = { css: args.css ?? '' };
|
|
6582
6720
|
if (args.mode !== undefined) body.mode = args.mode;
|
|
6583
6721
|
if (args.dry_run !== undefined) body.dry_run = args.dry_run;
|
|
6722
|
+
if (args.confirm !== undefined) body.confirm = args.confirm;
|
|
6584
6723
|
const r = await apiCall('/kit-global-css', 'POST', body);
|
|
6585
6724
|
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
6586
6725
|
let out = r.dry_run ? `=== SET KIT GLOBAL CSS (DRY RUN) ===\n` : `=== SET KIT GLOBAL CSS ===\n`;
|
|
@@ -6593,6 +6732,7 @@ async function handleToolCall(name, args) {
|
|
|
6593
6732
|
case 'patch_kit_global_css': {
|
|
6594
6733
|
const body = { operations: args.operations ?? [] };
|
|
6595
6734
|
if (args.dry_run !== undefined) body.dry_run = args.dry_run;
|
|
6735
|
+
if (args.confirm !== undefined) body.confirm = args.confirm;
|
|
6596
6736
|
const r = await apiCall('/kit-global-css/patch', 'POST', body);
|
|
6597
6737
|
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
6598
6738
|
let out = r.dry_run
|
|
@@ -6673,6 +6813,7 @@ async function handleToolCall(name, args) {
|
|
|
6673
6813
|
if (args.section) params.set('section', args.section);
|
|
6674
6814
|
if (args.search) params.set('search', args.search);
|
|
6675
6815
|
if (args.keys_only) params.set('keys_only', '1');
|
|
6816
|
+
if (args.include_common) params.set('include_common', '1');
|
|
6676
6817
|
const r = await apiCall(`/widget-schema?${params.toString()}`);
|
|
6677
6818
|
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
6678
6819
|
let out =
|
|
@@ -6699,6 +6840,9 @@ async function handleToolCall(name, args) {
|
|
|
6699
6840
|
out += ` ${name.padEnd(36)} ${meta.join(' | ')}${dflt}\n`;
|
|
6700
6841
|
if (c.label && !args.keys_only) out += `${' '.repeat(38)}label: ${c.label}\n`;
|
|
6701
6842
|
}
|
|
6843
|
+
if (r.excluded_common && r.excluded_common.controls > 0) {
|
|
6844
|
+
out += `\n(${r.excluded_common.controls} universal Advanced-tab controls hidden across ${r.excluded_common.sections.length} sections — pass include_common=true to see them.)`;
|
|
6845
|
+
}
|
|
6702
6846
|
return ok(out);
|
|
6703
6847
|
}
|
|
6704
6848
|
|
|
@@ -7010,6 +7154,27 @@ async function handleToolCall(name, args) {
|
|
|
7010
7154
|
return ok(msg);
|
|
7011
7155
|
}
|
|
7012
7156
|
|
|
7157
|
+
case 'create_template': {
|
|
7158
|
+
if (!args.type || !args.title) {
|
|
7159
|
+
return ok('Failed: type and title are required.');
|
|
7160
|
+
}
|
|
7161
|
+
const body = { type: args.type, title: args.title };
|
|
7162
|
+
if (args.slug) body.slug = args.slug;
|
|
7163
|
+
if (Array.isArray(args.conditions)) body.conditions = args.conditions;
|
|
7164
|
+
if (args.status) body.status = args.status;
|
|
7165
|
+
const r = await apiCall('/templates', 'POST', body);
|
|
7166
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
7167
|
+
let msg = `Template created!\n`;
|
|
7168
|
+
msg += `ID: ${r.template_id} ("${r.title}")\n`;
|
|
7169
|
+
msg += `Type: ${r.type}\n`;
|
|
7170
|
+
msg += `Status: ${r.status}\n`;
|
|
7171
|
+
msg += `Conditions: ${r.conditions && r.conditions.length ? r.conditions.join(', ') : '(none)'}\n`;
|
|
7172
|
+
if (r.warning) msg += `⚠️ ${r.warning}\n`;
|
|
7173
|
+
msg += `Edit URL: ${r.edit_url}\n`;
|
|
7174
|
+
msg += `Next: add content with append_section / add_element (target_id=${r.template_id}).`;
|
|
7175
|
+
return ok(msg);
|
|
7176
|
+
}
|
|
7177
|
+
|
|
7013
7178
|
case 'append_section': {
|
|
7014
7179
|
// Enforce root settings on appended sections
|
|
7015
7180
|
if (args.section) addBoilerplate(args.section);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noleemits/vision-builder-control-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.114.0",
|
|
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",
|