@noleemits/vision-builder-control-mcp 4.46.2 → 4.47.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.
Files changed (2) hide show
  1. package/index.js +60 -4
  2. 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.46.2';
114
+ const VERSION = '4.47.0';
115
115
  const MIN_PLUGIN_VERSION = '4.13.0'; // Minimum WP plugin version required by this MCP server
116
116
 
117
117
  // ================================================================
@@ -2429,7 +2429,8 @@ function getToolDefinitions() {
2429
2429
  featured_image_id: { type: 'number', description: 'Media library attachment ID' },
2430
2430
  featured_image_basename: { type: 'string', description: 'Filename of an attachment (e.g. "boating-accidents-v2.jpg") — resolves to the matching attachment ID. Use instead of featured_image_id when you have the source filename but not the ID.' },
2431
2431
  elementor: { type: 'boolean', description: 'Set up for Elementor editing' },
2432
- seo: { type: 'object', description: 'RankMath SEO: {title, description, focus_keyword, robots, ...}' }
2432
+ seo: { type: 'object', description: 'RankMath SEO: {title, description, focus_keyword, robots, ...}' },
2433
+ dedupe_by_title: { type: 'boolean', description: 'If true, check for an existing non-trashed post with the same (title, post_type) and return it instead of creating a duplicate. Response will have was_existing:true. Use for idempotent creates over flaky transports — protects against phantom duplicates when an MCP response times out but the server insert succeeded.' }
2433
2434
  },
2434
2435
  required: ['title']
2435
2436
  }
@@ -2469,6 +2470,35 @@ function getToolDefinitions() {
2469
2470
  required: ['post_id']
2470
2471
  }
2471
2472
  },
2473
+ {
2474
+ name: 'get_post_by_slug',
2475
+ description: 'Look up a single post by exact slug within a post type. Returns the post if found, or found:false if not. Use before create_post to check for slug collisions, or to resolve a slug into a post ID without scanning list_posts.',
2476
+ inputSchema: {
2477
+ type: 'object',
2478
+ properties: {
2479
+ slug: { type: 'string', description: 'Exact slug (post_name). Case-insensitive, sanitized server-side.' },
2480
+ post_type: { type: 'string', description: 'Post type slug. Default "post".' },
2481
+ status: { type: 'string', description: 'Comma-separated statuses to include. Default: publish,private,draft,pending (excludes trash).' }
2482
+ },
2483
+ required: ['slug']
2484
+ }
2485
+ },
2486
+ {
2487
+ name: 'change_post_type',
2488
+ 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.',
2489
+ inputSchema: {
2490
+ type: 'object',
2491
+ properties: {
2492
+ post_id: { type: 'number', description: 'WordPress post/page ID to migrate' },
2493
+ new_post_type: { type: 'string', description: 'Target post type slug. Must be registered.' },
2494
+ slug_override: { type: 'string', description: 'Optional. Force a specific slug. If omitted and the existing slug collides on the new post type, a unique suffix is added automatically.' },
2495
+ update_internal_links: { type: 'boolean', description: 'Rewrite old permalink → new permalink across all post_content. Default: true.' },
2496
+ create_redirect: { type: 'boolean', description: 'Create a RankMath 301 from the old permalink to the new one. Default: true. Requires RankMath active.' },
2497
+ flush_rewrites: { type: 'boolean', description: 'Flush WP rewrite rules after the change. Default: true.' }
2498
+ },
2499
+ required: ['post_id', 'new_post_type']
2500
+ }
2501
+ },
2472
2502
  // ── RankMath SEO ──
2473
2503
  {
2474
2504
  name: 'get_seo',
@@ -4794,11 +4824,37 @@ async function handleToolCall(name, args) {
4794
4824
  const body = { title: args.title };
4795
4825
  const passthrough = ['post_type', 'content', 'excerpt', 'status', 'slug',
4796
4826
  'parent', 'parent_slug', 'taxonomies', 'featured_image_id',
4797
- 'featured_image_basename', 'elementor', 'seo'];
4827
+ 'featured_image_basename', 'elementor', 'seo', 'dedupe_by_title'];
4798
4828
  for (const k of passthrough) if (args[k] !== undefined) body[k] = args[k];
4799
4829
  const r = await apiCall('/posts', 'POST', body);
4800
4830
  if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
4801
- return ok(`Post created!\nID: ${r.id} | Type: ${r.type} | Status: ${r.status}\nTitle: ${r.title}\nSlug: ${r.slug}\nURL: ${r.url}\nEdit: ${r.edit_url}`);
4831
+ const prefix = r.was_existing ? 'Post already existed (dedupe hit)' : 'Post created';
4832
+ 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}`);
4833
+ }
4834
+
4835
+ case 'get_post_by_slug': {
4836
+ const params = new URLSearchParams();
4837
+ params.set('slug', args.slug);
4838
+ if (args.post_type) params.set('post_type', args.post_type);
4839
+ if (args.status) params.set('status', args.status);
4840
+ const r = await apiCall(`/posts/by-slug?${params}`);
4841
+ if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
4842
+ if (!r.found) return ok(`No post with slug "${r.slug}" found in post type "${r.post_type}".`);
4843
+ return ok(`Found post!\nID: ${r.id} | Type: ${r.type} | Status: ${r.status}\nTitle: ${r.title}\nSlug: ${r.slug}\nURL: ${r.url}\nEdit: ${r.edit_url}`);
4844
+ }
4845
+
4846
+ case 'change_post_type': {
4847
+ const { post_id, ...body } = args;
4848
+ const r = await apiCall(`/posts/${post_id}/change-type`, 'POST', body);
4849
+ if (!r.success) return ok(`Failed: ${r.message || 'Unknown error'}`);
4850
+ let msg = `Post type changed!\nID: ${r.id}\n`;
4851
+ msg += `Type: ${r.old_post_type} → ${r.new_post_type}\n`;
4852
+ msg += `Permalink: ${r.old_permalink} → ${r.new_permalink}\n`;
4853
+ if (r.slug_changed) msg += `Slug: ${r.old_slug} → ${r.new_slug} (collision auto-resolved)\n`;
4854
+ msg += `Internal links rewritten: ${r.internal_links_updated}\n`;
4855
+ msg += `301 redirect created: ${r.redirect_created ? 'yes' : 'no'}\n`;
4856
+ msg += `Rewrite rules flushed: ${r.rewrites_flushed ? 'yes' : 'no'}`;
4857
+ return ok(msg);
4802
4858
  }
4803
4859
 
4804
4860
  case 'update_post': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noleemits/vision-builder-control-mcp",
3
- "version": "4.46.2",
3
+ "version": "4.47.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",