@adsim/wordpress-mcp-server 4.5.0 → 4.6.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/README.md +92 -11
- package/dxt/manifest.json +17 -6
- package/index.js +327 -164
- package/package.json +1 -1
- package/src/plugins/IPluginAdapter.js +95 -0
- package/src/plugins/adapters/acf/acfAdapter.js +181 -0
- package/src/plugins/adapters/elementor/elementorAdapter.js +176 -0
- package/src/plugins/contextGuard.js +57 -0
- package/src/plugins/registry.js +94 -0
- package/tests/unit/pluginLayer.test.js +151 -0
- package/tests/unit/plugins/acf/acfAdapter.test.js +205 -0
- package/tests/unit/plugins/acf/acfAdapter.write.test.js +157 -0
- package/tests/unit/plugins/contextGuard.test.js +51 -0
- package/tests/unit/plugins/elementor/elementorAdapter.test.js +206 -0
- package/tests/unit/plugins/iPluginAdapter.test.js +34 -0
- package/tests/unit/plugins/registry.test.js +84 -0
- package/tests/unit/tools/dynamicFiltering.test.js +136 -0
- package/tests/unit/tools/outputCompression.test.js +342 -0
- package/tests/unit/tools/site.test.js +3 -1
- package/tests/unit/tools/siteOptions.test.js +101 -0
package/index.js
CHANGED
|
@@ -29,12 +29,14 @@ import { parseImagesFromHtml, extractHeadings, extractInternalLinks as extractIn
|
|
|
29
29
|
import { summarizePost, applyContentFormat } from './src/utils/contentCompressor.js';
|
|
30
30
|
import { calculateReadabilityScore, extractHeadingsOutline, detectContentSections, extractTransitionWords, countPassiveSentences, buildTFIDFVectors, computeCosineSimilarity, findDuplicatePairs, extractEntities, computeTextDiff } from './src/contentAnalyzer.js';
|
|
31
31
|
import { detectSeoPlugin, getRenderedHead, parseRenderedHead } from './src/pluginDetector.js';
|
|
32
|
+
import { PluginRegistry } from './src/plugins/registry.js';
|
|
33
|
+
import { acfAdapter } from './src/plugins/adapters/acf/acfAdapter.js';
|
|
32
34
|
|
|
33
35
|
// ============================================================
|
|
34
36
|
// CONFIGURATION
|
|
35
37
|
// ============================================================
|
|
36
38
|
|
|
37
|
-
const VERSION = '
|
|
39
|
+
const VERSION = '4.6.0';
|
|
38
40
|
const VERBOSE = process.env.WP_MCP_VERBOSE === 'true' || process.argv.includes('--verbose');
|
|
39
41
|
const MAX_RETRIES = parseInt(process.env.WP_MCP_MAX_RETRIES || '3', 10);
|
|
40
42
|
const TIMEOUT_MS = parseInt(process.env.WP_MCP_TIMEOUT || '30000', 10);
|
|
@@ -418,6 +420,28 @@ async function healthCheck() {
|
|
|
418
420
|
} catch (e) { log.warn(`Health check: ${e.message}`); }
|
|
419
421
|
}
|
|
420
422
|
|
|
423
|
+
// ============================================================
|
|
424
|
+
// PLUGIN LAYER
|
|
425
|
+
// ============================================================
|
|
426
|
+
|
|
427
|
+
const pluginRegistry = new PluginRegistry();
|
|
428
|
+
|
|
429
|
+
const PLUGIN_ADAPTERS = [acfAdapter];
|
|
430
|
+
|
|
431
|
+
async function initializePluginLayer() {
|
|
432
|
+
try {
|
|
433
|
+
await pluginRegistry.initialize(wpApiCall);
|
|
434
|
+
for (const adapter of PLUGIN_ADAPTERS) {
|
|
435
|
+
if (pluginRegistry.isActive(adapter.id)) {
|
|
436
|
+
pluginRegistry.registerTools(adapter.id, adapter.getTools());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
log.info(JSON.stringify({ event: 'plugin_layer_initialized', summary: pluginRegistry.getSummary() }));
|
|
440
|
+
} catch (e) {
|
|
441
|
+
log.warn(`Plugin layer init failed (continuing without plugins): ${e.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
421
445
|
// ============================================================
|
|
422
446
|
// MCP SERVER
|
|
423
447
|
// ============================================================
|
|
@@ -436,7 +460,7 @@ const ORDERBY = ['date', 'relevance', 'id', 'title', 'slug', 'modified', 'author
|
|
|
436
460
|
const ORDERS = ['asc', 'desc'];
|
|
437
461
|
const MEDIA_TYPES = ['image', 'video', 'audio', 'application'];
|
|
438
462
|
const COMMENT_STATUSES = ['approved', 'hold', 'spam', 'trash'];
|
|
439
|
-
const TOOLS_COUNT =
|
|
463
|
+
const TOOLS_COUNT = 86;
|
|
440
464
|
|
|
441
465
|
function json(data) { return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }; }
|
|
442
466
|
function strip(html) { return (html || '').replace(/<[^>]*>/g, '').trim(); }
|
|
@@ -447,213 +471,229 @@ function strip(html) { return (html || '').replace(/<[^>]*>/g, '').trim(); }
|
|
|
447
471
|
|
|
448
472
|
const TOOLS_DEFINITIONS = [
|
|
449
473
|
// ── POSTS (6) ──
|
|
450
|
-
{ name: 'wp_list_posts', description: '
|
|
451
|
-
{ name: 'wp_get_post', description: '
|
|
452
|
-
{ name: 'wp_create_post', description: '
|
|
453
|
-
{ name: 'wp_update_post', description: '
|
|
454
|
-
{ name: 'wp_delete_post', description: '
|
|
455
|
-
{ name: 'wp_search', description: '
|
|
474
|
+
{ name: 'wp_list_posts', description: 'Use to browse/filter posts by status, category, tag, author, or search. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'publish' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' }, categories: { type: 'string' }, tags: { type: 'string' }, search: { type: 'string' }, author: { type: 'number' }, mode: { type: 'string', default: 'full', description: 'full=all fields, summary=id/title/slug/date/status/link only, ids_only=flat ID array' } }}},
|
|
475
|
+
{ name: 'wp_get_post', description: 'Use to read a single post with full content and meta. Read-only. Hint: use content_format=\'links_only\' for link audits (~800 chars vs 187k), \'text\' for rewrites, fields=[\'id\',\'title\',\'meta\'] for SEO-only.', inputSchema: { type: 'object', properties: { id: { type: 'number' }, fields: { type: 'array', items: { type: 'string' }, description: 'Return only specified fields (id,title,content,excerpt,slug,status,date,modified,categories,tags,author,featured_media,meta,link). Omit for all.' }, content_format: { type: 'string', default: 'html', description: 'html=raw HTML (truncated at WP_MAX_CONTENT_CHARS), text=plain text, links_only=extract internal links only' } }, required: ['id'] }},
|
|
476
|
+
{ name: 'wp_create_post', description: 'Use to create a post (defaults to draft). Accepts title, HTML content, categories, tags, featured_media, meta. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { title: { type: 'string' }, content: { type: 'string' }, status: { type: 'string', default: 'draft' }, excerpt: { type: 'string' }, categories: { type: 'array', items: { type: 'number' } }, tags: { type: 'array', items: { type: 'number' } }, slug: { type: 'string' }, featured_media: { type: 'number' }, meta: { type: 'object' }, author: { type: 'number' } }, required: ['title', 'content'] }},
|
|
477
|
+
{ name: 'wp_update_post', description: 'Use to modify any post field — only provided fields change. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { id: { type: 'number' }, title: { type: 'string' }, content: { type: 'string' }, status: { type: 'string' }, excerpt: { type: 'string' }, categories: { type: 'array', items: { type: 'number' } }, tags: { type: 'array', items: { type: 'number' } }, slug: { type: 'string' }, featured_media: { type: 'number' }, meta: { type: 'object' }, author: { type: 'number' } }, required: ['id'] }},
|
|
478
|
+
{ name: 'wp_delete_post', description: 'Use to trash or permanently delete a post. Write — blocked by WP_READ_ONLY, WP_DISABLE_DELETE.', inputSchema: { type: 'object', properties: { id: { type: 'number' }, force: { type: 'boolean', default: false }, confirmation_token: { type: 'string', description: 'Confirmation token returned by the first call when WP_CONFIRM_DESTRUCTIVE=true' } }, required: ['id'] }},
|
|
479
|
+
{ name: 'wp_search', description: 'Use for full-text search across all content types. Returns id, title, url, type. Read-only.', inputSchema: { type: 'object', properties: { search: { type: 'string' }, per_page: { type: 'number', default: 10 }, type: { type: 'string', default: '' } }, required: ['search'] }},
|
|
456
480
|
|
|
457
481
|
// ── APPROVAL WORKFLOW (3) ──
|
|
458
|
-
{ name: 'wp_submit_for_review', description: '
|
|
459
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
460
|
-
{ name: 'wp_approve_post', description: '
|
|
461
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
462
|
-
{ name: 'wp_reject_post', description: '
|
|
463
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
482
|
+
{ name: 'wp_submit_for_review', description: 'Use to transition a draft post to pending status for editorial review. Write — blocked by WP_READ_ONLY.',
|
|
483
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, note: { type: 'string', description: 'Optional review note (stored as post meta _mcp_review_note)' } }, required: ['id'] }},
|
|
484
|
+
{ name: 'wp_approve_post', description: 'Use to transition a pending post to published (editor/admin action). Write — blocked by WP_READ_ONLY, WP_DRAFT_ONLY.',
|
|
485
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
486
|
+
{ name: 'wp_reject_post', description: 'Use to return a pending post to draft with a mandatory rejection reason. Write — blocked by WP_READ_ONLY.',
|
|
487
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, reason: { type: 'string', description: 'Reason for rejection (stored as post meta _mcp_rejection_reason)' } }, required: ['id', 'reason'] }},
|
|
464
488
|
|
|
465
489
|
// ── PAGES (4) ──
|
|
466
|
-
{ name: 'wp_list_pages', description: '
|
|
467
|
-
{ name: 'wp_get_page', description: '
|
|
468
|
-
{ name: 'wp_create_page', description: '
|
|
469
|
-
{ name: 'wp_update_page', description: '
|
|
490
|
+
{ name: 'wp_list_pages', description: 'Use to browse pages with hierarchy and templates. Supports parent filter, menu_order sort. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'publish' }, parent: { type: 'number' }, orderby: { type: 'string', default: 'menu_order' }, order: { type: 'string', default: 'asc' }, search: { type: 'string' }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
491
|
+
{ name: 'wp_get_page', description: 'Use to read a single page with content and template. Read-only. Warning: Elementor pages can exceed 100k chars.', inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
492
|
+
{ name: 'wp_create_page', description: 'Use to create a page (defaults to draft). Supports parent, template, menu_order. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { title: { type: 'string' }, content: { type: 'string' }, status: { type: 'string', default: 'draft' }, parent: { type: 'number', default: 0 }, template: { type: 'string' }, menu_order: { type: 'number', default: 0 }, excerpt: { type: 'string' }, slug: { type: 'string' }, featured_media: { type: 'number' }, meta: { type: 'object' } }, required: ['title', 'content'] }},
|
|
493
|
+
{ name: 'wp_update_page', description: 'Use to modify any page field. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { id: { type: 'number' }, title: { type: 'string' }, content: { type: 'string' }, status: { type: 'string' }, parent: { type: 'number' }, template: { type: 'string' }, menu_order: { type: 'number' }, excerpt: { type: 'string' }, slug: { type: 'string' }, featured_media: { type: 'number' }, meta: { type: 'object' } }, required: ['id'] }},
|
|
470
494
|
|
|
471
495
|
// ── MEDIA (3) ──
|
|
472
|
-
{ name: 'wp_list_media', description: '
|
|
473
|
-
{ name: 'wp_get_media', description: '
|
|
474
|
-
{ name: 'wp_upload_media', description: '
|
|
496
|
+
{ name: 'wp_list_media', description: 'Use to browse media library by type (image/video/audio). Returns id, URL, alt_text, dimensions. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, media_type: { type: 'string' }, search: { type: 'string' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
497
|
+
{ name: 'wp_get_media', description: 'Use to get full media details with all available sizes. Read-only.', inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
498
|
+
{ name: 'wp_upload_media', description: 'Use to upload a file from a public URL to the media library. Set alt_text for image SEO. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { url: { type: 'string' }, filename: { type: 'string' }, title: { type: 'string' }, alt_text: { type: 'string' }, caption: { type: 'string' }, description: { type: 'string' }, post_id: { type: 'number' } }, required: ['url'] }},
|
|
475
499
|
|
|
476
500
|
// ── TAXONOMIES (3) ──
|
|
477
|
-
{ name: 'wp_list_categories', description: '
|
|
478
|
-
{ name: 'wp_list_tags', description: '
|
|
479
|
-
{ name: 'wp_create_taxonomy_term', description: '
|
|
501
|
+
{ name: 'wp_list_categories', description: 'Use to list categories with hierarchy, post count, and descriptions. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 100 }, page: { type: 'number', default: 1 }, parent: { type: 'number' }, search: { type: 'string' }, orderby: { type: 'string', default: 'name' }, hide_empty: { type: 'boolean', default: false }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
502
|
+
{ name: 'wp_list_tags', description: 'Use to list tags with post count. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 100 }, page: { type: 'number', default: 1 }, search: { type: 'string' }, orderby: { type: 'string', default: 'name' }, hide_empty: { type: 'boolean', default: false }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
503
|
+
{ name: 'wp_create_taxonomy_term', description: 'Use to create a new category or tag. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { taxonomy: { type: 'string' }, name: { type: 'string' }, slug: { type: 'string' }, description: { type: 'string' }, parent: { type: 'number' } }, required: ['taxonomy', 'name'] }},
|
|
480
504
|
|
|
481
505
|
// ── COMMENTS (2) ──
|
|
482
|
-
{ name: 'wp_list_comments', description: '
|
|
483
|
-
{ name: 'wp_create_comment', description: '
|
|
506
|
+
{ name: 'wp_list_comments', description: 'Use to list comments filtered by post or status (approved/hold/spam). Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, post: { type: 'number' }, status: { type: 'string' }, orderby: { type: 'string', default: 'date_gmt' }, order: { type: 'string', default: 'desc' }, search: { type: 'string' }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
507
|
+
{ name: 'wp_create_comment', description: 'Use to post a comment or reply on any post. Write — blocked by WP_READ_ONLY.', inputSchema: { type: 'object', properties: { post: { type: 'number' }, content: { type: 'string' }, parent: { type: 'number', default: 0 }, author_name: { type: 'string' }, author_email: { type: 'string' }, status: { type: 'string', default: 'approved' } }, required: ['post', 'content'] }},
|
|
484
508
|
|
|
485
509
|
// ── CUSTOM POST TYPES (2) ──
|
|
486
|
-
{ name: 'wp_list_post_types', description: '
|
|
487
|
-
{ name: 'wp_list_custom_posts', description: '
|
|
510
|
+
{ name: 'wp_list_post_types', description: 'Use to discover all registered post types including custom ones (products, portfolio, events). Read-only.', inputSchema: { type: 'object', properties: {} }},
|
|
511
|
+
{ name: 'wp_list_custom_posts', description: 'Use to list any custom post type (products, portfolio, events). Requires post_type slug. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { post_type: { type: 'string' }, per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'publish' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' }, search: { type: 'string' }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }, required: ['post_type'] }},
|
|
488
512
|
|
|
489
513
|
// ── USERS (1) ──
|
|
490
|
-
{ name: 'wp_list_users', description: '
|
|
514
|
+
{ name: 'wp_list_users', description: 'Use to list site users with roles. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.', inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, roles: { type: 'string' }, search: { type: 'string' }, orderby: { type: 'string', default: 'name' }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
491
515
|
|
|
492
516
|
// ── MULTI-TARGET (1) ──
|
|
493
|
-
{ name: 'wp_set_target', description: '
|
|
517
|
+
{ name: 'wp_set_target', description: 'Use to switch active WordPress site in multi-target mode. Write.',
|
|
494
518
|
inputSchema: { type: 'object', properties: { site: { type: 'string', description: 'Site key from targets config' } }, required: ['site'] }},
|
|
495
519
|
|
|
496
|
-
// ── SITE INFO (
|
|
497
|
-
{ name: 'wp_site_info', description: '
|
|
520
|
+
// ── SITE INFO (2) ──
|
|
521
|
+
{ name: 'wp_site_info', description: 'Use first to discover site config, user, post types, governance flags, and available targets. Read-only.',
|
|
498
522
|
inputSchema: { type: 'object', properties: {} }},
|
|
523
|
+
{ name: 'wp_get_site_options', description: 'Use to read WordPress site settings (title, tagline, language, timezone, etc.) via /wp/v2/settings. Requires manage_options. Read-only.',
|
|
524
|
+
inputSchema: { type: 'object', properties: { keys: { type: 'array', items: { type: 'string' }, description: 'Return only these option keys (returns all if omitted)' } }}},
|
|
499
525
|
|
|
500
526
|
// ── SEO METADATA (3) ──
|
|
501
|
-
{ name: 'wp_get_seo_meta', description: '
|
|
502
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
503
|
-
{ name: 'wp_update_seo_meta', description: '
|
|
504
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
505
|
-
{ name: 'wp_audit_seo', description: '
|
|
527
|
+
{ name: 'wp_get_seo_meta', description: 'Use to read SEO title, description, focus keyword, canonical, robots, OG for one post. Auto-detects Yoast/RankMath/SEOPress/AIOSEO. Read-only. Hint: prefer this over wp_get_post for SEO-only workflows.',
|
|
528
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' } }, required: ['id'] }},
|
|
529
|
+
{ name: 'wp_update_seo_meta', description: 'Use to update SEO title, description, focus keyword, canonical, or robots. Auto-detects SEO plugin. Write — blocked by WP_READ_ONLY.',
|
|
530
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' }, title: { type: 'string', description: 'SEO title' }, description: { type: 'string', description: 'Meta description' }, focus_keyword: { type: 'string', description: 'Focus keyword' }, canonical_url: { type: 'string' }, robots_noindex: { type: 'boolean' }, robots_nofollow: { type: 'boolean' } }, required: ['id'] }},
|
|
531
|
+
{ name: 'wp_audit_seo', description: 'Use for quick bulk SEO scoring (0-100) across posts or pages. Checks missing titles, descriptions, keywords, and length issues. Read-only. Hint: use wp_audit_rendered_seo for rendered-vs-stored comparison.',
|
|
506
532
|
inputSchema: { type: 'object', properties: { post_type: { type: 'string', default: 'post', description: 'post or page' }, per_page: { type: 'number', default: 20, description: 'Number of posts to audit (max 100)' }, status: { type: 'string', default: 'publish' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' } }}},
|
|
507
533
|
|
|
508
534
|
// ── PLUGINS (3) ──
|
|
509
|
-
{ name: 'wp_list_plugins', description: '
|
|
510
|
-
inputSchema: { type: 'object', properties: { search: { type: 'string'
|
|
511
|
-
{ name: 'wp_activate_plugin', description: '
|
|
535
|
+
{ name: 'wp_list_plugins', description: 'Use to list installed plugins with status and version. Requires Administrator role. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.',
|
|
536
|
+
inputSchema: { type: 'object', properties: { search: { type: 'string' }, status: { type: 'string', enum: ['active', 'inactive', 'all'], default: 'all' }, per_page: { type: 'number', default: 20 }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
537
|
+
{ name: 'wp_activate_plugin', description: 'Use to activate a plugin. Write — blocked by WP_READ_ONLY, WP_DISABLE_PLUGIN_MANAGEMENT.',
|
|
512
538
|
inputSchema: { type: 'object', properties: { plugin: { type: 'string', description: 'Plugin slug/file (e.g. "akismet/akismet.php"). Use wp_list_plugins to find the correct value.' } }, required: ['plugin'] }},
|
|
513
|
-
{ name: 'wp_deactivate_plugin', description: '
|
|
539
|
+
{ name: 'wp_deactivate_plugin', description: 'Use to deactivate a plugin. Write — blocked by WP_READ_ONLY, WP_DISABLE_PLUGIN_MANAGEMENT.',
|
|
514
540
|
inputSchema: { type: 'object', properties: { plugin: { type: 'string', description: 'Plugin slug/file (e.g. "akismet/akismet.php"). Use wp_list_plugins to find the correct value.' } }, required: ['plugin'] }},
|
|
515
541
|
|
|
516
542
|
// ── THEMES (2) ──
|
|
517
|
-
{ name: 'wp_list_themes', description: '
|
|
518
|
-
inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'inactive', 'all'], default: 'all',
|
|
519
|
-
{ name: 'wp_get_theme', description: '
|
|
543
|
+
{ name: 'wp_list_themes', description: 'Use to list installed themes with active theme detection. Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.',
|
|
544
|
+
inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'inactive', 'all'], default: 'all' }, per_page: { type: 'number', default: 20 }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }}},
|
|
545
|
+
{ name: 'wp_get_theme', description: 'Use to get theme details by stylesheet slug. Read-only.',
|
|
520
546
|
inputSchema: { type: 'object', properties: { stylesheet: { type: 'string', description: 'Theme stylesheet slug (e.g. "twentytwentyfour"). Use wp_list_themes to find the correct value.' } }, required: ['stylesheet'] }},
|
|
521
547
|
|
|
522
548
|
// ── REVISIONS (4) ──
|
|
523
|
-
{ name: 'wp_list_revisions', description: '
|
|
524
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
525
|
-
{ name: 'wp_get_revision', description: '
|
|
526
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
527
|
-
{ name: 'wp_restore_revision', description: '
|
|
528
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
529
|
-
{ name: 'wp_delete_revision', description: '
|
|
530
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
549
|
+
{ name: 'wp_list_revisions', description: 'Use to list revisions of a post or page (metadata only). Read-only. Hint: use mode=\'summary\' for listings, \'ids_only\' for batch ops.',
|
|
550
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'], default: 'post' }, per_page: { type: 'number', default: 10 }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'], default: 'full', description: 'full=all fields, summary=key fields only, ids_only=flat ID array' } }, required: ['post_id'] }},
|
|
551
|
+
{ name: 'wp_get_revision', description: 'Use to read a specific revision with full content. Read-only.',
|
|
552
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, revision_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'], default: 'post' } }, required: ['post_id', 'revision_id'] }},
|
|
553
|
+
{ name: 'wp_restore_revision', description: 'Use to restore a post to a previous revision. Write — blocked by WP_READ_ONLY.',
|
|
554
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, revision_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'], default: 'post' } }, required: ['post_id', 'revision_id'] }},
|
|
555
|
+
{ name: 'wp_delete_revision', description: 'Use to permanently delete a revision. Write — blocked by WP_READ_ONLY, WP_DISABLE_DELETE, WP_CONFIRM_DESTRUCTIVE.',
|
|
556
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, revision_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'], default: 'post' }, confirmation_token: { type: 'string', description: 'Confirmation token returned by the first call when WP_CONFIRM_DESTRUCTIVE=true' } }, required: ['post_id', 'revision_id'] }},
|
|
531
557
|
|
|
532
558
|
// ── LINK ANALYSIS (2) ──
|
|
533
|
-
{ name: 'wp_analyze_links', description: '
|
|
534
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
535
|
-
{ name: 'wp_suggest_internal_links', description: '
|
|
536
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
559
|
+
{ name: 'wp_analyze_links', description: 'Use to audit all internal and external links in a single post via HEAD requests. Returns broken/warning/ok status per link. Read-only.',
|
|
560
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, check_broken: { type: 'boolean', default: true, description: 'Check broken internal links via HEAD request' }, timeout_ms: { type: 'number', default: 5000, description: 'Timeout per HEAD request in ms' } }, required: ['post_id'] }},
|
|
561
|
+
{ name: 'wp_suggest_internal_links', description: 'Use to get scored internal link suggestions for a post based on keyword relevance, categories, and freshness. Read-only. Hint: call wp_get_post with content_format=\'links_only\' first to map existing links.',
|
|
562
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, max_suggestions: { type: 'number', default: 5, description: 'Number of suggestions (1-10)' }, focus_keywords: { type: 'array', items: { type: 'string' }, description: 'Additional keywords to match against' }, exclude_already_linked: { type: 'boolean', default: true, description: 'Exclude posts already linked from the current post' } }, required: ['post_id'] }},
|
|
537
563
|
|
|
538
564
|
// ── WOOCOMMERCE (6) ──
|
|
539
|
-
{ name: 'wc_list_products', description: '
|
|
540
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'any', description: 'any, draft, pending, private, publish' }, search: { type: 'string' }, category: { type: 'number'
|
|
541
|
-
{ name: 'wc_get_product', description: '
|
|
542
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
543
|
-
{ name: 'wc_list_orders', description: '
|
|
544
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'any', description: 'any, pending, processing, on-hold, completed, cancelled, refunded, failed' }, customer: { type: 'number'
|
|
545
|
-
{ name: 'wc_get_order', description: '
|
|
546
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
547
|
-
{ name: 'wc_list_customers', description: '
|
|
565
|
+
{ name: 'wc_list_products', description: 'Use to browse WooCommerce products. Filter by status, category, or search. Read-only.',
|
|
566
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'any', description: 'any, draft, pending, private, publish' }, search: { type: 'string' }, category: { type: 'number' }, orderby: { type: 'string', default: 'date', description: 'date, id, title, price, popularity' }, order: { type: 'string', default: 'desc' } }}},
|
|
567
|
+
{ name: 'wc_get_product', description: 'Use to get full product details including variations summary. Read-only.',
|
|
568
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
569
|
+
{ name: 'wc_list_orders', description: 'Use to browse WooCommerce orders. Filter by status or customer. Read-only.',
|
|
570
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, status: { type: 'string', default: 'any', description: 'any, pending, processing, on-hold, completed, cancelled, refunded, failed' }, customer: { type: 'number' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' } }}},
|
|
571
|
+
{ name: 'wc_get_order', description: 'Use to get order details with line items, shipping, billing, and payment info. Read-only.',
|
|
572
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
573
|
+
{ name: 'wc_list_customers', description: 'Use to list WooCommerce customers with search and role filtering. Read-only.',
|
|
548
574
|
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 10 }, page: { type: 'number', default: 1 }, search: { type: 'string' }, orderby: { type: 'string', default: 'date_created' }, order: { type: 'string', default: 'desc' }, role: { type: 'string', default: 'customer' } }}},
|
|
549
|
-
{ name: 'wc_price_guardrail', description: '
|
|
550
|
-
inputSchema: { type: 'object', properties: { product_id: { type: 'number'
|
|
575
|
+
{ name: 'wc_price_guardrail', description: 'Use BEFORE changing a product price. Analyzes safety of proposed price change. Read-only, always allowed.',
|
|
576
|
+
inputSchema: { type: 'object', properties: { product_id: { type: 'number' }, new_price: { type: 'number', description: 'Proposed new price' }, threshold_percent: { type: 'number', default: 20, description: 'Maximum allowed change percentage (default 20)' } }, required: ['product_id', 'new_price'] }},
|
|
551
577
|
|
|
552
578
|
// ── WOOCOMMERCE INTELLIGENCE (4) ──
|
|
553
|
-
{ name: 'wc_inventory_alert', description: '
|
|
554
|
-
inputSchema: { type: 'object', properties: { threshold: { type: 'number', default: 5, description: 'Stock quantity threshold (default 5)' }, per_page: { type: 'number', default: 50
|
|
555
|
-
{ name: 'wc_order_intelligence', description: '
|
|
556
|
-
inputSchema: { type: 'object', properties: { customer_id: { type: 'number'
|
|
557
|
-
{ name: 'wc_seo_product_audit', description: '
|
|
558
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 20
|
|
559
|
-
{ name: 'wc_suggest_product_links', description: '
|
|
560
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
579
|
+
{ name: 'wc_inventory_alert', description: 'Use to identify low-stock and out-of-stock products below a threshold, sorted by urgency. Read-only.',
|
|
580
|
+
inputSchema: { type: 'object', properties: { threshold: { type: 'number', default: 5, description: 'Stock quantity threshold (default 5)' }, per_page: { type: 'number', default: 50 }, include_out_of_stock: { type: 'boolean', default: true, description: 'Include out-of-stock products' } }}},
|
|
581
|
+
{ name: 'wc_order_intelligence', description: 'Use to analyze a customer\'s purchase history: lifetime value, average order, favorite products, frequency. Read-only.',
|
|
582
|
+
inputSchema: { type: 'object', properties: { customer_id: { type: 'number' } }, required: ['customer_id'] }},
|
|
583
|
+
{ name: 'wc_seo_product_audit', description: 'Use to audit WooCommerce product listings for SEO issues (missing descriptions, images, alt text, generic slugs). Read-only.',
|
|
584
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 20 }, page: { type: 'number', default: 1 } }}},
|
|
585
|
+
{ name: 'wc_suggest_product_links', description: 'Use to suggest WooCommerce products to link from a blog post based on keyword relevance. Read-only.',
|
|
586
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, max_suggestions: { type: 'number', default: 3, description: 'Maximum suggestions (1-5)' } }, required: ['post_id'] }},
|
|
561
587
|
|
|
562
588
|
// ── WOOCOMMERCE WRITE (3) ──
|
|
563
|
-
{ name: 'wc_update_product', description: '
|
|
564
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
565
|
-
{ name: 'wc_update_stock', description: '
|
|
566
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
567
|
-
{ name: 'wc_update_order_status', description: '
|
|
568
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
589
|
+
{ name: 'wc_update_product', description: 'Use to update product fields (title, description, price, stock, status). Write — blocked by WP_READ_ONLY. Hint: call wc_price_guardrail first for price changes.',
|
|
590
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, name: { type: 'string' }, description: { type: 'string' }, short_description: { type: 'string' }, regular_price: { type: 'string', description: 'Format "19.99"' }, sale_price: { type: 'string' }, status: { type: 'string', description: 'publish, draft, or private' }, price_guardrail_confirmed: { type: 'boolean', default: false, description: 'Set true to bypass price guardrail after calling wc_price_guardrail' } }, required: ['id'] }},
|
|
591
|
+
{ name: 'wc_update_stock', description: 'Use to update stock quantity of a product or variation. Write — blocked by WP_READ_ONLY.',
|
|
592
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, stock_quantity: { type: 'number' }, variation_id: { type: 'number', description: 'Variation ID (for variable products)' } }, required: ['id', 'stock_quantity'] }},
|
|
593
|
+
{ name: 'wc_update_order_status', description: 'Use to transition order status (e.g. processing → completed). Write — blocked by WP_READ_ONLY.',
|
|
594
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, status: { type: 'string', description: 'processing, completed, cancelled, refunded, on-hold, failed' }, note: { type: 'string' } }, required: ['id', 'status'] }},
|
|
569
595
|
|
|
570
596
|
// ── SEO ADVANCED (3) ──
|
|
571
|
-
{ name: 'wp_audit_media_seo', description: '
|
|
572
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 50
|
|
573
|
-
{ name: 'wp_find_orphan_pages', description: '
|
|
574
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 100
|
|
575
|
-
{ name: 'wp_audit_heading_structure', description: '
|
|
576
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
597
|
+
{ name: 'wp_audit_media_seo', description: 'Use when checking image SEO. Scans media library for missing/short alt text and bad filenames. Returns per-image scores + fix list. Read-only.',
|
|
598
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 50 }, page: { type: 'number', default: 1 }, post_id: { type: 'number', description: 'Also scan inline images from this post' } }}},
|
|
599
|
+
{ name: 'wp_find_orphan_pages', description: 'Use to find posts with zero inbound internal links, sorted by word count. Read-only. Hint: combine with wp_suggest_internal_links to fix orphans.',
|
|
600
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 100 }, exclude_ids: { type: 'array', items: { type: 'number' }, description: 'Page IDs to exclude from orphan check' }, min_words: { type: 'number', default: 0, description: 'Minimum word count to include in results' } }}},
|
|
601
|
+
{ name: 'wp_audit_heading_structure', description: 'Use to check H1-H6 hierarchy in a single post. Detects H1 in body, level skips, empty headings. Read-only.',
|
|
602
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' }, focus_keyword: { type: 'string', description: 'Keyword to check in H2 headings' } }, required: ['id'] }},
|
|
577
603
|
|
|
578
604
|
// ── SEO ADVANCED v4.1 (3) ──
|
|
579
|
-
{ name: 'wp_find_thin_content', description: '
|
|
580
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 100
|
|
581
|
-
{ name: 'wp_audit_canonicals', description: '
|
|
582
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 50
|
|
583
|
-
{ name: 'wp_analyze_eeat_signals', description: '
|
|
584
|
-
inputSchema: { type: 'object', properties: { post_ids: { type: 'array', items: { type: 'number' }, description: 'Specific post IDs
|
|
605
|
+
{ name: 'wp_find_thin_content', description: 'Use to surface short/low-quality posts below a word count threshold. Classifies severity. Read-only.',
|
|
606
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 100 }, min_words: { type: 'number', default: 300, description: 'Threshold for "too short"' }, critical_words: { type: 'number', default: 150, description: 'Threshold for "very short"' }, max_age_days: { type: 'number', default: 730, description: 'Days since update to flag as outdated' }, include_uncategorized: { type: 'boolean', default: true, description: 'Flag uncategorized posts' }, post_type: { type: 'string', default: 'post', description: 'post or page' } }}},
|
|
607
|
+
{ name: 'wp_audit_canonicals', description: 'Use to validate canonical URLs across posts/pages. Detects missing, mismatched, or staging URLs. Auto-detects SEO plugin. Read-only.',
|
|
608
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 50 }, post_type: { type: 'string', default: 'post', description: 'post, page, or both' }, check_staging_patterns: { type: 'boolean', default: true, description: 'Detect staging/dev URLs' } }}},
|
|
609
|
+
{ name: 'wp_analyze_eeat_signals', description: 'Use to score E-E-A-T per post (0-100): author bio, dates, citations, word count, structured data. Read-only.',
|
|
610
|
+
inputSchema: { type: 'object', properties: { post_ids: { type: 'array', items: { type: 'number' }, description: 'Specific post IDs (if empty, audits latest N)' }, limit: { type: 'number', default: 10 }, post_type: { type: 'string', default: 'post', description: 'post or page' }, authoritative_domains: { type: 'array', items: { type: 'string' }, default: ['wikipedia.org', 'gov', 'edu', 'who.int', 'pubmed'], description: 'Domains considered authoritative' } }}},
|
|
585
611
|
|
|
586
612
|
// ── SEO ADVANCED v4.2 (4) ──
|
|
587
|
-
{ name: 'wp_find_broken_internal_links', description: '
|
|
588
|
-
inputSchema: { type: 'object', properties: { limit_posts: { type: 'number', default: 20
|
|
589
|
-
{ name: 'wp_find_keyword_cannibalization', description: '
|
|
590
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 200
|
|
591
|
-
{ name: 'wp_audit_taxonomies', description: '
|
|
592
|
-
inputSchema: { type: 'object', properties: { check_tags: { type: 'boolean', default: true
|
|
593
|
-
{ name: 'wp_audit_outbound_links', description: '
|
|
594
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 30
|
|
613
|
+
{ name: 'wp_find_broken_internal_links', description: 'Use to check internal links via HEAD requests. Returns broken (4xx), redirected (3xx), and slow links. Read-only.',
|
|
614
|
+
inputSchema: { type: 'object', properties: { limit_posts: { type: 'number', default: 20 }, batch_size: { type: 'number', default: 5, description: 'Links per batch (1-10)' }, timeout_ms: { type: 'number', default: 5000, description: 'Timeout per HEAD request (1000-30000)' }, delay_ms: { type: 'number', default: 200, description: 'Delay between batches (0-2000)' }, post_type: { type: 'string', default: 'post', description: 'post, page, or both' }, include_redirects: { type: 'boolean', default: true, description: 'Include 301/302 redirects in results' } }}},
|
|
615
|
+
{ name: 'wp_find_keyword_cannibalization', description: 'Use to detect posts competing on the same focus keyword. Groups conflicts, flags weakest post. Read-only.',
|
|
616
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 200 }, post_type: { type: 'string', default: 'post', description: 'post, page, or both' }, similarity_mode: { type: 'string', default: 'normalized', description: 'exact or normalized keyword matching' }, min_group_size: { type: 'number', default: 2, description: 'Minimum articles per group (min 2)' } }}},
|
|
617
|
+
{ name: 'wp_audit_taxonomies', description: 'Use to detect taxonomy bloat: empty/single-post terms, near-duplicates, missing descriptions. Read-only.',
|
|
618
|
+
inputSchema: { type: 'object', properties: { check_tags: { type: 'boolean', default: true }, check_categories: { type: 'boolean', default: true }, min_posts_threshold: { type: 'number', default: 2, description: 'Minimum posts per term' }, detect_duplicates: { type: 'boolean', default: true, description: 'Detect near-duplicate terms via Levenshtein' } }}},
|
|
619
|
+
{ name: 'wp_audit_outbound_links', description: 'Use to analyze external link profile per post. Detects over-linking, missing nofollow, broken external URLs. Read-only.',
|
|
620
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 30 }, post_type: { type: 'string', default: 'post', description: 'post or page' }, min_outbound: { type: 'number', default: 1, description: 'Minimum outbound links threshold' }, max_outbound: { type: 'number', default: 15, description: 'Maximum outbound links before dilution warning' }, authoritative_domains: { type: 'array', items: { type: 'string' }, default: ['wikipedia.org', 'gov', 'edu', 'who.int', 'pubmed.ncbi'], description: 'Domains considered authoritative' } }}},
|
|
595
621
|
|
|
596
622
|
// ── CONTENT INTELLIGENCE v4.4 (2) ──
|
|
597
|
-
{ name: 'wp_get_content_brief', description: '
|
|
598
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
599
|
-
{ name: 'wp_extract_post_outline', description: '
|
|
600
|
-
inputSchema: { type: 'object', properties: { category_id: { type: 'number'
|
|
623
|
+
{ name: 'wp_get_content_brief', description: 'Use to get a compact content brief in 1 call: title, SEO meta, headings, word count, links, categories. Read-only. Hint: start here before writing or rewriting content.',
|
|
624
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' } }, required: ['id'] }},
|
|
625
|
+
{ name: 'wp_extract_post_outline', description: 'Use to extract H1-H4 outline from N posts in a category as a reference template for new content. Read-only.',
|
|
626
|
+
inputSchema: { type: 'object', properties: { category_id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' }, limit: { type: 'number', default: 10 } }, required: ['category_id'] }},
|
|
601
627
|
|
|
602
628
|
// ── CONTENT INTELLIGENCE v4.4 Week 2 (3) ──
|
|
603
|
-
{ name: 'wp_audit_readability', description: '
|
|
604
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
605
|
-
{ name: 'wp_audit_update_frequency', description: '
|
|
606
|
-
inputSchema: { type: 'object', properties: { days_threshold: { type: 'number', description: 'Flag posts not modified in X days (default 180)' }, limit: { type: 'number'
|
|
607
|
-
{ name: 'wp_build_link_map', description: '
|
|
608
|
-
inputSchema: { type: 'object', properties: { post_type: { type: 'string', enum: ['post', 'page', 'both']
|
|
629
|
+
{ name: 'wp_audit_readability', description: 'Use to score text readability (Flesch-Kincaid adapted). Returns transition density and passive ratio. Read-only.',
|
|
630
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] }, category_id: { type: 'number' }, min_words: { type: 'number', description: 'Minimum word count to include (default 100)' } }}},
|
|
631
|
+
{ name: 'wp_audit_update_frequency', description: 'Use to find stale posts not updated since N days, cross-referenced with SEO score. Read-only.',
|
|
632
|
+
inputSchema: { type: 'object', properties: { days_threshold: { type: 'number', description: 'Flag posts not modified in X days (default 180)' }, limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] }, include_seo_score: { type: 'boolean', description: 'Cross-reference with SEO metadata quality (default true)' } }}},
|
|
633
|
+
{ name: 'wp_build_link_map', description: 'Use to generate full internal link matrix with simplified PageRank scores per post. Read-only.',
|
|
634
|
+
inputSchema: { type: 'object', properties: { post_type: { type: 'string', enum: ['post', 'page', 'both'] }, limit: { type: 'number' }, category_id: { type: 'number' } }}},
|
|
609
635
|
|
|
610
636
|
// ── CONTENT INTELLIGENCE v4.4 Week 3 (3) ──
|
|
611
|
-
{ name: 'wp_audit_anchor_texts', description: '
|
|
612
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
613
|
-
{ name: 'wp_audit_schema_markup', description: '
|
|
614
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
615
|
-
{ name: 'wp_audit_content_structure', description: '
|
|
616
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
637
|
+
{ name: 'wp_audit_anchor_texts', description: 'Use to check internal link anchor diversity. Detects generic (\'click here\') and over-optimized anchors. Read-only.',
|
|
638
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
639
|
+
{ name: 'wp_audit_schema_markup', description: 'Use to detect and validate JSON-LD in post HTML content (Article, FAQ, HowTo, LocalBusiness). Read-only. Hint: use wp_audit_schema_plugins for plugin-native schema instead.',
|
|
640
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
641
|
+
{ name: 'wp_audit_content_structure', description: 'Use to analyze post structure: intro/body/conclusion ratio, FAQ presence, TOC, lists, tables. Read-only.',
|
|
642
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] }, category_id: { type: 'number' } }}},
|
|
617
643
|
|
|
618
644
|
// ── CONTENT INTELLIGENCE v4.4 Batch 4A (4) ──
|
|
619
|
-
{ name: 'wp_find_duplicate_content', description: '
|
|
620
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
621
|
-
{ name: 'wp_find_content_gaps', description: '
|
|
622
|
-
inputSchema: { type: 'object', properties: { min_posts: { type: 'number', description: 'Minimum posts per term to NOT be flagged (default 3)' }, taxonomy: { type: 'string', enum: ['category', 'post_tag', 'both']
|
|
623
|
-
{ name: 'wp_extract_faq_blocks', description: '
|
|
624
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
625
|
-
{ name: 'wp_audit_cta_presence', description: '
|
|
626
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
645
|
+
{ name: 'wp_find_duplicate_content', description: 'Use to detect near-duplicate posts via TF-IDF cosine similarity. Read-only.',
|
|
646
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] }, category_id: { type: 'number' }, similarity_threshold: { type: 'number', description: 'Minimum similarity to flag (0.0-1.0, default 0.7)' } }}},
|
|
647
|
+
{ name: 'wp_find_content_gaps', description: 'Use to find under-represented taxonomy terms (< N posts) as content creation opportunities. Read-only.',
|
|
648
|
+
inputSchema: { type: 'object', properties: { min_posts: { type: 'number', description: 'Minimum posts per term to NOT be flagged (default 3)' }, taxonomy: { type: 'string', enum: ['category', 'post_tag', 'both'] }, exclude_empty: { type: 'boolean', description: 'Exclude terms with 0 posts (default false)' } }}},
|
|
649
|
+
{ name: 'wp_extract_faq_blocks', description: 'Use to inventory all FAQ blocks (Gutenberg + schema JSON-LD) across the corpus. Read-only.',
|
|
650
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
651
|
+
{ name: 'wp_audit_cta_presence', description: 'Use to detect presence/absence of CTAs (contact links, forms, buttons) per post. Read-only.',
|
|
652
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] }, category_id: { type: 'number' } }}},
|
|
627
653
|
|
|
628
654
|
// ── CONTENT INTELLIGENCE v4.4 Batch 4B (4) ──
|
|
629
|
-
{ name: 'wp_extract_entities', description: '
|
|
630
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
631
|
-
{ name: 'wp_get_publishing_velocity', description: '
|
|
632
|
-
inputSchema: { type: 'object', properties: { periods: { type: 'string', description: "Comma-separated day periods (default '30,90,180')" }, post_type: { type: 'string', enum: ['post', 'page']
|
|
633
|
-
{ name: 'wp_compare_revisions_diff', description: '
|
|
634
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
635
|
-
{ name: 'wp_list_posts_by_word_count', description: '
|
|
636
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
655
|
+
{ name: 'wp_extract_entities', description: 'Use to extract named entities (brands, places, people, organizations) from post content. Read-only.',
|
|
656
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] }, min_occurrences: { type: 'number', description: 'Minimum total occurrences across corpus (default 2)' } }}},
|
|
657
|
+
{ name: 'wp_get_publishing_velocity', description: 'Use to measure publication cadence per author and category over 30/90/180 days. Read-only.',
|
|
658
|
+
inputSchema: { type: 'object', properties: { periods: { type: 'string', description: "Comma-separated day periods (default '30,90,180')" }, post_type: { type: 'string', enum: ['post', 'page'] }, limit: { type: 'number' } }}},
|
|
659
|
+
{ name: 'wp_compare_revisions_diff', description: 'Use to diff two revisions and measure update amplitude. Read-only.',
|
|
660
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, revision_id_from: { type: 'number', description: 'Older revision ID (baseline)' }, revision_id_to: { type: 'number', description: 'Newer revision ID (omit for current post)' }, post_type: { type: 'string', enum: ['post', 'page'] } }, required: ['post_id', 'revision_id_from'] }},
|
|
661
|
+
{ name: 'wp_list_posts_by_word_count', description: 'Use to rank posts by length with auto-segmentation (<500, 500-1k, 1k-2k, 2k+). Read-only.',
|
|
662
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] }, order: { type: 'string', enum: ['asc', 'desc'] }, category_id: { type: 'number' } }}},
|
|
637
663
|
|
|
638
664
|
// ── PLUGIN INTELLIGENCE v4.5 (3) ──
|
|
639
|
-
{ name: 'wp_get_rendered_head', description: '
|
|
640
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
641
|
-
{ name: 'wp_audit_rendered_seo', description: '
|
|
642
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
643
|
-
{ name: 'wp_get_pillar_content', description: '
|
|
644
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
665
|
+
{ name: 'wp_get_rendered_head', description: 'Use to fetch the real <head> HTML Google sees via RankMath/Yoast headless endpoint. Compares rendered vs stored meta. Read-only. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
666
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] } }, required: ['post_id'] }},
|
|
667
|
+
{ name: 'wp_audit_rendered_seo', description: 'Use for bulk rendered-vs-stored SEO divergence detection with per-post scoring. Read-only. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
668
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] } }}},
|
|
669
|
+
{ name: 'wp_get_pillar_content', description: 'Use to read or set RankMath cornerstone/pillar flag. Read always allowed. Write — blocked by WP_READ_ONLY. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
670
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, set_pillar: { type: 'boolean', description: 'Set pillar flag (true=pillar, false=not). Requires write access.' }, list_pillars: { type: 'boolean', description: 'List all pillar content posts (ignores post_id)' }, post_type: { type: 'string', enum: ['post', 'page'] }, limit: { type: 'number' } }}},
|
|
645
671
|
|
|
646
672
|
// ── PLUGIN INTELLIGENCE v4.5 batch 2 (3) ──
|
|
647
|
-
{ name: 'wp_audit_schema_plugins', description: '
|
|
648
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
649
|
-
{ name: 'wp_get_seo_score', description: '
|
|
650
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
651
|
-
{ name: 'wp_get_twitter_meta', description: '
|
|
652
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
673
|
+
{ name: 'wp_audit_schema_plugins', description: 'Use to validate JSON-LD from SEO plugin native fields (rank_math_schema or yoast_head_json). Read-only. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
674
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
675
|
+
{ name: 'wp_get_seo_score', description: 'Use to read RankMath native SEO score (0-100). Bulk mode shows distribution stats. Read-only. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
676
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, limit: { type: 'number', description: 'Bulk mode: max posts (1-100, default 20)' }, post_type: { type: 'string', enum: ['post', 'page'] }, order: { type: 'string', enum: ['asc', 'desc'] } }}},
|
|
677
|
+
{ name: 'wp_get_twitter_meta', description: 'Use to read or update Twitter Card meta (title, description, image) for RankMath/Yoast/SEOPress. Write — blocked by WP_READ_ONLY. Requires WP_ENABLE_PLUGIN_INTELLIGENCE=true.',
|
|
678
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] }, twitter_title: { type: 'string', description: 'Set Twitter title (write mode)' }, twitter_description: { type: 'string', description: 'Set Twitter description (write mode)' }, twitter_image: { type: 'string', description: 'Set Twitter image URL (write mode)' } }, required: ['post_id'] }}
|
|
653
679
|
];
|
|
654
680
|
|
|
681
|
+
function getFilteredTools(allTools = TOOLS_DEFINITIONS) {
|
|
682
|
+
const pluginIntelTools = ['wp_get_rendered_head', 'wp_audit_rendered_seo', 'wp_get_pillar_content', 'wp_audit_schema_plugins', 'wp_get_seo_score', 'wp_get_twitter_meta'];
|
|
683
|
+
const editorialTools = ['wp_submit_for_review', 'wp_approve_post', 'wp_reject_post'];
|
|
684
|
+
const coreFiltered = allTools.filter(tool => {
|
|
685
|
+
const n = tool.name;
|
|
686
|
+
if (n.startsWith('wc_') && !process.env.WC_CONSUMER_KEY) return false;
|
|
687
|
+
if (editorialTools.includes(n) && process.env.WP_REQUIRE_APPROVAL !== 'true') return false;
|
|
688
|
+
if (pluginIntelTools.includes(n) && process.env.WP_ENABLE_PLUGIN_INTELLIGENCE !== 'true') return false;
|
|
689
|
+
return true;
|
|
690
|
+
});
|
|
691
|
+
const pluginTools = pluginRegistry.getAvailableTools();
|
|
692
|
+
return [...coreFiltered, ...pluginTools];
|
|
693
|
+
}
|
|
694
|
+
|
|
655
695
|
function registerHandlers(s) {
|
|
656
|
-
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools:
|
|
696
|
+
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: getFilteredTools() }));
|
|
657
697
|
s.setRequestHandler(CallToolRequestSchema, handleToolCall);
|
|
658
698
|
}
|
|
659
699
|
|
|
@@ -680,6 +720,21 @@ export async function handleToolCall(request) {
|
|
|
680
720
|
|
|
681
721
|
log.debug(`Tool: ${name}`, args);
|
|
682
722
|
|
|
723
|
+
// Plugin Layer routing — check adapters before core switch
|
|
724
|
+
const pluginTool = pluginRegistry.getAvailableTools().find(t => t.name === name);
|
|
725
|
+
if (pluginTool && pluginTool.handler) {
|
|
726
|
+
try {
|
|
727
|
+
const pluginResult = await pluginTool.handler(args, wpApiCall);
|
|
728
|
+
log.debug(`${name} done in ${Date.now() - t0}ms`);
|
|
729
|
+
return pluginResult;
|
|
730
|
+
} catch (error) {
|
|
731
|
+
const ms = Date.now() - t0;
|
|
732
|
+
log.error(`${name} failed (${ms}ms): ${error.message}`);
|
|
733
|
+
auditLog({ tool: name, target: args.id || null, status: 'error', latency_ms: ms, params: sanitizeParams(args), error: error.message });
|
|
734
|
+
return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
683
738
|
try {
|
|
684
739
|
let result;
|
|
685
740
|
|
|
@@ -795,11 +850,19 @@ export async function handleToolCall(request) {
|
|
|
795
850
|
// ── PAGES ──
|
|
796
851
|
|
|
797
852
|
case 'wp_list_pages': {
|
|
798
|
-
validateInput(args, { per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, status: { type: 'string', enum: STATUSES }, order: { type: 'string', enum: ORDERS } });
|
|
799
|
-
const { per_page = 10, page = 1, status = 'publish', parent, orderby = 'menu_order', order = 'asc', search } = args;
|
|
853
|
+
validateInput(args, { per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, status: { type: 'string', enum: STATUSES }, order: { type: 'string', enum: ORDERS }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] } });
|
|
854
|
+
const { per_page = 10, page = 1, status = 'publish', parent, orderby = 'menu_order', order = 'asc', search, mode = 'full' } = args;
|
|
800
855
|
let ep = `/pages?per_page=${per_page}&page=${page}&status=${status}&orderby=${orderby}&order=${order}`;
|
|
801
856
|
if (parent !== undefined) ep += `&parent=${parent}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
802
857
|
const pgs = await wpApiCall(ep);
|
|
858
|
+
if (mode === 'ids_only') {
|
|
859
|
+
result = json({ total: pgs.length, page, mode: 'ids_only', ids: pgs.map(p => p.id) });
|
|
860
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
861
|
+
}
|
|
862
|
+
if (mode === 'summary') {
|
|
863
|
+
result = json({ total: pgs.length, page, mode: 'summary', pages: pgs.map(p => ({ id: p.id, title: p.title.rendered, slug: p.slug, status: p.status, link: p.link, parent: p.parent })) });
|
|
864
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
865
|
+
}
|
|
803
866
|
result = json({ total: pgs.length, page, pages: pgs.map(p => ({ id: p.id, title: p.title.rendered, status: p.status, date: p.date, link: p.link, parent: p.parent, menu_order: p.menu_order, template: p.template, excerpt: strip(p.excerpt.rendered).substring(0, 200) })) });
|
|
804
867
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
805
868
|
break;
|
|
@@ -839,11 +902,19 @@ export async function handleToolCall(request) {
|
|
|
839
902
|
// ── MEDIA ──
|
|
840
903
|
|
|
841
904
|
case 'wp_list_media': {
|
|
842
|
-
validateInput(args, { per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, media_type: { type: 'string', enum: MEDIA_TYPES }, order: { type: 'string', enum: ORDERS } });
|
|
843
|
-
const { per_page = 10, page = 1, media_type, search, orderby = 'date', order = 'desc' } = args;
|
|
905
|
+
validateInput(args, { per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, media_type: { type: 'string', enum: MEDIA_TYPES }, order: { type: 'string', enum: ORDERS }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] } });
|
|
906
|
+
const { per_page = 10, page = 1, media_type, search, orderby = 'date', order = 'desc', mode = 'full' } = args;
|
|
844
907
|
let ep = `/media?per_page=${per_page}&page=${page}&orderby=${orderby}&order=${order}`;
|
|
845
908
|
if (media_type) ep += `&media_type=${media_type}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
846
909
|
const media = await wpApiCall(ep);
|
|
910
|
+
if (mode === 'ids_only') {
|
|
911
|
+
result = json({ total: media.length, page, mode: 'ids_only', ids: media.map(m => m.id) });
|
|
912
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
913
|
+
}
|
|
914
|
+
if (mode === 'summary') {
|
|
915
|
+
result = json({ total: media.length, page, mode: 'summary', media: media.map(m => ({ id: m.id, title: m.title.rendered, mime_type: m.mime_type, source_url: m.source_url, alt_text: m.alt_text })) });
|
|
916
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
917
|
+
}
|
|
847
918
|
result = json({ total: media.length, page, media: media.map(m => ({ id: m.id, title: m.title.rendered, date: m.date, mime_type: m.mime_type, source_url: m.source_url, alt_text: m.alt_text, width: m.media_details?.width, height: m.media_details?.height })) });
|
|
848
919
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
849
920
|
break;
|
|
@@ -880,20 +951,36 @@ export async function handleToolCall(request) {
|
|
|
880
951
|
// ── TAXONOMIES ──
|
|
881
952
|
|
|
882
953
|
case 'wp_list_categories': {
|
|
883
|
-
const { per_page = 100, page = 1, parent, search, orderby = 'name', hide_empty = false } = args;
|
|
954
|
+
const { per_page = 100, page = 1, parent, search, orderby = 'name', hide_empty = false, mode = 'full' } = args;
|
|
884
955
|
let ep = `/categories?per_page=${per_page}&page=${page}&orderby=${orderby}&hide_empty=${hide_empty}`;
|
|
885
956
|
if (parent !== undefined) ep += `&parent=${parent}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
886
957
|
const cats = await wpApiCall(ep);
|
|
958
|
+
if (mode === 'ids_only') {
|
|
959
|
+
result = json({ total: cats.length, mode: 'ids_only', ids: cats.map(c => c.id) });
|
|
960
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
961
|
+
}
|
|
962
|
+
if (mode === 'summary') {
|
|
963
|
+
result = json({ total: cats.length, mode: 'summary', categories: cats.map(c => ({ id: c.id, name: c.name, slug: c.slug, count: c.count, parent: c.parent })) });
|
|
964
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
965
|
+
}
|
|
887
966
|
result = json({ total: cats.length, categories: cats.map(c => ({ id: c.id, name: c.name, slug: c.slug, description: c.description, parent: c.parent, count: c.count })) });
|
|
888
967
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
889
968
|
break;
|
|
890
969
|
}
|
|
891
970
|
|
|
892
971
|
case 'wp_list_tags': {
|
|
893
|
-
const { per_page = 100, page = 1, search, orderby = 'name', hide_empty = false } = args;
|
|
972
|
+
const { per_page = 100, page = 1, search, orderby = 'name', hide_empty = false, mode = 'full' } = args;
|
|
894
973
|
let ep = `/tags?per_page=${per_page}&page=${page}&orderby=${orderby}&hide_empty=${hide_empty}`;
|
|
895
974
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
896
975
|
const tags = await wpApiCall(ep);
|
|
976
|
+
if (mode === 'ids_only') {
|
|
977
|
+
result = json({ total: tags.length, mode: 'ids_only', ids: tags.map(t => t.id) });
|
|
978
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
979
|
+
}
|
|
980
|
+
if (mode === 'summary') {
|
|
981
|
+
result = json({ total: tags.length, mode: 'summary', tags: tags.map(t => ({ id: t.id, name: t.name, slug: t.slug, count: t.count })) });
|
|
982
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
983
|
+
}
|
|
897
984
|
result = json({ total: tags.length, tags: tags.map(t => ({ id: t.id, name: t.name, slug: t.slug, count: t.count })) });
|
|
898
985
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
899
986
|
break;
|
|
@@ -914,10 +1001,18 @@ export async function handleToolCall(request) {
|
|
|
914
1001
|
// ── COMMENTS ──
|
|
915
1002
|
|
|
916
1003
|
case 'wp_list_comments': {
|
|
917
|
-
const { per_page = 10, page = 1, post, status, orderby = 'date_gmt', order = 'desc', search } = args;
|
|
1004
|
+
const { per_page = 10, page = 1, post, status, orderby = 'date_gmt', order = 'desc', search, mode = 'full' } = args;
|
|
918
1005
|
let ep = `/comments?per_page=${per_page}&page=${page}&orderby=${orderby}&order=${order}`;
|
|
919
1006
|
if (post) ep += `&post=${post}`; if (status) ep += `&status=${status}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
920
1007
|
const comments = await wpApiCall(ep);
|
|
1008
|
+
if (mode === 'ids_only') {
|
|
1009
|
+
result = json({ total: comments.length, page, mode: 'ids_only', ids: comments.map(c => c.id) });
|
|
1010
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1011
|
+
}
|
|
1012
|
+
if (mode === 'summary') {
|
|
1013
|
+
result = json({ total: comments.length, page, mode: 'summary', comments: comments.map(c => ({ id: c.id, post: c.post, author_name: c.author_name, date: c.date, status: c.status })) });
|
|
1014
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1015
|
+
}
|
|
921
1016
|
result = json({ total: comments.length, page, comments: comments.map(c => ({ id: c.id, post: c.post, parent: c.parent, author_name: c.author_name, date: c.date, status: c.status, content: strip(c.content.rendered).substring(0, 300), link: c.link })) });
|
|
922
1017
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
923
1018
|
break;
|
|
@@ -944,9 +1039,9 @@ export async function handleToolCall(request) {
|
|
|
944
1039
|
}
|
|
945
1040
|
|
|
946
1041
|
case 'wp_list_custom_posts': {
|
|
947
|
-
validateInput(args, { post_type: { type: 'string', required: true }, per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, order: { type: 'string', enum: ORDERS } });
|
|
1042
|
+
validateInput(args, { post_type: { type: 'string', required: true }, per_page: { type: 'number', min: 1, max: 100 }, page: { type: 'number', min: 1 }, order: { type: 'string', enum: ORDERS }, mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] } });
|
|
948
1043
|
enforceAllowedTypes(args.post_type);
|
|
949
|
-
const { post_type, per_page = 10, page = 1, status = 'publish', orderby = 'date', order = 'desc', search } = args;
|
|
1044
|
+
const { post_type, per_page = 10, page = 1, status = 'publish', orderby = 'date', order = 'desc', search, mode = 'full' } = args;
|
|
950
1045
|
const types = await wpApiCall('/types');
|
|
951
1046
|
const typeInfo = Object.values(types).find(t => t.slug === post_type || t.rest_base === post_type);
|
|
952
1047
|
if (!typeInfo) throw new Error(`Post type "${post_type}" not found.`);
|
|
@@ -954,6 +1049,14 @@ export async function handleToolCall(request) {
|
|
|
954
1049
|
let ep = `/${restBase}?per_page=${per_page}&page=${page}&status=${status}&orderby=${orderby}&order=${order}`;
|
|
955
1050
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
956
1051
|
const posts = await wpApiCall(ep);
|
|
1052
|
+
if (mode === 'ids_only') {
|
|
1053
|
+
result = json({ post_type, total: posts.length, page, mode: 'ids_only', ids: posts.map(p => p.id) });
|
|
1054
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } }); break;
|
|
1055
|
+
}
|
|
1056
|
+
if (mode === 'summary') {
|
|
1057
|
+
result = json({ post_type, total: posts.length, page, mode: 'summary', posts: posts.map(p => ({ id: p.id, title: p.title?.rendered || p.title, slug: p.slug, date: p.date, status: p.status, link: p.link })) });
|
|
1058
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } }); break;
|
|
1059
|
+
}
|
|
957
1060
|
result = json({ post_type, total: posts.length, page, posts: posts.map(p => ({ id: p.id, title: p.title?.rendered || p.title, status: p.status, date: p.date, link: p.link, slug: p.slug, type: p.type, meta: p.meta || {} })) });
|
|
958
1061
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } });
|
|
959
1062
|
break;
|
|
@@ -962,10 +1065,18 @@ export async function handleToolCall(request) {
|
|
|
962
1065
|
// ── USERS ──
|
|
963
1066
|
|
|
964
1067
|
case 'wp_list_users': {
|
|
965
|
-
const { per_page = 10, page = 1, roles, search, orderby = 'name' } = args;
|
|
1068
|
+
const { per_page = 10, page = 1, roles, search, orderby = 'name', mode = 'full' } = args;
|
|
966
1069
|
let ep = `/users?per_page=${per_page}&page=${page}&orderby=${orderby}`;
|
|
967
1070
|
if (roles) ep += `&roles=${roles}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
968
1071
|
const users = await wpApiCall(ep);
|
|
1072
|
+
if (mode === 'ids_only') {
|
|
1073
|
+
result = json({ total: users.length, mode: 'ids_only', ids: users.map(u => u.id) });
|
|
1074
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1075
|
+
}
|
|
1076
|
+
if (mode === 'summary') {
|
|
1077
|
+
result = json({ total: users.length, mode: 'summary', users: users.map(u => ({ id: u.id, name: u.name, slug: u.slug, roles: u.roles || [] })) });
|
|
1078
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1079
|
+
}
|
|
969
1080
|
result = json({ total: users.length, users: users.map(u => ({ id: u.id, name: u.name, slug: u.slug, link: u.link, roles: u.roles, avatar: u.avatar_urls?.['96'] })) });
|
|
970
1081
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
971
1082
|
break;
|
|
@@ -1022,12 +1133,36 @@ export async function handleToolCall(request) {
|
|
|
1022
1133
|
enabled: isMultiTarget, active_site: currentTarget?.name || 'default',
|
|
1023
1134
|
available_sites: Object.keys(targets)
|
|
1024
1135
|
},
|
|
1025
|
-
server:
|
|
1136
|
+
server: (() => {
|
|
1137
|
+
const exposed = getFilteredTools().length;
|
|
1138
|
+
const groups = [];
|
|
1139
|
+
if (!process.env.WC_CONSUMER_KEY) groups.push('woocommerce');
|
|
1140
|
+
if (process.env.WP_REQUIRE_APPROVAL !== 'true') groups.push('editorial');
|
|
1141
|
+
if (process.env.WP_ENABLE_PLUGIN_INTELLIGENCE !== 'true') groups.push('plugin_intelligence');
|
|
1142
|
+
return { mcp_version: VERSION, tools_total: TOOLS_COUNT, tools_exposed: exposed, filtered_out: groups };
|
|
1143
|
+
})(),
|
|
1144
|
+
plugin_layer: pluginRegistry.getSummary()
|
|
1026
1145
|
});
|
|
1027
1146
|
auditLog({ tool: name, action: 'info', status: 'success', latency_ms: Date.now() - t0 });
|
|
1028
1147
|
break;
|
|
1029
1148
|
}
|
|
1030
1149
|
|
|
1150
|
+
case 'wp_get_site_options': {
|
|
1151
|
+
validateInput(args, { keys: { type: 'array' } });
|
|
1152
|
+
const { keys } = args;
|
|
1153
|
+
const settings = await wpApiCall('/settings');
|
|
1154
|
+
let data = settings;
|
|
1155
|
+
if (keys && keys.length > 0) {
|
|
1156
|
+
data = {};
|
|
1157
|
+
for (const k of keys) {
|
|
1158
|
+
if (k in settings) data[k] = settings[k];
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
result = json(data);
|
|
1162
|
+
auditLog({ tool: name, action: 'read_options', status: 'success', latency_ms: Date.now() - t0, params: { keys_requested: keys?.length || 'all', keys_returned: Object.keys(data).length } });
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1031
1166
|
// ── SEO METADATA ──
|
|
1032
1167
|
|
|
1033
1168
|
case 'wp_get_seo_meta': {
|
|
@@ -1264,15 +1399,20 @@ export async function handleToolCall(request) {
|
|
|
1264
1399
|
validateInput(args, {
|
|
1265
1400
|
search: { type: 'string' },
|
|
1266
1401
|
status: { type: 'string', enum: ['active', 'inactive', 'all'] },
|
|
1267
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1402
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1403
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1268
1404
|
});
|
|
1269
|
-
const { search, status = 'all', per_page = 20 } = args;
|
|
1405
|
+
const { search, status = 'all', per_page = 20, mode = 'full' } = args;
|
|
1270
1406
|
let ep = `/plugins?per_page=${per_page}&context=edit`;
|
|
1271
1407
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
1272
1408
|
if (status && status !== 'all') ep += `&status=${status}`;
|
|
1273
1409
|
|
|
1274
1410
|
try {
|
|
1275
1411
|
const plugins = await wpApiCall(ep);
|
|
1412
|
+
if (mode === 'ids_only') {
|
|
1413
|
+
result = json({ total: plugins.length, mode: 'ids_only', ids: plugins.map(p => p.plugin) });
|
|
1414
|
+
auditLog({ tool: name, action: 'list', target_type: 'plugin', status: 'success', latency_ms: Date.now() - t0, params: { search, status, per_page } }); break;
|
|
1415
|
+
}
|
|
1276
1416
|
const mapped = plugins.map(p => ({
|
|
1277
1417
|
plugin: p.plugin,
|
|
1278
1418
|
name: p.name,
|
|
@@ -1286,6 +1426,10 @@ export async function handleToolCall(request) {
|
|
|
1286
1426
|
network_only: p.network_only ?? false,
|
|
1287
1427
|
textdomain: p.textdomain ?? ''
|
|
1288
1428
|
}));
|
|
1429
|
+
if (mode === 'summary') {
|
|
1430
|
+
result = json({ total: mapped.length, mode: 'summary', plugins: mapped.map(p => ({ plugin: p.plugin, name: p.name, status: p.status, version: p.version })) });
|
|
1431
|
+
auditLog({ tool: name, action: 'list', target_type: 'plugin', status: 'success', latency_ms: Date.now() - t0, params: { search, status, per_page } }); break;
|
|
1432
|
+
}
|
|
1289
1433
|
const activeCount = mapped.filter(p => p.status === 'active').length;
|
|
1290
1434
|
const inactiveCount = mapped.filter(p => p.status === 'inactive').length;
|
|
1291
1435
|
result = json({ total: mapped.length, active: activeCount, inactive: inactiveCount, plugins: mapped });
|
|
@@ -1346,13 +1490,18 @@ export async function handleToolCall(request) {
|
|
|
1346
1490
|
case 'wp_list_themes': {
|
|
1347
1491
|
validateInput(args, {
|
|
1348
1492
|
status: { type: 'string', enum: ['active', 'inactive', 'all'] },
|
|
1349
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1493
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1494
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1350
1495
|
});
|
|
1351
|
-
const { status = 'all', per_page = 20 } = args;
|
|
1496
|
+
const { status = 'all', per_page = 20, mode = 'full' } = args;
|
|
1352
1497
|
let ep = `/themes?per_page=${per_page}&context=edit`;
|
|
1353
1498
|
if (status && status !== 'all') ep += `&status=${status}`;
|
|
1354
1499
|
try {
|
|
1355
1500
|
const themes = await wpApiCall(ep);
|
|
1501
|
+
if (mode === 'ids_only') {
|
|
1502
|
+
result = json({ total: themes.length, mode: 'ids_only', ids: themes.map(t => t.stylesheet) });
|
|
1503
|
+
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } }); break;
|
|
1504
|
+
}
|
|
1356
1505
|
const mapped = themes.map(t => ({
|
|
1357
1506
|
stylesheet: t.stylesheet,
|
|
1358
1507
|
template: t.template,
|
|
@@ -1367,6 +1516,10 @@ export async function handleToolCall(request) {
|
|
|
1367
1516
|
requires_php: t.requires_php ?? '',
|
|
1368
1517
|
tags: t.tags?.rendered ?? t.tags ?? []
|
|
1369
1518
|
}));
|
|
1519
|
+
if (mode === 'summary') {
|
|
1520
|
+
result = json({ total: mapped.length, mode: 'summary', themes: mapped.map(t => ({ stylesheet: t.stylesheet, name: t.name, status: t.status, version: t.version })) });
|
|
1521
|
+
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } }); break;
|
|
1522
|
+
}
|
|
1370
1523
|
const activeTheme = mapped.find(t => t.status === 'active');
|
|
1371
1524
|
result = json({ total: mapped.length, active_theme: activeTheme ? activeTheme.name : null, themes: mapped });
|
|
1372
1525
|
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } });
|
|
@@ -1416,12 +1569,21 @@ export async function handleToolCall(request) {
|
|
|
1416
1569
|
validateInput(args, {
|
|
1417
1570
|
post_id: { type: 'number', required: true, min: 1 },
|
|
1418
1571
|
post_type: { type: 'string', enum: ['post', 'page'] },
|
|
1419
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1572
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1573
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1420
1574
|
});
|
|
1421
|
-
const { post_id, post_type = 'post', per_page = 10 } = args;
|
|
1575
|
+
const { post_id, post_type = 'post', per_page = 10, mode = 'full' } = args;
|
|
1422
1576
|
const base = post_type === 'page' ? 'pages' : 'posts';
|
|
1423
1577
|
try {
|
|
1424
1578
|
const revisions = await wpApiCall(`/${base}/${post_id}/revisions?per_page=${per_page}&context=edit`);
|
|
1579
|
+
if (mode === 'ids_only') {
|
|
1580
|
+
result = json({ total: revisions.length, post_id, post_type, mode: 'ids_only', ids: revisions.map(r => r.id) });
|
|
1581
|
+
auditLog({ tool: name, action: 'list', target: post_id, target_type: 'revision', status: 'success', latency_ms: Date.now() - t0, params: { post_type, per_page } }); break;
|
|
1582
|
+
}
|
|
1583
|
+
if (mode === 'summary') {
|
|
1584
|
+
result = json({ total: revisions.length, post_id, post_type, mode: 'summary', revisions: revisions.map(r => ({ id: r.id, date: r.date, author: r.author })) });
|
|
1585
|
+
auditLog({ tool: name, action: 'list', target: post_id, target_type: 'revision', status: 'success', latency_ms: Date.now() - t0, params: { post_type, per_page } }); break;
|
|
1586
|
+
}
|
|
1425
1587
|
result = json({
|
|
1426
1588
|
total: revisions.length,
|
|
1427
1589
|
post_id,
|
|
@@ -4921,6 +5083,7 @@ export function createMcpServer() {
|
|
|
4921
5083
|
|
|
4922
5084
|
async function main() {
|
|
4923
5085
|
await healthCheck();
|
|
5086
|
+
await initializePluginLayer();
|
|
4924
5087
|
|
|
4925
5088
|
const transportMode = (process.env.MCP_TRANSPORT || 'stdio').toLowerCase();
|
|
4926
5089
|
|
|
@@ -4952,4 +5115,4 @@ if (process.env.NODE_ENV !== 'test') {
|
|
|
4952
5115
|
main().catch((error) => { log.error(`Fatal: ${error.message}`); process.exit(1); });
|
|
4953
5116
|
}
|
|
4954
5117
|
|
|
4955
|
-
export { server, getActiveControls, getControlSources, _testSetTarget };
|
|
5118
|
+
export { server, getActiveControls, getControlSources, _testSetTarget, getFilteredTools, pluginRegistry, initializePluginLayer };
|