@noleemits/vision-builder-control-mcp 4.41.1 → 4.42.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 +165 -2
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Noleemits Vision Builder Control MCP Server
|
|
4
4
|
*
|
|
5
|
-
* Provides
|
|
5
|
+
* Provides 69 tools for building and managing WordPress/Elementor sites.
|
|
6
|
+
* v4.42.0: audit_text + replace_text — unified search/replace across Elementor + post columns with literal/regex/fuzzy modes. Fuzzy mode tolerates NBSP, curly quotes/dashes, ideographic punctuation — fixes the "copy-pasted needle won't match" bug.
|
|
6
7
|
* v4.35.0: PixelVault image integration — pixelvault_status, find_images, generate_image, get_batch_status, insert_image tools; proxy REST endpoints at /nvbc/v1/pixelvault/*.
|
|
7
8
|
* v4.34.0: Design tokens auto-inject as CSS custom properties (--nvbc-color-*, --nvbc-fs-*, --nvbc-space-*) site-wide via wp_head; kit global CSS tools (get_kit_global_css, set_kit_global_css); get_kit_settings formatter handles gap-style objects.
|
|
8
9
|
* v4.33.0: Elementor kit layout settings (get_kit_settings, set_kit_settings) — read/write container_width, viewports, gutter.
|
|
@@ -107,7 +108,7 @@ process.on('SIGINT', () => {
|
|
|
107
108
|
// CONFIG
|
|
108
109
|
// ================================================================
|
|
109
110
|
|
|
110
|
-
const VERSION = '4.
|
|
111
|
+
const VERSION = '4.42.3';
|
|
111
112
|
const MIN_PLUGIN_VERSION = '4.13.0'; // Minimum WP plugin version required by this MCP server
|
|
112
113
|
|
|
113
114
|
// ================================================================
|
|
@@ -2172,6 +2173,18 @@ function getToolDefinitions() {
|
|
|
2172
2173
|
required: ['page_id']
|
|
2173
2174
|
}
|
|
2174
2175
|
},
|
|
2176
|
+
{
|
|
2177
|
+
name: 'fix_atomic_validation',
|
|
2178
|
+
description: 'Fix Elementor V4 atomic widget validation issues introduced when widgets were created via raw add_element calls. Walks _elementor_data and patches: (1) empty styles[*].label fields → defaults to the style id (Elementor requires 2-50 char labels matching a CSS-class regex); (2) invalid tag values on e-heading widgets (e.g. "div", "span") → defaults to "h2" (e-heading enum: h1-h6 only); (3) invalid tag values on e-paragraph widgets → defaults to "p" (e-paragraph enum: p, span only). Returns counts of what was fixed. Use this when the editor throws "class_name_too_short" or "tag: invalid_value" errors after MCP-built atomic content. Idempotent — safe to call multiple times.',
|
|
2179
|
+
inputSchema: {
|
|
2180
|
+
type: 'object',
|
|
2181
|
+
properties: {
|
|
2182
|
+
page_id: { type: 'number', description: 'WordPress page ID' },
|
|
2183
|
+
force: { type: 'boolean', description: 'Override edit locks (default: false)' }
|
|
2184
|
+
},
|
|
2185
|
+
required: ['page_id']
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2175
2188
|
{
|
|
2176
2189
|
name: 'audit_links',
|
|
2177
2190
|
description: 'Scan all Elementor pages and audit link targets. Reports internal links that open in new tab and external links that open in same tab. Use include_templates to also scan library templates (menus, headers, footers).',
|
|
@@ -3067,6 +3080,42 @@ function getToolDefinitions() {
|
|
|
3067
3080
|
required: ['search', 'replace']
|
|
3068
3081
|
}
|
|
3069
3082
|
},
|
|
3083
|
+
{
|
|
3084
|
+
name: 'audit_text',
|
|
3085
|
+
description: 'Unified text search across Elementor (full settings tree, every string value), post_content, post_title, and post_excerpt. The fuzzy mode tolerates unicode whitespace and punctuation variants — use it when a literal copy-pasted string mysteriously fails to match (NBSP between words, curly quotes/dashes/periods inserted by builders, etc.). Read-only.',
|
|
3086
|
+
inputSchema: {
|
|
3087
|
+
type: 'object',
|
|
3088
|
+
properties: {
|
|
3089
|
+
search: { type: 'string', description: 'Text or pattern to find.' },
|
|
3090
|
+
mode: { type: 'string', enum: ['literal', 'regex', 'fuzzy'], description: '"literal" (exact bytes), "regex" (PHP regex without delimiters, u flag forced), or "fuzzy" (tolerates NBSP, curly quotes/dashes/periods, ideographic whitespace). Default: literal.' },
|
|
3091
|
+
case_insensitive: { type: 'boolean', description: 'Ignore case. Default: false.' },
|
|
3092
|
+
stores: { type: 'array', items: { type: 'string', enum: ['elementor', 'post_content', 'post_title', 'post_excerpt'] }, description: 'Which stores to scan. Default: all four. Pass [\"elementor\"] to limit scope.' },
|
|
3093
|
+
post_type: { type: 'string', description: '"post", "page", "any", or a specific CPT slug. Default: any.' },
|
|
3094
|
+
include_drafts: { type: 'boolean', description: 'Include draft/private/pending posts. Default: true.' },
|
|
3095
|
+
limit: { type: 'number', description: 'Max posts to scan per store (default 1000, max 5000).' }
|
|
3096
|
+
},
|
|
3097
|
+
required: ['search']
|
|
3098
|
+
}
|
|
3099
|
+
},
|
|
3100
|
+
{
|
|
3101
|
+
name: 'replace_text',
|
|
3102
|
+
description: 'Unified bulk find/replace across Elementor (entire settings tree), post_content, post_title, and post_excerpt. Same matching modes as audit_text — fuzzy mode is the fix for the "copy-pasted needle won\'t match" class of bug. ALWAYS dry_run=true first. Never changes post_modified date.',
|
|
3103
|
+
inputSchema: {
|
|
3104
|
+
type: 'object',
|
|
3105
|
+
properties: {
|
|
3106
|
+
search: { type: 'string', description: 'Text or pattern to find.' },
|
|
3107
|
+
replace: { type: 'string', description: 'Replacement text. For regex/fuzzy modes, $1 etc. work as backreferences in regex mode only.' },
|
|
3108
|
+
mode: { type: 'string', enum: ['literal', 'regex', 'fuzzy'], description: '"literal", "regex", or "fuzzy". Default: literal.' },
|
|
3109
|
+
case_insensitive: { type: 'boolean', description: 'Ignore case. Default: false.' },
|
|
3110
|
+
stores: { type: 'array', items: { type: 'string', enum: ['elementor', 'post_content', 'post_title', 'post_excerpt'] }, description: 'Stores to operate on. Default: all four.' },
|
|
3111
|
+
post_type: { type: 'string', description: '"post", "page", "any", CPT slug. Default: any.' },
|
|
3112
|
+
include_drafts: { type: 'boolean', description: 'Include draft/private/pending. Default: true.' },
|
|
3113
|
+
limit: { type: 'number', description: 'Max posts per store. Default 1000, max 5000.' },
|
|
3114
|
+
dry_run: { type: 'boolean', description: 'Preview without saving. Default: true (SAFE).' }
|
|
3115
|
+
},
|
|
3116
|
+
required: ['search', 'replace']
|
|
3117
|
+
}
|
|
3118
|
+
},
|
|
3070
3119
|
{
|
|
3071
3120
|
name: 'set_faq_schema',
|
|
3072
3121
|
description: 'Set FAQPage JSON-LD schema on a single post. Stores in post meta and auto-injects into page head. Use for adding FAQ rich snippets to posts with Q&A content.',
|
|
@@ -4081,6 +4130,25 @@ async function handleToolCall(name, args) {
|
|
|
4081
4130
|
}
|
|
4082
4131
|
}
|
|
4083
4132
|
|
|
4133
|
+
case 'fix_atomic_validation': {
|
|
4134
|
+
try {
|
|
4135
|
+
const r = await apiCall(`/pages/${args.page_id}/fix-atomic-validation`, 'POST', {
|
|
4136
|
+
force: parseBool(args.force),
|
|
4137
|
+
});
|
|
4138
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
4139
|
+
const s = r.stats || {};
|
|
4140
|
+
return ok(
|
|
4141
|
+
`Fixed atomic widgets on page ${r.page_id}:\n` +
|
|
4142
|
+
` Visited: ${s.widgets_visited || 0} atomic widgets\n` +
|
|
4143
|
+
` Labels fixed: ${s.labels_fixed || 0}\n` +
|
|
4144
|
+
` Heading tags fixed: ${s.heading_tags_fixed || 0}\n` +
|
|
4145
|
+
` Paragraph tags fixed: ${s.paragraph_tags_fixed || 0}`
|
|
4146
|
+
);
|
|
4147
|
+
} catch (err) {
|
|
4148
|
+
return ok(`Failed: ${err.message}`);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4084
4152
|
case 'audit_links': {
|
|
4085
4153
|
const params = new URLSearchParams();
|
|
4086
4154
|
if (args.page_id) params.set('page_id', args.page_id);
|
|
@@ -5325,6 +5393,101 @@ async function handleToolCall(name, args) {
|
|
|
5325
5393
|
return ok(out);
|
|
5326
5394
|
}
|
|
5327
5395
|
|
|
5396
|
+
case 'audit_text': {
|
|
5397
|
+
const body = { search: stripCDATA(args.search) };
|
|
5398
|
+
if (args.mode) body.mode = args.mode;
|
|
5399
|
+
if (args.case_insensitive) body.case_insensitive = parseBool(args.case_insensitive, false);
|
|
5400
|
+
if (args.stores) body.stores = args.stores;
|
|
5401
|
+
if (args.post_type) body.post_type = args.post_type;
|
|
5402
|
+
if (args.include_drafts !== undefined) body.include_drafts = parseBool(args.include_drafts, true);
|
|
5403
|
+
if (args.limit) body.limit = args.limit;
|
|
5404
|
+
const r = await apiCall('/audit-text', 'POST', body);
|
|
5405
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
5406
|
+
const s = r.summary || { total_matches: 0, posts_affected: 0, by_store: {} };
|
|
5407
|
+
let out = `=== AUDIT TEXT (${r.mode || 'literal'}${r.case_insensitive ? ', ci' : ''}) ===\n`;
|
|
5408
|
+
out += `Search: "${r.search}"\n`;
|
|
5409
|
+
out += `Stores: ${(r.stores || []).join(', ')}\n`;
|
|
5410
|
+
out += `Total matches: ${s.total_matches} | Posts affected: ${s.posts_affected}\n`;
|
|
5411
|
+
if (s.by_store && Object.keys(s.by_store).length) {
|
|
5412
|
+
out += `By store: ${Object.entries(s.by_store).map(([k,v]) => `${k}=${v}`).join(', ')}\n`;
|
|
5413
|
+
}
|
|
5414
|
+
if (!r.matches?.length) {
|
|
5415
|
+
out += '\nNo matches found.';
|
|
5416
|
+
if (r.mode !== 'fuzzy') out += '\n💡 Try mode="fuzzy" to tolerate NBSP / curly punctuation.';
|
|
5417
|
+
return ok(out);
|
|
5418
|
+
}
|
|
5419
|
+
// Group by post for readability
|
|
5420
|
+
const groups = {};
|
|
5421
|
+
r.matches.forEach(m => {
|
|
5422
|
+
const key = `${m.post_id}|${m.post_title || ''}`;
|
|
5423
|
+
(groups[key] = groups[key] || []).push(m);
|
|
5424
|
+
});
|
|
5425
|
+
Object.entries(groups).slice(0, 50).forEach(([key, ms]) => {
|
|
5426
|
+
const [pid, title] = key.split('|');
|
|
5427
|
+
out += `\n--- [${pid}] ${title} — ${ms.length} match(es) ---\n`;
|
|
5428
|
+
ms.slice(0, 10).forEach(m => {
|
|
5429
|
+
if (m.store === 'elementor') {
|
|
5430
|
+
out += ` [elementor.${m.widget || '?'}] ${m.element_id}.${m.field || ''}\n`;
|
|
5431
|
+
} else {
|
|
5432
|
+
out += ` [${m.store}] ${m.field || ''}\n`;
|
|
5433
|
+
}
|
|
5434
|
+
out += ` raw: "${(m.raw_match || '').slice(0, 80).replace(/\n/g, ' ')}"\n`;
|
|
5435
|
+
out += ` ctx: ${(m.context || '').slice(0, 160)}\n`;
|
|
5436
|
+
});
|
|
5437
|
+
if (ms.length > 10) out += ` … and ${ms.length - 10} more\n`;
|
|
5438
|
+
});
|
|
5439
|
+
if (Object.keys(groups).length > 50) out += `\n…and ${Object.keys(groups).length - 50} more posts (truncated)`;
|
|
5440
|
+
return ok(out);
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5443
|
+
case 'replace_text': {
|
|
5444
|
+
const body = {
|
|
5445
|
+
search: stripCDATA(args.search),
|
|
5446
|
+
replace: stripCDATA(args.replace),
|
|
5447
|
+
dry_run: parseBool(args.dry_run, true),
|
|
5448
|
+
};
|
|
5449
|
+
if (args.mode) body.mode = args.mode;
|
|
5450
|
+
if (args.case_insensitive) body.case_insensitive = parseBool(args.case_insensitive, false);
|
|
5451
|
+
if (args.stores) body.stores = args.stores;
|
|
5452
|
+
if (args.post_type) body.post_type = args.post_type;
|
|
5453
|
+
if (args.include_drafts !== undefined) body.include_drafts = parseBool(args.include_drafts, true);
|
|
5454
|
+
if (args.limit) body.limit = args.limit;
|
|
5455
|
+
const r = await apiCall('/replace-text', 'POST', body);
|
|
5456
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error || 'Unknown error'}`);
|
|
5457
|
+
const s = r.summary || { total_matches: 0, posts_affected: 0, by_store: {} };
|
|
5458
|
+
const tag = r.dry_run ? 'DRY RUN' : 'APPLIED';
|
|
5459
|
+
let out = `=== REPLACE TEXT (${tag} · ${r.mode || 'literal'}${r.case_insensitive ? ', ci' : ''}) ===\n`;
|
|
5460
|
+
out += `Search: "${r.search}"\nReplace: "${r.replace}"\n`;
|
|
5461
|
+
out += `Stores: ${(r.stores || []).join(', ')}\n`;
|
|
5462
|
+
out += `Total matches: ${s.total_matches} | Posts affected: ${s.posts_affected}\n`;
|
|
5463
|
+
if (s.by_store && Object.keys(s.by_store).length) {
|
|
5464
|
+
out += `By store: ${Object.entries(s.by_store).map(([k,v]) => `${k}=${v}`).join(', ')}\n`;
|
|
5465
|
+
}
|
|
5466
|
+
if (!r.matches?.length) {
|
|
5467
|
+
out += '\nNo matches found.';
|
|
5468
|
+
if (r.mode !== 'fuzzy') out += '\n💡 Try mode="fuzzy" to tolerate NBSP / curly punctuation.';
|
|
5469
|
+
return ok(out);
|
|
5470
|
+
}
|
|
5471
|
+
const groups = {};
|
|
5472
|
+
r.matches.forEach(m => {
|
|
5473
|
+
const key = `${m.post_id}|${m.post_title || ''}`;
|
|
5474
|
+
(groups[key] = groups[key] || []).push(m);
|
|
5475
|
+
});
|
|
5476
|
+
Object.entries(groups).slice(0, 50).forEach(([key, ms]) => {
|
|
5477
|
+
const [pid, title] = key.split('|');
|
|
5478
|
+
out += `\n--- [${pid}] ${title} — ${ms.length} change(s) ---\n`;
|
|
5479
|
+
ms.slice(0, 8).forEach(m => {
|
|
5480
|
+
const loc = m.store === 'elementor'
|
|
5481
|
+
? `elementor.${m.widget || '?'} ${m.element_id}.${m.field || ''}`
|
|
5482
|
+
: `${m.store}`;
|
|
5483
|
+
out += ` [${loc}] "${(m.raw_match || '').slice(0, 60).replace(/\n/g, ' ')}"\n`;
|
|
5484
|
+
});
|
|
5485
|
+
if (ms.length > 8) out += ` … and ${ms.length - 8} more\n`;
|
|
5486
|
+
});
|
|
5487
|
+
if (r.dry_run) out += `\n(Dry run — no changes saved. Set dry_run=false to apply.)`;
|
|
5488
|
+
return ok(out);
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5328
5491
|
case 'set_faq_schema': {
|
|
5329
5492
|
const body = { post_id: args.post_id, faqs: args.faqs };
|
|
5330
5493
|
if (args.dry_run !== undefined) body.dry_run = args.dry_run;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noleemits/vision-builder-control-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.42.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",
|