@noleemits/vision-builder-control-mcp 4.114.1 → 4.119.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 +245 -5
- 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.119.0';
|
|
115
115
|
const MIN_PLUGIN_VERSION = '4.13.0'; // Minimum WP plugin version required by this MCP server
|
|
116
116
|
|
|
117
117
|
// v4.110.0: shared styling philosophy appended to every build/style tool so the
|
|
@@ -1908,6 +1908,17 @@ function getToolDefinitions() {
|
|
|
1908
1908
|
description: 'Probe the connected WordPress site for the actual plugin version + every REST route the plugin has registered. Use when a tool returns "Backend route missing on this site" / "No route was found" — this confirms the site is running an older plugin version than the MCP server expects. Output includes a mismatch report comparing MCP-required routes against what the backend actually exposes, so callers can decide whether to update the site plugin. v4.46.0+.',
|
|
1909
1909
|
inputSchema: { type: 'object', properties: {} }
|
|
1910
1910
|
},
|
|
1911
|
+
{
|
|
1912
|
+
name: 'search_tools',
|
|
1913
|
+
description: 'Search available MCP tools by keyword. Returns matching tool names and one-line descriptions. Call this early in a session to discover which tools cover a given task — e.g. "css classes", "page", "template", "seo", "image", "nav". Pass an empty string to list every tool.',
|
|
1914
|
+
inputSchema: {
|
|
1915
|
+
type: 'object',
|
|
1916
|
+
properties: {
|
|
1917
|
+
query: { type: 'string', description: 'Keyword(s) to match against tool names and descriptions. Empty string returns all tools.' }
|
|
1918
|
+
},
|
|
1919
|
+
required: ['query']
|
|
1920
|
+
}
|
|
1921
|
+
},
|
|
1911
1922
|
{
|
|
1912
1923
|
name: 'get_design_tokens',
|
|
1913
1924
|
description: 'Get all design tokens (colors, typography, spacing, URLs, buttons, globals_map). Tokens are cached for 5 minutes.',
|
|
@@ -2465,7 +2476,7 @@ function getToolDefinitions() {
|
|
|
2465
2476
|
},
|
|
2466
2477
|
{
|
|
2467
2478
|
name: 'create_post',
|
|
2468
|
-
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.',
|
|
2479
|
+
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. Accepts author (ID, login, slug, or email; requires edit_others_posts).',
|
|
2469
2480
|
inputSchema: {
|
|
2470
2481
|
type: 'object',
|
|
2471
2482
|
properties: {
|
|
@@ -2474,9 +2485,10 @@ function getToolDefinitions() {
|
|
|
2474
2485
|
content: { type: 'string', description: 'Post content (block HTML or plain text)' },
|
|
2475
2486
|
excerpt: { type: 'string', description: 'Post excerpt' },
|
|
2476
2487
|
status: { type: 'string', enum: ['draft', 'publish', 'private', 'pending'], description: 'Post status (default: draft)' },
|
|
2488
|
+
slug: { type: 'string', description: 'URL slug (auto-generated if omitted)' },
|
|
2489
|
+
author: { type: ['string', 'number'], description: 'Author to assign: user ID, login, slug, or email. Requires edit_others_posts.' },
|
|
2477
2490
|
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.' },
|
|
2478
2491
|
date_gmt: { type: 'string', description: 'Optional explicit UTC date. If omitted, derived from `date` via the site timezone.' },
|
|
2479
|
-
slug: { type: 'string', description: 'URL slug (auto-generated if omitted)' },
|
|
2480
2492
|
parent: { type: 'number', description: 'Parent post ID (for hierarchical types). Use this OR parent_slug.' },
|
|
2481
2493
|
parent_slug: { type: 'string', description: 'Parent slug — looked up against the same post_type. Saves a follow-up update_post call.' },
|
|
2482
2494
|
taxonomies: { type: 'object', description: 'Object: {"category": [1,2], "post_tag": ["seo","health"]}' },
|
|
@@ -2491,7 +2503,7 @@ function getToolDefinitions() {
|
|
|
2491
2503
|
},
|
|
2492
2504
|
{
|
|
2493
2505
|
name: 'update_post',
|
|
2494
|
-
description: 'Update any field on a WordPress post/page. Only provided fields are changed. Supports title, content, status, slug, excerpt, parent (ID or slug), taxonomies, featured image (ID or basename), and RankMath SEO. Pass preserve_modified_date=true to keep the original last-modified date.',
|
|
2506
|
+
description: 'Update any field on a WordPress post/page. Only provided fields are changed. Supports title, content, status, slug, excerpt, parent (ID or slug), taxonomies, featured image (ID or basename), and RankMath SEO. Pass preserve_modified_date=true to keep the original last-modified date. Accepts author (ID, login, slug, or email; requires edit_others_posts).',
|
|
2495
2507
|
inputSchema: {
|
|
2496
2508
|
type: 'object',
|
|
2497
2509
|
properties: {
|
|
@@ -2501,6 +2513,7 @@ function getToolDefinitions() {
|
|
|
2501
2513
|
excerpt: { type: 'string' },
|
|
2502
2514
|
status: { type: 'string', enum: ['draft', 'publish', 'private', 'pending', 'trash'] },
|
|
2503
2515
|
slug: { type: 'string' },
|
|
2516
|
+
author: { type: ['string', 'number'], description: 'Author to assign: user ID, login, slug, or email. Requires edit_others_posts.' },
|
|
2504
2517
|
parent: { type: 'number', description: 'Parent post/page ID (for hierarchical types like pages). Changes URL structure.' },
|
|
2505
2518
|
parent_slug: { type: 'string', description: 'Parent slug — looked up against this post\'s post_type. Use instead of parent when you don\'t know the parent ID.' },
|
|
2506
2519
|
taxonomies: { type: 'object', description: '{"category": [1,2]}' },
|
|
@@ -2537,6 +2550,46 @@ function getToolDefinitions() {
|
|
|
2537
2550
|
required: ['slug']
|
|
2538
2551
|
}
|
|
2539
2552
|
},
|
|
2553
|
+
{
|
|
2554
|
+
name: 'list_users',
|
|
2555
|
+
description: 'List WordPress users (authors/editors/etc). Returns id, login, slug, display_name, email, roles, and published post_count. Use to discover an author ID/login before assigning posts with create_post/update_post/set_post_author/reassign_author. Requires the list_users capability.',
|
|
2556
|
+
inputSchema: {
|
|
2557
|
+
type: 'object',
|
|
2558
|
+
properties: {
|
|
2559
|
+
role: { type: 'string', description: 'Filter by role slug (e.g. "author", "editor", "administrator").' },
|
|
2560
|
+
search: { type: 'string', description: 'Substring match on login, name, or email.' },
|
|
2561
|
+
has_posts: { type: 'boolean', description: 'If true, only users with at least one published post.' },
|
|
2562
|
+
per_page: { type: 'number', description: 'Max results (default 100, cap 200).' }
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
name: 'set_post_author',
|
|
2568
|
+
description: 'Assign one author to a specific list of posts (bulk). Pass post_ids and an author (ID, login, slug, or email). Returns which posts were updated vs skipped. Requires edit_others_posts.',
|
|
2569
|
+
inputSchema: {
|
|
2570
|
+
type: 'object',
|
|
2571
|
+
properties: {
|
|
2572
|
+
post_ids: { type: 'array', items: { type: 'number' }, description: 'Post IDs to reassign.' },
|
|
2573
|
+
author: { type: ['string', 'number'], description: 'Target author: ID, login, slug, or email.' }
|
|
2574
|
+
},
|
|
2575
|
+
required: ['post_ids', 'author']
|
|
2576
|
+
}
|
|
2577
|
+
},
|
|
2578
|
+
{
|
|
2579
|
+
name: 'reassign_author',
|
|
2580
|
+
description: 'Reassign ALL posts from one author to another (e.g. offboarding a writer). from/to accept ID, login, slug, or email. Optionally filter by post_type and status. Pass dry_run:true first to see how many posts would move without changing anything. Requires edit_others_posts.',
|
|
2581
|
+
inputSchema: {
|
|
2582
|
+
type: 'object',
|
|
2583
|
+
properties: {
|
|
2584
|
+
from: { type: ['string', 'number'], description: 'Source author (ID, login, slug, or email).' },
|
|
2585
|
+
to: { type: ['string', 'number'], description: 'Destination author (ID, login, slug, or email).' },
|
|
2586
|
+
post_type: { type: 'string', description: 'Limit to one post type (default: all).' },
|
|
2587
|
+
status: { type: 'array', items: { type: 'string' }, description: 'Statuses to include (default: publish,draft,pending,private,future).' },
|
|
2588
|
+
dry_run: { type: 'boolean', description: 'If true, only count + list the post IDs that would move. ALWAYS run this first on a large reassignment.' }
|
|
2589
|
+
},
|
|
2590
|
+
required: ['from', 'to']
|
|
2591
|
+
}
|
|
2592
|
+
},
|
|
2540
2593
|
{
|
|
2541
2594
|
name: 'change_post_type',
|
|
2542
2595
|
description: 'Migrate a WordPress post to a different post type IN PLACE — preserves post ID, post_meta (SEO, featured image, custom fields), comments, attachments, post_date, and any taxonomy terms registered on both types. Atomic operation (single DB write to the post_type column, no save_post re-fire). Optionally rewrites internal links across all post_content and creates a RankMath 301 redirect from the old permalink. Use this instead of create_post + delete_post when moving a page to a CPT — it is faster, lossless, and keeps the URL history.',
|
|
@@ -3634,6 +3687,31 @@ function getToolDefinitions() {
|
|
|
3634
3687
|
required: ['post_id', 'fields']
|
|
3635
3688
|
}
|
|
3636
3689
|
},
|
|
3690
|
+
{
|
|
3691
|
+
name: 'get_post_meta',
|
|
3692
|
+
description: 'Read RAW WordPress post meta for any post (not ACF-specific — get_acf_fields cannot see plain meta written by themes, page builders, or generators like Mosaic). Returns stored values including HTML. Distinguishes missing keys (in `missing`) from present-but-empty (value === ""). Omit `keys` to return all non-protected meta.',
|
|
3693
|
+
inputSchema: {
|
|
3694
|
+
type: 'object',
|
|
3695
|
+
properties: {
|
|
3696
|
+
post_id: { type: 'number', description: 'WordPress post ID' },
|
|
3697
|
+
keys: { type: 'array', items: { type: 'string' }, description: 'Optional list of meta keys. Omit to return all non-protected meta.' }
|
|
3698
|
+
},
|
|
3699
|
+
required: ['post_id']
|
|
3700
|
+
}
|
|
3701
|
+
},
|
|
3702
|
+
{
|
|
3703
|
+
name: 'update_post_meta',
|
|
3704
|
+
description: 'Write RAW WordPress post meta (HTML-safe, no wpautop mangling). Defaults to dry_run=true — set dry_run=false to apply. Protected meta (keys starting with "_") is skipped. Use for seeding/correcting plain custom fields. Note: update returns "unchanged_or_failed" when the new value equals the stored value.',
|
|
3705
|
+
inputSchema: {
|
|
3706
|
+
type: 'object',
|
|
3707
|
+
properties: {
|
|
3708
|
+
post_id: { type: 'number', description: 'WordPress post ID' },
|
|
3709
|
+
meta: { type: 'object', description: 'Object of { metaKey: value }. Values stored raw (HTML preserved).' },
|
|
3710
|
+
dry_run: { type: 'boolean', description: 'Default true. Set false to write.' }
|
|
3711
|
+
},
|
|
3712
|
+
required: ['post_id', 'meta']
|
|
3713
|
+
}
|
|
3714
|
+
},
|
|
3637
3715
|
{
|
|
3638
3716
|
name: 'create_acf_field_group',
|
|
3639
3717
|
description: 'Create a new ACF field group with fields and post type assignments. Supports field types: text, textarea, wysiwyg, number, url, image, select, true_false, repeater, group.',
|
|
@@ -3718,6 +3796,48 @@ function getToolDefinitions() {
|
|
|
3718
3796
|
required: ['source_template_id', 'title']
|
|
3719
3797
|
}
|
|
3720
3798
|
},
|
|
3799
|
+
{
|
|
3800
|
+
name: 'duplicate_post',
|
|
3801
|
+
description: 'Duplicate any post into a new post in one call. Can change post_type (e.g. clone a CPT post into a standalone page). Copies _elementor_data with regenerated element IDs, the WP page template, selected post meta, and RankMath SEO by default. Returns the new ID + preview URL. NOTE: this copies structure + meta but does NOT resolve dynamic tags — a clone of a template-driven CPT post still renders via dynamic tags unless you also flatten_dynamic_tags or update_post_meta the clone.',
|
|
3802
|
+
inputSchema: {
|
|
3803
|
+
type: 'object',
|
|
3804
|
+
properties: {
|
|
3805
|
+
source_id: { type: 'number', description: 'Post ID to duplicate' },
|
|
3806
|
+
new_title: { type: 'string', description: 'Title for the copy (default: "<source> (copy)")' },
|
|
3807
|
+
new_slug: { type: 'string', description: 'Optional slug (auto from title if omitted)' },
|
|
3808
|
+
status: { type: 'string', enum: ['publish', 'draft', 'private', 'pending'], description: 'Default draft' },
|
|
3809
|
+
target_post_type: { type: 'string', description: 'Optional — clone into a different post type, e.g. "page". Default: same as source.' },
|
|
3810
|
+
include: {
|
|
3811
|
+
type: 'object',
|
|
3812
|
+
description: 'What to copy. Defaults all true. meta can be true (all non-protected) or an array of specific keys. Set seo:false to skip rank_math_* when copying all meta.',
|
|
3813
|
+
properties: {
|
|
3814
|
+
elementor_data: { type: 'boolean' },
|
|
3815
|
+
page_template: { type: 'boolean' },
|
|
3816
|
+
meta: {},
|
|
3817
|
+
seo: { type: 'boolean' }
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
},
|
|
3821
|
+
required: ['source_id']
|
|
3822
|
+
}
|
|
3823
|
+
},
|
|
3824
|
+
{
|
|
3825
|
+
name: 'flatten_dynamic_tags',
|
|
3826
|
+
description: 'Resolve Elementor dynamic tags on a post to their literal rendered values and write the result back (in place, or to a different target). Resolves ANY bound tag via Elementor itself — honors fallback/after exactly as the front end. meta_context_id lets you bake post X\'s content onto a generic clone. Action tags (popup) and functional widgets (jet-form-builder-form) are skipped by default — extend via skip_tags / skip_widgets. Defaults dry_run=true: review the per-binding report, then set dry_run=false. This is the one-shot de-dynamification primitive for converting template-driven CPT posts into self-contained static pages.',
|
|
3827
|
+
inputSchema: {
|
|
3828
|
+
type: 'object',
|
|
3829
|
+
properties: {
|
|
3830
|
+
source_id: { type: 'number', description: 'Post whose dynamic tags to resolve (path param)' },
|
|
3831
|
+
target_id: { type: 'number', description: 'Write resolved data here. Omit = in place (= source).' },
|
|
3832
|
+
meta_context_id: { type: 'number', description: 'Resolve post-aware tags against THIS post\'s context/meta. Omit = source. CRITICAL when source is a shared template (which has no per-item meta): set this to the specific CPT post whose content you want baked, or every tag resolves to its fallback.' },
|
|
3833
|
+
strip_bindings: { type: 'boolean', description: 'Remove __dynamic__ entries after inlining. Default true.' },
|
|
3834
|
+
dry_run: { type: 'boolean', description: 'Default true. Set false to write.' },
|
|
3835
|
+
skip_tags: { type: 'array', items: { type: 'string' }, description: 'Extra tag names to leave untouched (popup always skipped).' },
|
|
3836
|
+
skip_widgets: { type: 'array', items: { type: 'string' }, description: 'Extra widget types to leave untouched (jet-form-builder-form always skipped).' }
|
|
3837
|
+
},
|
|
3838
|
+
required: ['source_id']
|
|
3839
|
+
}
|
|
3840
|
+
},
|
|
3721
3841
|
{
|
|
3722
3842
|
name: 'create_template',
|
|
3723
3843
|
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.',
|
|
@@ -5223,6 +5343,42 @@ async function handleToolCall(name, args) {
|
|
|
5223
5343
|
return ok(out);
|
|
5224
5344
|
}
|
|
5225
5345
|
|
|
5346
|
+
case 'list_users': {
|
|
5347
|
+
const params = new URLSearchParams();
|
|
5348
|
+
if (args.role) params.set('role', args.role);
|
|
5349
|
+
if (args.search) params.set('search', args.search);
|
|
5350
|
+
if (args.has_posts) params.set('has_posts', '1');
|
|
5351
|
+
if (args.per_page) params.set('per_page', args.per_page);
|
|
5352
|
+
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
5353
|
+
const r = await apiCall(`/users${qs}`);
|
|
5354
|
+
if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
|
|
5355
|
+
let out = `${r.total} users\n${'─'.repeat(50)}\n`;
|
|
5356
|
+
r.users.forEach(u => {
|
|
5357
|
+
out += `[${u.id}] ${u.display_name} (${u.login})\n`;
|
|
5358
|
+
out += ` Roles: ${u.roles.join(', ') || 'none'} | Posts: ${u.post_count} | ${u.email}\n`;
|
|
5359
|
+
});
|
|
5360
|
+
return ok(out);
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
case 'set_post_author': {
|
|
5364
|
+
const r = await apiCall('/posts/set-author', 'POST', { post_ids: args.post_ids, author: args.author });
|
|
5365
|
+
if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
|
|
5366
|
+
let out = `Set author → ${r.author} (#${r.author_id})\nUpdated ${r.count}: ${r.updated.join(', ')}`;
|
|
5367
|
+
if (r.skipped.length) out += `\nSkipped ${r.skipped.length}: ${r.skipped.map(s => `${s.id} (${s.reason})`).join(', ')}`;
|
|
5368
|
+
return ok(out);
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
case 'reassign_author': {
|
|
5372
|
+
const body = { from: args.from, to: args.to };
|
|
5373
|
+
if (args.post_type) body.post_type = args.post_type;
|
|
5374
|
+
if (args.status) body.status = args.status;
|
|
5375
|
+
if (args.dry_run) body.dry_run = true;
|
|
5376
|
+
const r = await apiCall('/users/reassign-author', 'POST', body);
|
|
5377
|
+
if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
|
|
5378
|
+
if (r.dry_run) return ok(`DRY RUN: ${r.would_reassign} posts would move from #${r.from_id} → #${r.to_id}.\nIDs: ${r.post_ids.join(', ')}`);
|
|
5379
|
+
return ok(`Reassigned ${r.count} posts: ${r.from} (#${r.from_id}) → ${r.to} (#${r.to_id})`);
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5226
5382
|
case 'get_post': {
|
|
5227
5383
|
const r = await apiCall(`/posts/${args.post_id}`);
|
|
5228
5384
|
if (!r.success) return ok(`Failed: ${r.message || 'Not found'}`);
|
|
@@ -5255,7 +5411,7 @@ async function handleToolCall(name, args) {
|
|
|
5255
5411
|
case 'create_post': {
|
|
5256
5412
|
const body = { title: args.title };
|
|
5257
5413
|
const passthrough = ['post_type', 'content', 'excerpt', 'status', 'slug',
|
|
5258
|
-
'parent', 'parent_slug', 'taxonomies', 'featured_image_id',
|
|
5414
|
+
'author', 'parent', 'parent_slug', 'taxonomies', 'featured_image_id',
|
|
5259
5415
|
'featured_image_basename', 'elementor', 'seo', 'dedupe_by_title',
|
|
5260
5416
|
'date', 'date_gmt'];
|
|
5261
5417
|
for (const k of passthrough) if (args[k] !== undefined) body[k] = args[k];
|
|
@@ -7021,6 +7177,39 @@ async function handleToolCall(name, args) {
|
|
|
7021
7177
|
return ok(out);
|
|
7022
7178
|
}
|
|
7023
7179
|
|
|
7180
|
+
case 'get_post_meta': {
|
|
7181
|
+
let ep = `/post-meta?post_id=${args.post_id}`;
|
|
7182
|
+
if (Array.isArray(args.keys) && args.keys.length) {
|
|
7183
|
+
ep += `&keys=${encodeURIComponent(args.keys.join(','))}`;
|
|
7184
|
+
}
|
|
7185
|
+
const r = await apiCall(ep);
|
|
7186
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
7187
|
+
let out = `=== POST META: ${r.post_title} (ID:${r.post_id}) ===\n`;
|
|
7188
|
+
const keys = Object.keys(r.meta || {});
|
|
7189
|
+
if (!keys.length) {
|
|
7190
|
+
out += 'No meta found.\n';
|
|
7191
|
+
} else {
|
|
7192
|
+
keys.forEach(k => {
|
|
7193
|
+
const m = r.meta[k];
|
|
7194
|
+
const val = typeof m.value === 'object' ? JSON.stringify(m.value) : (m.value === '' ? '(empty)' : m.value);
|
|
7195
|
+
out += `\n ${k}${m.count > 1 ? ` [×${m.count}]` : ''}: ${val}\n`;
|
|
7196
|
+
});
|
|
7197
|
+
}
|
|
7198
|
+
if (r.missing && r.missing.length) out += `\nMISSING (not present): ${r.missing.join(', ')}\n`;
|
|
7199
|
+
return ok(out);
|
|
7200
|
+
}
|
|
7201
|
+
|
|
7202
|
+
case 'update_post_meta': {
|
|
7203
|
+
const body = { post_id: args.post_id, meta: args.meta };
|
|
7204
|
+
if (typeof args.dry_run === 'boolean') body.dry_run = args.dry_run;
|
|
7205
|
+
const r = await apiCall('/post-meta', 'POST', body);
|
|
7206
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
7207
|
+
let out = `${r.dry_run ? 'DRY RUN' : 'WROTE'} — ${r.updated} key(s) on "${r.post_title}" (ID:${r.post_id})\n`;
|
|
7208
|
+
(r.results || []).forEach(x => { out += ` ${x.key}: ${x.status}\n`; });
|
|
7209
|
+
out += `\n${r.note}\n`;
|
|
7210
|
+
return ok(out);
|
|
7211
|
+
}
|
|
7212
|
+
|
|
7024
7213
|
case 'create_acf_field_group': {
|
|
7025
7214
|
const body = { title: args.title, fields: args.fields };
|
|
7026
7215
|
if (args.post_types) body.post_types = args.post_types;
|
|
@@ -7155,6 +7344,42 @@ async function handleToolCall(name, args) {
|
|
|
7155
7344
|
return ok(msg);
|
|
7156
7345
|
}
|
|
7157
7346
|
|
|
7347
|
+
case 'duplicate_post': {
|
|
7348
|
+
if (!args.source_id) return ok('Failed: source_id is required.');
|
|
7349
|
+
const body = { source_id: args.source_id };
|
|
7350
|
+
if (args.new_title) body.new_title = args.new_title;
|
|
7351
|
+
if (args.new_slug) body.new_slug = args.new_slug;
|
|
7352
|
+
if (args.status) body.status = args.status;
|
|
7353
|
+
if (args.target_post_type) body.target_post_type = args.target_post_type;
|
|
7354
|
+
if (args.include) body.include = args.include;
|
|
7355
|
+
const r = await apiCall('/posts/duplicate', 'POST', body);
|
|
7356
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
7357
|
+
let out = `Duplicated post ${r.source_id} (${r.source_type}) → ${r.new_id} (${r.new_post_type}, ${r.status})\n`;
|
|
7358
|
+
out += `Sections: ${r.sections} | Meta copied: ${r.meta_copied.length}\n`;
|
|
7359
|
+
out += `Preview: ${r.preview_url}\nEdit: ${r.edit_url}\n`;
|
|
7360
|
+
return ok(out);
|
|
7361
|
+
}
|
|
7362
|
+
|
|
7363
|
+
case 'flatten_dynamic_tags': {
|
|
7364
|
+
if (!args.source_id) return ok('Failed: source_id is required.');
|
|
7365
|
+
const body = {};
|
|
7366
|
+
if (args.target_id) body.target_id = args.target_id;
|
|
7367
|
+
if (args.meta_context_id) body.meta_context_id = args.meta_context_id;
|
|
7368
|
+
if (typeof args.strip_bindings === 'boolean') body.strip_bindings = args.strip_bindings;
|
|
7369
|
+
if (typeof args.dry_run === 'boolean') body.dry_run = args.dry_run;
|
|
7370
|
+
if (Array.isArray(args.skip_tags)) body.skip_tags = args.skip_tags;
|
|
7371
|
+
if (Array.isArray(args.skip_widgets)) body.skip_widgets = args.skip_widgets;
|
|
7372
|
+
const r = await apiCall(`/pages/${args.source_id}/flatten-dynamic-tags`, 'POST', body);
|
|
7373
|
+
if (r.code || r.error) return ok(`Failed: ${r.message || r.error}`);
|
|
7374
|
+
let out = `${r.dry_run ? 'DRY RUN' : 'FLATTENED'} — ${r.resolved} binding(s) | source ${r.source_id} → target ${r.target_id} | ctx ${r.meta_context_id}\n`;
|
|
7375
|
+
out += `Skipped tags: ${r.skipped_tags.join(', ')} | Skipped widgets: ${r.skipped_widgets.join(', ')}\n\n`;
|
|
7376
|
+
(r.report || []).forEach(x => {
|
|
7377
|
+
out += ` [${x.action}] ${x.id || ''} ${x.field ? `.${x.field}` : ''} ${x.tag ? `(${x.tag})` : ''}${x.preview ? ` → ${x.preview}` : ''}\n`;
|
|
7378
|
+
});
|
|
7379
|
+
out += `\n${r.note}\n`;
|
|
7380
|
+
return ok(out);
|
|
7381
|
+
}
|
|
7382
|
+
|
|
7158
7383
|
case 'create_template': {
|
|
7159
7384
|
if (!args.type || !args.title) {
|
|
7160
7385
|
return ok('Failed: type and title are required.');
|
|
@@ -8269,6 +8494,21 @@ async function handleToolCall(name, args) {
|
|
|
8269
8494
|
return ok(msg);
|
|
8270
8495
|
}
|
|
8271
8496
|
|
|
8497
|
+
case 'search_tools': {
|
|
8498
|
+
const q = (args.query || '').toLowerCase().trim();
|
|
8499
|
+
const all = getToolDefinitions();
|
|
8500
|
+
const matches = q
|
|
8501
|
+
? all.filter(t => t.name.includes(q) || t.description.toLowerCase().includes(q))
|
|
8502
|
+
: all;
|
|
8503
|
+
if (!matches.length) return ok(`No tools match "${args.query}". Try a broader term.`);
|
|
8504
|
+
const lines = matches.map(t => {
|
|
8505
|
+
const firstSentence = t.description.split(/[.\n]/)[0].trim();
|
|
8506
|
+
return `${t.name} — ${firstSentence}`;
|
|
8507
|
+
});
|
|
8508
|
+
const header = q ? `${matches.length} tool(s) matched "${args.query}"` : `${matches.length} tools available`;
|
|
8509
|
+
return ok(`${header}\n\n${lines.join('\n')}`);
|
|
8510
|
+
}
|
|
8511
|
+
|
|
8272
8512
|
default:
|
|
8273
8513
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
8274
8514
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noleemits/vision-builder-control-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.119.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",
|