@adsim/wordpress-mcp-server 4.4.0 → 4.5.1
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 +48 -4
- package/dxt/manifest.json +12 -5
- package/index.js +720 -150
- package/package.json +1 -1
- package/src/pluginDetector.js +158 -0
- package/tests/unit/pluginDetector.test.js +167 -0
- package/tests/unit/tools/dynamicFiltering.test.js +136 -0
- package/tests/unit/tools/outputCompression.test.js +342 -0
- package/tests/unit/tools/pluginIntelligence.test.js +864 -0
- package/tests/unit/tools/site.test.js +3 -1
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { wcApiCall, getWcCredentials } from './src/woocommerceClient.js';
|
|
|
28
28
|
import { parseImagesFromHtml, extractHeadings, extractInternalLinks as extractInternalLinksHtml, countWords } from './src/htmlParser.js';
|
|
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
|
+
import { detectSeoPlugin, getRenderedHead, parseRenderedHead } from './src/pluginDetector.js';
|
|
31
32
|
|
|
32
33
|
// ============================================================
|
|
33
34
|
// CONFIGURATION
|
|
@@ -435,7 +436,7 @@ const ORDERBY = ['date', 'relevance', 'id', 'title', 'slug', 'modified', 'author
|
|
|
435
436
|
const ORDERS = ['asc', 'desc'];
|
|
436
437
|
const MEDIA_TYPES = ['image', 'video', 'audio', 'application'];
|
|
437
438
|
const COMMENT_STATUSES = ['approved', 'hold', 'spam', 'trash'];
|
|
438
|
-
const TOOLS_COUNT =
|
|
439
|
+
const TOOLS_COUNT = 85;
|
|
439
440
|
|
|
440
441
|
function json(data) { return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }; }
|
|
441
442
|
function strip(html) { return (html || '').replace(/<[^>]*>/g, '').trim(); }
|
|
@@ -446,197 +447,225 @@ function strip(html) { return (html || '').replace(/<[^>]*>/g, '').trim(); }
|
|
|
446
447
|
|
|
447
448
|
const TOOLS_DEFINITIONS = [
|
|
448
449
|
// ── POSTS (6) ──
|
|
449
|
-
{ name: 'wp_list_posts', description: '
|
|
450
|
-
{ name: 'wp_get_post', description: '
|
|
451
|
-
{ name: 'wp_create_post', description: '
|
|
452
|
-
{ name: 'wp_update_post', description: '
|
|
453
|
-
{ name: 'wp_delete_post', description: '
|
|
454
|
-
{ name: 'wp_search', description: '
|
|
450
|
+
{ 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' } }}},
|
|
451
|
+
{ 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'] }},
|
|
452
|
+
{ 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'] }},
|
|
453
|
+
{ 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'] }},
|
|
454
|
+
{ 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'] }},
|
|
455
|
+
{ 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'] }},
|
|
455
456
|
|
|
456
457
|
// ── APPROVAL WORKFLOW (3) ──
|
|
457
|
-
{ name: 'wp_submit_for_review', description: '
|
|
458
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
459
|
-
{ name: 'wp_approve_post', description: '
|
|
460
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
461
|
-
{ name: 'wp_reject_post', description: '
|
|
462
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
458
|
+
{ name: 'wp_submit_for_review', description: 'Use to transition a draft post to pending status for editorial review. Write — blocked by WP_READ_ONLY.',
|
|
459
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, note: { type: 'string', description: 'Optional review note (stored as post meta _mcp_review_note)' } }, required: ['id'] }},
|
|
460
|
+
{ 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.',
|
|
461
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
462
|
+
{ name: 'wp_reject_post', description: 'Use to return a pending post to draft with a mandatory rejection reason. Write — blocked by WP_READ_ONLY.',
|
|
463
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, reason: { type: 'string', description: 'Reason for rejection (stored as post meta _mcp_rejection_reason)' } }, required: ['id', 'reason'] }},
|
|
463
464
|
|
|
464
465
|
// ── PAGES (4) ──
|
|
465
|
-
{ name: 'wp_list_pages', description: '
|
|
466
|
-
{ name: 'wp_get_page', description: '
|
|
467
|
-
{ name: 'wp_create_page', description: '
|
|
468
|
-
{ name: 'wp_update_page', description: '
|
|
466
|
+
{ 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' } }}},
|
|
467
|
+
{ 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'] }},
|
|
468
|
+
{ 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'] }},
|
|
469
|
+
{ 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'] }},
|
|
469
470
|
|
|
470
471
|
// ── MEDIA (3) ──
|
|
471
|
-
{ name: 'wp_list_media', description: '
|
|
472
|
-
{ name: 'wp_get_media', description: '
|
|
473
|
-
{ name: 'wp_upload_media', description: '
|
|
472
|
+
{ 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' } }}},
|
|
473
|
+
{ 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'] }},
|
|
474
|
+
{ 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'] }},
|
|
474
475
|
|
|
475
476
|
// ── TAXONOMIES (3) ──
|
|
476
|
-
{ name: 'wp_list_categories', description: '
|
|
477
|
-
{ name: 'wp_list_tags', description: '
|
|
478
|
-
{ name: 'wp_create_taxonomy_term', description: '
|
|
477
|
+
{ 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' } }}},
|
|
478
|
+
{ 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' } }}},
|
|
479
|
+
{ 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'] }},
|
|
479
480
|
|
|
480
481
|
// ── COMMENTS (2) ──
|
|
481
|
-
{ name: 'wp_list_comments', description: '
|
|
482
|
-
{ name: 'wp_create_comment', description: '
|
|
482
|
+
{ 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' } }}},
|
|
483
|
+
{ 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'] }},
|
|
483
484
|
|
|
484
485
|
// ── CUSTOM POST TYPES (2) ──
|
|
485
|
-
{ name: 'wp_list_post_types', description: '
|
|
486
|
-
{ name: 'wp_list_custom_posts', description: '
|
|
486
|
+
{ 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: {} }},
|
|
487
|
+
{ 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'] }},
|
|
487
488
|
|
|
488
489
|
// ── USERS (1) ──
|
|
489
|
-
{ name: 'wp_list_users', description: '
|
|
490
|
+
{ 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' } }}},
|
|
490
491
|
|
|
491
492
|
// ── MULTI-TARGET (1) ──
|
|
492
|
-
{ name: 'wp_set_target', description: '
|
|
493
|
+
{ name: 'wp_set_target', description: 'Use to switch active WordPress site in multi-target mode. Write.',
|
|
493
494
|
inputSchema: { type: 'object', properties: { site: { type: 'string', description: 'Site key from targets config' } }, required: ['site'] }},
|
|
494
495
|
|
|
495
496
|
// ── SITE INFO (1) ──
|
|
496
|
-
{ name: 'wp_site_info', description: '
|
|
497
|
+
{ name: 'wp_site_info', description: 'Use first to discover site config, user, post types, governance flags, and available targets. Read-only.',
|
|
497
498
|
inputSchema: { type: 'object', properties: {} }},
|
|
498
499
|
|
|
499
500
|
// ── SEO METADATA (3) ──
|
|
500
|
-
{ name: 'wp_get_seo_meta', description: '
|
|
501
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
502
|
-
{ name: 'wp_update_seo_meta', description: '
|
|
503
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
504
|
-
{ name: 'wp_audit_seo', description: '
|
|
501
|
+
{ 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.',
|
|
502
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' } }, required: ['id'] }},
|
|
503
|
+
{ 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.',
|
|
504
|
+
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'] }},
|
|
505
|
+
{ 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.',
|
|
505
506
|
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' } }}},
|
|
506
507
|
|
|
507
508
|
// ── PLUGINS (3) ──
|
|
508
|
-
{ name: 'wp_list_plugins', description: '
|
|
509
|
-
inputSchema: { type: 'object', properties: { search: { type: 'string'
|
|
510
|
-
{ name: 'wp_activate_plugin', description: '
|
|
509
|
+
{ 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.',
|
|
510
|
+
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' } }}},
|
|
511
|
+
{ name: 'wp_activate_plugin', description: 'Use to activate a plugin. Write — blocked by WP_READ_ONLY, WP_DISABLE_PLUGIN_MANAGEMENT.',
|
|
511
512
|
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'] }},
|
|
512
|
-
{ name: 'wp_deactivate_plugin', description: '
|
|
513
|
+
{ name: 'wp_deactivate_plugin', description: 'Use to deactivate a plugin. Write — blocked by WP_READ_ONLY, WP_DISABLE_PLUGIN_MANAGEMENT.',
|
|
513
514
|
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'] }},
|
|
514
515
|
|
|
515
516
|
// ── THEMES (2) ──
|
|
516
|
-
{ name: 'wp_list_themes', description: '
|
|
517
|
-
inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'inactive', 'all'], default: 'all',
|
|
518
|
-
{ name: 'wp_get_theme', description: '
|
|
517
|
+
{ 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.',
|
|
518
|
+
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' } }}},
|
|
519
|
+
{ name: 'wp_get_theme', description: 'Use to get theme details by stylesheet slug. Read-only.',
|
|
519
520
|
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'] }},
|
|
520
521
|
|
|
521
522
|
// ── REVISIONS (4) ──
|
|
522
|
-
{ name: 'wp_list_revisions', description: '
|
|
523
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
524
|
-
{ name: 'wp_get_revision', description: '
|
|
525
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
526
|
-
{ name: 'wp_restore_revision', description: '
|
|
527
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
528
|
-
{ name: 'wp_delete_revision', description: '
|
|
529
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
523
|
+
{ 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.',
|
|
524
|
+
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'] }},
|
|
525
|
+
{ name: 'wp_get_revision', description: 'Use to read a specific revision with full content. Read-only.',
|
|
526
|
+
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'] }},
|
|
527
|
+
{ name: 'wp_restore_revision', description: 'Use to restore a post to a previous revision. Write — blocked by WP_READ_ONLY.',
|
|
528
|
+
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'] }},
|
|
529
|
+
{ name: 'wp_delete_revision', description: 'Use to permanently delete a revision. Write — blocked by WP_READ_ONLY, WP_DISABLE_DELETE, WP_CONFIRM_DESTRUCTIVE.',
|
|
530
|
+
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'] }},
|
|
530
531
|
|
|
531
532
|
// ── LINK ANALYSIS (2) ──
|
|
532
|
-
{ name: 'wp_analyze_links', description: '
|
|
533
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
534
|
-
{ name: 'wp_suggest_internal_links', description: '
|
|
535
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
533
|
+
{ 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.',
|
|
534
|
+
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'] }},
|
|
535
|
+
{ 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.',
|
|
536
|
+
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'] }},
|
|
536
537
|
|
|
537
538
|
// ── WOOCOMMERCE (6) ──
|
|
538
|
-
{ name: 'wc_list_products', description: '
|
|
539
|
-
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'
|
|
540
|
-
{ name: 'wc_get_product', description: '
|
|
541
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
542
|
-
{ name: 'wc_list_orders', description: '
|
|
543
|
-
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'
|
|
544
|
-
{ name: 'wc_get_order', description: '
|
|
545
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
546
|
-
{ name: 'wc_list_customers', description: '
|
|
539
|
+
{ name: 'wc_list_products', description: 'Use to browse WooCommerce products. Filter by status, category, or search. Read-only.',
|
|
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' }, orderby: { type: 'string', default: 'date', description: 'date, id, title, price, popularity' }, order: { type: 'string', default: 'desc' } }}},
|
|
541
|
+
{ name: 'wc_get_product', description: 'Use to get full product details including variations summary. Read-only.',
|
|
542
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
543
|
+
{ name: 'wc_list_orders', description: 'Use to browse WooCommerce orders. Filter by status or customer. Read-only.',
|
|
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' }, orderby: { type: 'string', default: 'date' }, order: { type: 'string', default: 'desc' } }}},
|
|
545
|
+
{ name: 'wc_get_order', description: 'Use to get order details with line items, shipping, billing, and payment info. Read-only.',
|
|
546
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }},
|
|
547
|
+
{ name: 'wc_list_customers', description: 'Use to list WooCommerce customers with search and role filtering. Read-only.',
|
|
547
548
|
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' } }}},
|
|
548
|
-
{ name: 'wc_price_guardrail', description: '
|
|
549
|
-
inputSchema: { type: 'object', properties: { product_id: { type: 'number'
|
|
549
|
+
{ name: 'wc_price_guardrail', description: 'Use BEFORE changing a product price. Analyzes safety of proposed price change. Read-only, always allowed.',
|
|
550
|
+
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'] }},
|
|
550
551
|
|
|
551
552
|
// ── WOOCOMMERCE INTELLIGENCE (4) ──
|
|
552
|
-
{ name: 'wc_inventory_alert', description: '
|
|
553
|
-
inputSchema: { type: 'object', properties: { threshold: { type: 'number', default: 5, description: 'Stock quantity threshold (default 5)' }, per_page: { type: 'number', default: 50
|
|
554
|
-
{ name: 'wc_order_intelligence', description: '
|
|
555
|
-
inputSchema: { type: 'object', properties: { customer_id: { type: 'number'
|
|
556
|
-
{ name: 'wc_seo_product_audit', description: '
|
|
557
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 20
|
|
558
|
-
{ name: 'wc_suggest_product_links', description: '
|
|
559
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
553
|
+
{ name: 'wc_inventory_alert', description: 'Use to identify low-stock and out-of-stock products below a threshold, sorted by urgency. Read-only.',
|
|
554
|
+
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' } }}},
|
|
555
|
+
{ name: 'wc_order_intelligence', description: 'Use to analyze a customer\'s purchase history: lifetime value, average order, favorite products, frequency. Read-only.',
|
|
556
|
+
inputSchema: { type: 'object', properties: { customer_id: { type: 'number' } }, required: ['customer_id'] }},
|
|
557
|
+
{ name: 'wc_seo_product_audit', description: 'Use to audit WooCommerce product listings for SEO issues (missing descriptions, images, alt text, generic slugs). Read-only.',
|
|
558
|
+
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 20 }, page: { type: 'number', default: 1 } }}},
|
|
559
|
+
{ name: 'wc_suggest_product_links', description: 'Use to suggest WooCommerce products to link from a blog post based on keyword relevance. Read-only.',
|
|
560
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, max_suggestions: { type: 'number', default: 3, description: 'Maximum suggestions (1-5)' } }, required: ['post_id'] }},
|
|
560
561
|
|
|
561
562
|
// ── WOOCOMMERCE WRITE (3) ──
|
|
562
|
-
{ name: 'wc_update_product', description: '
|
|
563
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
564
|
-
{ name: 'wc_update_stock', description: '
|
|
565
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
566
|
-
{ name: 'wc_update_order_status', description: '
|
|
567
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
563
|
+
{ 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.',
|
|
564
|
+
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'] }},
|
|
565
|
+
{ name: 'wc_update_stock', description: 'Use to update stock quantity of a product or variation. Write — blocked by WP_READ_ONLY.',
|
|
566
|
+
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'] }},
|
|
567
|
+
{ name: 'wc_update_order_status', description: 'Use to transition order status (e.g. processing → completed). Write — blocked by WP_READ_ONLY.',
|
|
568
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, status: { type: 'string', description: 'processing, completed, cancelled, refunded, on-hold, failed' }, note: { type: 'string' } }, required: ['id', 'status'] }},
|
|
568
569
|
|
|
569
570
|
// ── SEO ADVANCED (3) ──
|
|
570
|
-
{ name: 'wp_audit_media_seo', description: '
|
|
571
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 50
|
|
572
|
-
{ name: 'wp_find_orphan_pages', description: '
|
|
573
|
-
inputSchema: { type: 'object', properties: { per_page: { type: 'number', default: 100
|
|
574
|
-
{ name: 'wp_audit_heading_structure', description: '
|
|
575
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
571
|
+
{ 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.',
|
|
572
|
+
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' } }}},
|
|
573
|
+
{ 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.',
|
|
574
|
+
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' } }}},
|
|
575
|
+
{ 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.',
|
|
576
|
+
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'] }},
|
|
576
577
|
|
|
577
578
|
// ── SEO ADVANCED v4.1 (3) ──
|
|
578
|
-
{ name: 'wp_find_thin_content', description: '
|
|
579
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 100
|
|
580
|
-
{ name: 'wp_audit_canonicals', description: '
|
|
581
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 50
|
|
582
|
-
{ name: 'wp_analyze_eeat_signals', description: '
|
|
583
|
-
inputSchema: { type: 'object', properties: { post_ids: { type: 'array', items: { type: 'number' }, description: 'Specific post IDs
|
|
579
|
+
{ name: 'wp_find_thin_content', description: 'Use to surface short/low-quality posts below a word count threshold. Classifies severity. Read-only.',
|
|
580
|
+
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' } }}},
|
|
581
|
+
{ 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.',
|
|
582
|
+
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' } }}},
|
|
583
|
+
{ 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.',
|
|
584
|
+
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' } }}},
|
|
584
585
|
|
|
585
586
|
// ── SEO ADVANCED v4.2 (4) ──
|
|
586
|
-
{ name: 'wp_find_broken_internal_links', description: '
|
|
587
|
-
inputSchema: { type: 'object', properties: { limit_posts: { type: 'number', default: 20
|
|
588
|
-
{ name: 'wp_find_keyword_cannibalization', description: '
|
|
589
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 200
|
|
590
|
-
{ name: 'wp_audit_taxonomies', description: '
|
|
591
|
-
inputSchema: { type: 'object', properties: { check_tags: { type: 'boolean', default: true
|
|
592
|
-
{ name: 'wp_audit_outbound_links', description: '
|
|
593
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number', default: 30
|
|
587
|
+
{ 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.',
|
|
588
|
+
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' } }}},
|
|
589
|
+
{ name: 'wp_find_keyword_cannibalization', description: 'Use to detect posts competing on the same focus keyword. Groups conflicts, flags weakest post. Read-only.',
|
|
590
|
+
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)' } }}},
|
|
591
|
+
{ name: 'wp_audit_taxonomies', description: 'Use to detect taxonomy bloat: empty/single-post terms, near-duplicates, missing descriptions. Read-only.',
|
|
592
|
+
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' } }}},
|
|
593
|
+
{ name: 'wp_audit_outbound_links', description: 'Use to analyze external link profile per post. Detects over-linking, missing nofollow, broken external URLs. Read-only.',
|
|
594
|
+
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' } }}},
|
|
594
595
|
|
|
595
596
|
// ── CONTENT INTELLIGENCE v4.4 (2) ──
|
|
596
|
-
{ name: 'wp_get_content_brief', description: '
|
|
597
|
-
inputSchema: { type: 'object', properties: { id: { type: 'number'
|
|
598
|
-
{ name: 'wp_extract_post_outline', description: '
|
|
599
|
-
inputSchema: { type: 'object', properties: { category_id: { type: 'number'
|
|
597
|
+
{ 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.',
|
|
598
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number' }, post_type: { type: 'string', default: 'post', description: 'post or page' } }, required: ['id'] }},
|
|
599
|
+
{ 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.',
|
|
600
|
+
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'] }},
|
|
600
601
|
|
|
601
602
|
// ── CONTENT INTELLIGENCE v4.4 Week 2 (3) ──
|
|
602
|
-
{ name: 'wp_audit_readability', description: '
|
|
603
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
604
|
-
{ name: 'wp_audit_update_frequency', description: '
|
|
605
|
-
inputSchema: { type: 'object', properties: { days_threshold: { type: 'number', description: 'Flag posts not modified in X days (default 180)' }, limit: { type: 'number'
|
|
606
|
-
{ name: 'wp_build_link_map', description: '
|
|
607
|
-
inputSchema: { type: 'object', properties: { post_type: { type: 'string', enum: ['post', 'page', 'both']
|
|
603
|
+
{ name: 'wp_audit_readability', description: 'Use to score text readability (Flesch-Kincaid adapted). Returns transition density and passive ratio. Read-only.',
|
|
604
|
+
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)' } }}},
|
|
605
|
+
{ name: 'wp_audit_update_frequency', description: 'Use to find stale posts not updated since N days, cross-referenced with SEO score. Read-only.',
|
|
606
|
+
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)' } }}},
|
|
607
|
+
{ name: 'wp_build_link_map', description: 'Use to generate full internal link matrix with simplified PageRank scores per post. Read-only.',
|
|
608
|
+
inputSchema: { type: 'object', properties: { post_type: { type: 'string', enum: ['post', 'page', 'both'] }, limit: { type: 'number' }, category_id: { type: 'number' } }}},
|
|
608
609
|
|
|
609
610
|
// ── CONTENT INTELLIGENCE v4.4 Week 3 (3) ──
|
|
610
|
-
{ name: 'wp_audit_anchor_texts', description: '
|
|
611
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
612
|
-
{ name: 'wp_audit_schema_markup', description: '
|
|
613
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
614
|
-
{ name: 'wp_audit_content_structure', description: '
|
|
615
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
611
|
+
{ name: 'wp_audit_anchor_texts', description: 'Use to check internal link anchor diversity. Detects generic (\'click here\') and over-optimized anchors. Read-only.',
|
|
612
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
613
|
+
{ 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.',
|
|
614
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
615
|
+
{ name: 'wp_audit_content_structure', description: 'Use to analyze post structure: intro/body/conclusion ratio, FAQ presence, TOC, lists, tables. Read-only.',
|
|
616
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] }, category_id: { type: 'number' } }}},
|
|
616
617
|
|
|
617
618
|
// ── CONTENT INTELLIGENCE v4.4 Batch 4A (4) ──
|
|
618
|
-
{ name: 'wp_find_duplicate_content', description: '
|
|
619
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
620
|
-
{ name: 'wp_find_content_gaps', description: '
|
|
621
|
-
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']
|
|
622
|
-
{ name: 'wp_extract_faq_blocks', description: '
|
|
623
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
624
|
-
{ name: 'wp_audit_cta_presence', description: '
|
|
625
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
619
|
+
{ name: 'wp_find_duplicate_content', description: 'Use to detect near-duplicate posts via TF-IDF cosine similarity. Read-only.',
|
|
620
|
+
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)' } }}},
|
|
621
|
+
{ name: 'wp_find_content_gaps', description: 'Use to find under-represented taxonomy terms (< N posts) as content creation opportunities. Read-only.',
|
|
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'] }, exclude_empty: { type: 'boolean', description: 'Exclude terms with 0 posts (default false)' } }}},
|
|
623
|
+
{ name: 'wp_extract_faq_blocks', description: 'Use to inventory all FAQ blocks (Gutenberg + schema JSON-LD) across the corpus. Read-only.',
|
|
624
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
625
|
+
{ name: 'wp_audit_cta_presence', description: 'Use to detect presence/absence of CTAs (contact links, forms, buttons) per post. Read-only.',
|
|
626
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] }, category_id: { type: 'number' } }}},
|
|
626
627
|
|
|
627
628
|
// ── CONTENT INTELLIGENCE v4.4 Batch 4B (4) ──
|
|
628
|
-
{ name: 'wp_extract_entities', description: '
|
|
629
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
630
|
-
{ name: 'wp_get_publishing_velocity', description: '
|
|
631
|
-
inputSchema: { type: 'object', properties: { periods: { type: 'string', description: "Comma-separated day periods (default '30,90,180')" }, post_type: { type: 'string', enum: ['post', 'page']
|
|
632
|
-
{ name: 'wp_compare_revisions_diff', description: '
|
|
633
|
-
inputSchema: { type: 'object', properties: { post_id: { type: 'number'
|
|
634
|
-
{ name: 'wp_list_posts_by_word_count', description: '
|
|
635
|
-
inputSchema: { type: 'object', properties: { limit: { type: 'number'
|
|
629
|
+
{ name: 'wp_extract_entities', description: 'Use to extract named entities (brands, places, people, organizations) from post content. Read-only.',
|
|
630
|
+
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)' } }}},
|
|
631
|
+
{ name: 'wp_get_publishing_velocity', description: 'Use to measure publication cadence per author and category over 30/90/180 days. Read-only.',
|
|
632
|
+
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' } }}},
|
|
633
|
+
{ name: 'wp_compare_revisions_diff', description: 'Use to diff two revisions and measure update amplitude. Read-only.',
|
|
634
|
+
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'] }},
|
|
635
|
+
{ 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.',
|
|
636
|
+
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
|
+
|
|
638
|
+
// ── PLUGIN INTELLIGENCE v4.5 (3) ──
|
|
639
|
+
{ 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.',
|
|
640
|
+
inputSchema: { type: 'object', properties: { post_id: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] } }, required: ['post_id'] }},
|
|
641
|
+
{ 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.',
|
|
642
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page'] } }}},
|
|
643
|
+
{ 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.',
|
|
644
|
+
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
|
+
|
|
646
|
+
// ── PLUGIN INTELLIGENCE v4.5 batch 2 (3) ──
|
|
647
|
+
{ 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.',
|
|
648
|
+
inputSchema: { type: 'object', properties: { limit: { type: 'number' }, post_type: { type: 'string', enum: ['post', 'page', 'both'] } }}},
|
|
649
|
+
{ 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.',
|
|
650
|
+
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'] } }}},
|
|
651
|
+
{ 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.',
|
|
652
|
+
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'] }}
|
|
636
653
|
];
|
|
637
654
|
|
|
655
|
+
function getFilteredTools(allTools = TOOLS_DEFINITIONS) {
|
|
656
|
+
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'];
|
|
657
|
+
const editorialTools = ['wp_submit_for_review', 'wp_approve_post', 'wp_reject_post'];
|
|
658
|
+
return allTools.filter(tool => {
|
|
659
|
+
const n = tool.name;
|
|
660
|
+
if (n.startsWith('wc_') && !process.env.WC_CONSUMER_KEY) return false;
|
|
661
|
+
if (editorialTools.includes(n) && process.env.WP_REQUIRE_APPROVAL !== 'true') return false;
|
|
662
|
+
if (pluginIntelTools.includes(n) && process.env.WP_ENABLE_PLUGIN_INTELLIGENCE !== 'true') return false;
|
|
663
|
+
return true;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
638
667
|
function registerHandlers(s) {
|
|
639
|
-
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools:
|
|
668
|
+
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: getFilteredTools() }));
|
|
640
669
|
s.setRequestHandler(CallToolRequestSchema, handleToolCall);
|
|
641
670
|
}
|
|
642
671
|
|
|
@@ -778,11 +807,19 @@ export async function handleToolCall(request) {
|
|
|
778
807
|
// ── PAGES ──
|
|
779
808
|
|
|
780
809
|
case 'wp_list_pages': {
|
|
781
|
-
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 } });
|
|
782
|
-
const { per_page = 10, page = 1, status = 'publish', parent, orderby = 'menu_order', order = 'asc', search } = args;
|
|
810
|
+
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'] } });
|
|
811
|
+
const { per_page = 10, page = 1, status = 'publish', parent, orderby = 'menu_order', order = 'asc', search, mode = 'full' } = args;
|
|
783
812
|
let ep = `/pages?per_page=${per_page}&page=${page}&status=${status}&orderby=${orderby}&order=${order}`;
|
|
784
813
|
if (parent !== undefined) ep += `&parent=${parent}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
785
814
|
const pgs = await wpApiCall(ep);
|
|
815
|
+
if (mode === 'ids_only') {
|
|
816
|
+
result = json({ total: pgs.length, page, mode: 'ids_only', ids: pgs.map(p => p.id) });
|
|
817
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
818
|
+
}
|
|
819
|
+
if (mode === 'summary') {
|
|
820
|
+
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 })) });
|
|
821
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
822
|
+
}
|
|
786
823
|
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) })) });
|
|
787
824
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
788
825
|
break;
|
|
@@ -822,11 +859,19 @@ export async function handleToolCall(request) {
|
|
|
822
859
|
// ── MEDIA ──
|
|
823
860
|
|
|
824
861
|
case 'wp_list_media': {
|
|
825
|
-
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 } });
|
|
826
|
-
const { per_page = 10, page = 1, media_type, search, orderby = 'date', order = 'desc' } = args;
|
|
862
|
+
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'] } });
|
|
863
|
+
const { per_page = 10, page = 1, media_type, search, orderby = 'date', order = 'desc', mode = 'full' } = args;
|
|
827
864
|
let ep = `/media?per_page=${per_page}&page=${page}&orderby=${orderby}&order=${order}`;
|
|
828
865
|
if (media_type) ep += `&media_type=${media_type}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
829
866
|
const media = await wpApiCall(ep);
|
|
867
|
+
if (mode === 'ids_only') {
|
|
868
|
+
result = json({ total: media.length, page, mode: 'ids_only', ids: media.map(m => m.id) });
|
|
869
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
870
|
+
}
|
|
871
|
+
if (mode === 'summary') {
|
|
872
|
+
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 })) });
|
|
873
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
874
|
+
}
|
|
830
875
|
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 })) });
|
|
831
876
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
832
877
|
break;
|
|
@@ -863,20 +908,36 @@ export async function handleToolCall(request) {
|
|
|
863
908
|
// ── TAXONOMIES ──
|
|
864
909
|
|
|
865
910
|
case 'wp_list_categories': {
|
|
866
|
-
const { per_page = 100, page = 1, parent, search, orderby = 'name', hide_empty = false } = args;
|
|
911
|
+
const { per_page = 100, page = 1, parent, search, orderby = 'name', hide_empty = false, mode = 'full' } = args;
|
|
867
912
|
let ep = `/categories?per_page=${per_page}&page=${page}&orderby=${orderby}&hide_empty=${hide_empty}`;
|
|
868
913
|
if (parent !== undefined) ep += `&parent=${parent}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
869
914
|
const cats = await wpApiCall(ep);
|
|
915
|
+
if (mode === 'ids_only') {
|
|
916
|
+
result = json({ total: cats.length, mode: 'ids_only', ids: cats.map(c => c.id) });
|
|
917
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
918
|
+
}
|
|
919
|
+
if (mode === 'summary') {
|
|
920
|
+
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 })) });
|
|
921
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
922
|
+
}
|
|
870
923
|
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 })) });
|
|
871
924
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
872
925
|
break;
|
|
873
926
|
}
|
|
874
927
|
|
|
875
928
|
case 'wp_list_tags': {
|
|
876
|
-
const { per_page = 100, page = 1, search, orderby = 'name', hide_empty = false } = args;
|
|
929
|
+
const { per_page = 100, page = 1, search, orderby = 'name', hide_empty = false, mode = 'full' } = args;
|
|
877
930
|
let ep = `/tags?per_page=${per_page}&page=${page}&orderby=${orderby}&hide_empty=${hide_empty}`;
|
|
878
931
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
879
932
|
const tags = await wpApiCall(ep);
|
|
933
|
+
if (mode === 'ids_only') {
|
|
934
|
+
result = json({ total: tags.length, mode: 'ids_only', ids: tags.map(t => t.id) });
|
|
935
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
936
|
+
}
|
|
937
|
+
if (mode === 'summary') {
|
|
938
|
+
result = json({ total: tags.length, mode: 'summary', tags: tags.map(t => ({ id: t.id, name: t.name, slug: t.slug, count: t.count })) });
|
|
939
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
940
|
+
}
|
|
880
941
|
result = json({ total: tags.length, tags: tags.map(t => ({ id: t.id, name: t.name, slug: t.slug, count: t.count })) });
|
|
881
942
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
882
943
|
break;
|
|
@@ -897,10 +958,18 @@ export async function handleToolCall(request) {
|
|
|
897
958
|
// ── COMMENTS ──
|
|
898
959
|
|
|
899
960
|
case 'wp_list_comments': {
|
|
900
|
-
const { per_page = 10, page = 1, post, status, orderby = 'date_gmt', order = 'desc', search } = args;
|
|
961
|
+
const { per_page = 10, page = 1, post, status, orderby = 'date_gmt', order = 'desc', search, mode = 'full' } = args;
|
|
901
962
|
let ep = `/comments?per_page=${per_page}&page=${page}&orderby=${orderby}&order=${order}`;
|
|
902
963
|
if (post) ep += `&post=${post}`; if (status) ep += `&status=${status}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
903
964
|
const comments = await wpApiCall(ep);
|
|
965
|
+
if (mode === 'ids_only') {
|
|
966
|
+
result = json({ total: comments.length, page, mode: 'ids_only', ids: comments.map(c => c.id) });
|
|
967
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
968
|
+
}
|
|
969
|
+
if (mode === 'summary') {
|
|
970
|
+
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 })) });
|
|
971
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
972
|
+
}
|
|
904
973
|
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 })) });
|
|
905
974
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
906
975
|
break;
|
|
@@ -927,9 +996,9 @@ export async function handleToolCall(request) {
|
|
|
927
996
|
}
|
|
928
997
|
|
|
929
998
|
case 'wp_list_custom_posts': {
|
|
930
|
-
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 } });
|
|
999
|
+
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'] } });
|
|
931
1000
|
enforceAllowedTypes(args.post_type);
|
|
932
|
-
const { post_type, per_page = 10, page = 1, status = 'publish', orderby = 'date', order = 'desc', search } = args;
|
|
1001
|
+
const { post_type, per_page = 10, page = 1, status = 'publish', orderby = 'date', order = 'desc', search, mode = 'full' } = args;
|
|
933
1002
|
const types = await wpApiCall('/types');
|
|
934
1003
|
const typeInfo = Object.values(types).find(t => t.slug === post_type || t.rest_base === post_type);
|
|
935
1004
|
if (!typeInfo) throw new Error(`Post type "${post_type}" not found.`);
|
|
@@ -937,6 +1006,14 @@ export async function handleToolCall(request) {
|
|
|
937
1006
|
let ep = `/${restBase}?per_page=${per_page}&page=${page}&status=${status}&orderby=${orderby}&order=${order}`;
|
|
938
1007
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
939
1008
|
const posts = await wpApiCall(ep);
|
|
1009
|
+
if (mode === 'ids_only') {
|
|
1010
|
+
result = json({ post_type, total: posts.length, page, mode: 'ids_only', ids: posts.map(p => p.id) });
|
|
1011
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } }); break;
|
|
1012
|
+
}
|
|
1013
|
+
if (mode === 'summary') {
|
|
1014
|
+
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 })) });
|
|
1015
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } }); break;
|
|
1016
|
+
}
|
|
940
1017
|
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 || {} })) });
|
|
941
1018
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0, params: { post_type } });
|
|
942
1019
|
break;
|
|
@@ -945,10 +1022,18 @@ export async function handleToolCall(request) {
|
|
|
945
1022
|
// ── USERS ──
|
|
946
1023
|
|
|
947
1024
|
case 'wp_list_users': {
|
|
948
|
-
const { per_page = 10, page = 1, roles, search, orderby = 'name' } = args;
|
|
1025
|
+
const { per_page = 10, page = 1, roles, search, orderby = 'name', mode = 'full' } = args;
|
|
949
1026
|
let ep = `/users?per_page=${per_page}&page=${page}&orderby=${orderby}`;
|
|
950
1027
|
if (roles) ep += `&roles=${roles}`; if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
951
1028
|
const users = await wpApiCall(ep);
|
|
1029
|
+
if (mode === 'ids_only') {
|
|
1030
|
+
result = json({ total: users.length, mode: 'ids_only', ids: users.map(u => u.id) });
|
|
1031
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1032
|
+
}
|
|
1033
|
+
if (mode === 'summary') {
|
|
1034
|
+
result = json({ total: users.length, mode: 'summary', users: users.map(u => ({ id: u.id, name: u.name, slug: u.slug, roles: u.roles || [] })) });
|
|
1035
|
+
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 }); break;
|
|
1036
|
+
}
|
|
952
1037
|
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'] })) });
|
|
953
1038
|
auditLog({ tool: name, action: 'list', status: 'success', latency_ms: Date.now() - t0 });
|
|
954
1039
|
break;
|
|
@@ -1005,7 +1090,14 @@ export async function handleToolCall(request) {
|
|
|
1005
1090
|
enabled: isMultiTarget, active_site: currentTarget?.name || 'default',
|
|
1006
1091
|
available_sites: Object.keys(targets)
|
|
1007
1092
|
},
|
|
1008
|
-
server:
|
|
1093
|
+
server: (() => {
|
|
1094
|
+
const exposed = getFilteredTools().length;
|
|
1095
|
+
const groups = [];
|
|
1096
|
+
if (!process.env.WC_CONSUMER_KEY) groups.push('woocommerce');
|
|
1097
|
+
if (process.env.WP_REQUIRE_APPROVAL !== 'true') groups.push('editorial');
|
|
1098
|
+
if (process.env.WP_ENABLE_PLUGIN_INTELLIGENCE !== 'true') groups.push('plugin_intelligence');
|
|
1099
|
+
return { mcp_version: VERSION, tools_total: TOOLS_COUNT, tools_exposed: exposed, filtered_out: groups };
|
|
1100
|
+
})()
|
|
1009
1101
|
});
|
|
1010
1102
|
auditLog({ tool: name, action: 'info', status: 'success', latency_ms: Date.now() - t0 });
|
|
1011
1103
|
break;
|
|
@@ -1247,15 +1339,20 @@ export async function handleToolCall(request) {
|
|
|
1247
1339
|
validateInput(args, {
|
|
1248
1340
|
search: { type: 'string' },
|
|
1249
1341
|
status: { type: 'string', enum: ['active', 'inactive', 'all'] },
|
|
1250
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1342
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1343
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1251
1344
|
});
|
|
1252
|
-
const { search, status = 'all', per_page = 20 } = args;
|
|
1345
|
+
const { search, status = 'all', per_page = 20, mode = 'full' } = args;
|
|
1253
1346
|
let ep = `/plugins?per_page=${per_page}&context=edit`;
|
|
1254
1347
|
if (search) ep += `&search=${encodeURIComponent(search)}`;
|
|
1255
1348
|
if (status && status !== 'all') ep += `&status=${status}`;
|
|
1256
1349
|
|
|
1257
1350
|
try {
|
|
1258
1351
|
const plugins = await wpApiCall(ep);
|
|
1352
|
+
if (mode === 'ids_only') {
|
|
1353
|
+
result = json({ total: plugins.length, mode: 'ids_only', ids: plugins.map(p => p.plugin) });
|
|
1354
|
+
auditLog({ tool: name, action: 'list', target_type: 'plugin', status: 'success', latency_ms: Date.now() - t0, params: { search, status, per_page } }); break;
|
|
1355
|
+
}
|
|
1259
1356
|
const mapped = plugins.map(p => ({
|
|
1260
1357
|
plugin: p.plugin,
|
|
1261
1358
|
name: p.name,
|
|
@@ -1269,6 +1366,10 @@ export async function handleToolCall(request) {
|
|
|
1269
1366
|
network_only: p.network_only ?? false,
|
|
1270
1367
|
textdomain: p.textdomain ?? ''
|
|
1271
1368
|
}));
|
|
1369
|
+
if (mode === 'summary') {
|
|
1370
|
+
result = json({ total: mapped.length, mode: 'summary', plugins: mapped.map(p => ({ plugin: p.plugin, name: p.name, status: p.status, version: p.version })) });
|
|
1371
|
+
auditLog({ tool: name, action: 'list', target_type: 'plugin', status: 'success', latency_ms: Date.now() - t0, params: { search, status, per_page } }); break;
|
|
1372
|
+
}
|
|
1272
1373
|
const activeCount = mapped.filter(p => p.status === 'active').length;
|
|
1273
1374
|
const inactiveCount = mapped.filter(p => p.status === 'inactive').length;
|
|
1274
1375
|
result = json({ total: mapped.length, active: activeCount, inactive: inactiveCount, plugins: mapped });
|
|
@@ -1329,13 +1430,18 @@ export async function handleToolCall(request) {
|
|
|
1329
1430
|
case 'wp_list_themes': {
|
|
1330
1431
|
validateInput(args, {
|
|
1331
1432
|
status: { type: 'string', enum: ['active', 'inactive', 'all'] },
|
|
1332
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1433
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1434
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1333
1435
|
});
|
|
1334
|
-
const { status = 'all', per_page = 20 } = args;
|
|
1436
|
+
const { status = 'all', per_page = 20, mode = 'full' } = args;
|
|
1335
1437
|
let ep = `/themes?per_page=${per_page}&context=edit`;
|
|
1336
1438
|
if (status && status !== 'all') ep += `&status=${status}`;
|
|
1337
1439
|
try {
|
|
1338
1440
|
const themes = await wpApiCall(ep);
|
|
1441
|
+
if (mode === 'ids_only') {
|
|
1442
|
+
result = json({ total: themes.length, mode: 'ids_only', ids: themes.map(t => t.stylesheet) });
|
|
1443
|
+
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } }); break;
|
|
1444
|
+
}
|
|
1339
1445
|
const mapped = themes.map(t => ({
|
|
1340
1446
|
stylesheet: t.stylesheet,
|
|
1341
1447
|
template: t.template,
|
|
@@ -1350,6 +1456,10 @@ export async function handleToolCall(request) {
|
|
|
1350
1456
|
requires_php: t.requires_php ?? '',
|
|
1351
1457
|
tags: t.tags?.rendered ?? t.tags ?? []
|
|
1352
1458
|
}));
|
|
1459
|
+
if (mode === 'summary') {
|
|
1460
|
+
result = json({ total: mapped.length, mode: 'summary', themes: mapped.map(t => ({ stylesheet: t.stylesheet, name: t.name, status: t.status, version: t.version })) });
|
|
1461
|
+
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } }); break;
|
|
1462
|
+
}
|
|
1353
1463
|
const activeTheme = mapped.find(t => t.status === 'active');
|
|
1354
1464
|
result = json({ total: mapped.length, active_theme: activeTheme ? activeTheme.name : null, themes: mapped });
|
|
1355
1465
|
auditLog({ tool: name, action: 'list', target_type: 'theme', status: 'success', latency_ms: Date.now() - t0, params: { status, per_page } });
|
|
@@ -1399,12 +1509,21 @@ export async function handleToolCall(request) {
|
|
|
1399
1509
|
validateInput(args, {
|
|
1400
1510
|
post_id: { type: 'number', required: true, min: 1 },
|
|
1401
1511
|
post_type: { type: 'string', enum: ['post', 'page'] },
|
|
1402
|
-
per_page: { type: 'number', min: 1, max: 100 }
|
|
1512
|
+
per_page: { type: 'number', min: 1, max: 100 },
|
|
1513
|
+
mode: { type: 'string', enum: ['full', 'summary', 'ids_only'] }
|
|
1403
1514
|
});
|
|
1404
|
-
const { post_id, post_type = 'post', per_page = 10 } = args;
|
|
1515
|
+
const { post_id, post_type = 'post', per_page = 10, mode = 'full' } = args;
|
|
1405
1516
|
const base = post_type === 'page' ? 'pages' : 'posts';
|
|
1406
1517
|
try {
|
|
1407
1518
|
const revisions = await wpApiCall(`/${base}/${post_id}/revisions?per_page=${per_page}&context=edit`);
|
|
1519
|
+
if (mode === 'ids_only') {
|
|
1520
|
+
result = json({ total: revisions.length, post_id, post_type, mode: 'ids_only', ids: revisions.map(r => r.id) });
|
|
1521
|
+
auditLog({ tool: name, action: 'list', target: post_id, target_type: 'revision', status: 'success', latency_ms: Date.now() - t0, params: { post_type, per_page } }); break;
|
|
1522
|
+
}
|
|
1523
|
+
if (mode === 'summary') {
|
|
1524
|
+
result = json({ total: revisions.length, post_id, post_type, mode: 'summary', revisions: revisions.map(r => ({ id: r.id, date: r.date, author: r.author })) });
|
|
1525
|
+
auditLog({ tool: name, action: 'list', target: post_id, target_type: 'revision', status: 'success', latency_ms: Date.now() - t0, params: { post_type, per_page } }); break;
|
|
1526
|
+
}
|
|
1408
1527
|
result = json({
|
|
1409
1528
|
total: revisions.length,
|
|
1410
1529
|
post_id,
|
|
@@ -4419,6 +4538,457 @@ export async function handleToolCall(request) {
|
|
|
4419
4538
|
break;
|
|
4420
4539
|
}
|
|
4421
4540
|
|
|
4541
|
+
// ── PLUGIN INTELLIGENCE v4.5 ──
|
|
4542
|
+
|
|
4543
|
+
case 'wp_get_rendered_head': {
|
|
4544
|
+
validateInput(args, { post_id: { type: 'number', required: true }, post_type: { type: 'string', enum: ['post', 'page'] } });
|
|
4545
|
+
const grhPostId = args.post_id;
|
|
4546
|
+
const grhPostType = args.post_type || 'post';
|
|
4547
|
+
const { url: grhBaseUrl, auth: grhAuth } = getActiveAuth();
|
|
4548
|
+
|
|
4549
|
+
const grhPlugin = await detectSeoPlugin(grhBaseUrl, fetch);
|
|
4550
|
+
if (!grhPlugin) throw new Error('No supported SEO plugin detected. wp_get_rendered_head requires RankMath or Yoast.');
|
|
4551
|
+
if (grhPlugin !== 'rankmath' && grhPlugin !== 'yoast') throw new Error(`Rendered head requires RankMath or Yoast (detected: ${grhPlugin})`);
|
|
4552
|
+
|
|
4553
|
+
const grhEp = grhPostType === 'page' ? `/pages/${grhPostId}?_fields=id,title,link,slug,meta` : `/posts/${grhPostId}?_fields=id,title,link,slug,meta`;
|
|
4554
|
+
const grhPost = await wpApiCall(grhEp);
|
|
4555
|
+
|
|
4556
|
+
const grhHeadResult = await getRenderedHead(grhBaseUrl, grhPost.link, grhPlugin, fetch, grhAuth);
|
|
4557
|
+
if (!grhHeadResult.success) throw new Error(grhHeadResult.error);
|
|
4558
|
+
|
|
4559
|
+
const grhParsed = parseRenderedHead(grhHeadResult.head);
|
|
4560
|
+
const grhMeta = grhPost.meta || {};
|
|
4561
|
+
|
|
4562
|
+
// Extract stored SEO meta based on plugin
|
|
4563
|
+
let grhStoredTitle, grhStoredDesc, grhStoredKeyword, grhStoredCanonical, grhStoredRobots;
|
|
4564
|
+
if (grhPlugin === 'rankmath') {
|
|
4565
|
+
grhStoredTitle = grhMeta.rank_math_title || null;
|
|
4566
|
+
grhStoredDesc = grhMeta.rank_math_description || null;
|
|
4567
|
+
grhStoredKeyword = grhMeta.rank_math_focus_keyword || null;
|
|
4568
|
+
grhStoredCanonical = grhMeta.rank_math_canonical_url || null;
|
|
4569
|
+
const rm = grhMeta.rank_math_robots || [];
|
|
4570
|
+
grhStoredRobots = Array.isArray(rm) && rm.length > 0 ? rm.join(', ') : null;
|
|
4571
|
+
} else {
|
|
4572
|
+
grhStoredTitle = grhMeta._yoast_wpseo_title || null;
|
|
4573
|
+
grhStoredDesc = grhMeta._yoast_wpseo_metadesc || null;
|
|
4574
|
+
grhStoredKeyword = grhMeta._yoast_wpseo_focuskw || null;
|
|
4575
|
+
grhStoredCanonical = grhMeta._yoast_wpseo_canonical || null;
|
|
4576
|
+
grhStoredRobots = grhMeta._yoast_wpseo_meta_robots_noindex === '1' ? 'noindex' : null;
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4579
|
+
result = json({
|
|
4580
|
+
post_id: grhPostId,
|
|
4581
|
+
post_url: grhPost.link,
|
|
4582
|
+
seo_plugin: grhPlugin,
|
|
4583
|
+
rendered: grhParsed,
|
|
4584
|
+
stored: {
|
|
4585
|
+
title: grhStoredTitle,
|
|
4586
|
+
description: grhStoredDesc,
|
|
4587
|
+
focus_keyword: grhStoredKeyword,
|
|
4588
|
+
canonical: grhStoredCanonical,
|
|
4589
|
+
robots: grhStoredRobots
|
|
4590
|
+
},
|
|
4591
|
+
raw_head_length: grhHeadResult.head.length,
|
|
4592
|
+
schemas_count: grhParsed.schema_json_ld.length
|
|
4593
|
+
});
|
|
4594
|
+
auditLog({ tool: name, action: 'get_rendered_head', status: 'success', latency_ms: Date.now() - t0, params: { post_id: grhPostId, post_type: grhPostType, plugin: grhPlugin } });
|
|
4595
|
+
break;
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
case 'wp_audit_rendered_seo': {
|
|
4599
|
+
validateInput(args, { limit: { type: 'number', min: 1, max: 50 }, post_type: { type: 'string', enum: ['post', 'page'] } });
|
|
4600
|
+
const arsLimit = args.limit || 10;
|
|
4601
|
+
const arsPostType = args.post_type || 'post';
|
|
4602
|
+
const { url: arsBaseUrl, auth: arsAuth } = getActiveAuth();
|
|
4603
|
+
|
|
4604
|
+
const arsPlugin = await detectSeoPlugin(arsBaseUrl, fetch);
|
|
4605
|
+
if (!arsPlugin) throw new Error('No supported SEO plugin detected. wp_audit_rendered_seo requires RankMath or Yoast.');
|
|
4606
|
+
if (arsPlugin !== 'rankmath' && arsPlugin !== 'yoast') throw new Error(`Rendered SEO audit requires RankMath or Yoast (detected: ${arsPlugin})`);
|
|
4607
|
+
|
|
4608
|
+
const arsEp = `/${arsPostType}s?per_page=${arsLimit}&status=publish&_fields=id,title,link,slug,meta`;
|
|
4609
|
+
const arsPosts = await wpApiCall(arsEp);
|
|
4610
|
+
|
|
4611
|
+
const arsResults = [];
|
|
4612
|
+
const arsSummary = { title_mismatch: 0, description_mismatch: 0, canonical_mismatch: 0, missing_rendered_title: 0, missing_rendered_description: 0, robots_mismatch: 0, schema_missing: 0 };
|
|
4613
|
+
|
|
4614
|
+
for (const p of arsPosts) {
|
|
4615
|
+
const headRes = await getRenderedHead(arsBaseUrl, p.link, arsPlugin, fetch, arsAuth);
|
|
4616
|
+
if (!headRes.success) {
|
|
4617
|
+
arsResults.push({ id: p.id, title: strip(p.title?.rendered || ''), url: p.link, score: 0, issues: ['head_fetch_failed'], rendered: null, stored: null });
|
|
4618
|
+
continue;
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
const parsed = parseRenderedHead(headRes.head);
|
|
4622
|
+
const meta = p.meta || {};
|
|
4623
|
+
const issues = [];
|
|
4624
|
+
|
|
4625
|
+
// Extract stored meta
|
|
4626
|
+
let storedTitle, storedDesc, storedCanonical, storedRobots;
|
|
4627
|
+
if (arsPlugin === 'rankmath') {
|
|
4628
|
+
storedTitle = meta.rank_math_title || null;
|
|
4629
|
+
storedDesc = meta.rank_math_description || null;
|
|
4630
|
+
storedCanonical = meta.rank_math_canonical_url || null;
|
|
4631
|
+
const rm = meta.rank_math_robots || [];
|
|
4632
|
+
storedRobots = Array.isArray(rm) && rm.length > 0 ? rm.join(', ') : null;
|
|
4633
|
+
} else {
|
|
4634
|
+
storedTitle = meta._yoast_wpseo_title || null;
|
|
4635
|
+
storedDesc = meta._yoast_wpseo_metadesc || null;
|
|
4636
|
+
storedCanonical = meta._yoast_wpseo_canonical || null;
|
|
4637
|
+
storedRobots = meta._yoast_wpseo_meta_robots_noindex === '1' ? 'noindex' : null;
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4640
|
+
// Compare rendered vs stored
|
|
4641
|
+
if (!parsed.title) { issues.push('missing_rendered_title'); arsSummary.missing_rendered_title++; }
|
|
4642
|
+
else if (storedTitle && !parsed.title.includes(storedTitle)) { issues.push('title_mismatch'); arsSummary.title_mismatch++; }
|
|
4643
|
+
|
|
4644
|
+
if (!parsed.meta_description) { issues.push('missing_rendered_description'); arsSummary.missing_rendered_description++; }
|
|
4645
|
+
else if (storedDesc && parsed.meta_description !== storedDesc) { issues.push('description_mismatch'); arsSummary.description_mismatch++; }
|
|
4646
|
+
|
|
4647
|
+
if (parsed.canonical && parsed.canonical !== p.link && storedCanonical && parsed.canonical !== storedCanonical) { issues.push('canonical_mismatch'); arsSummary.canonical_mismatch++; }
|
|
4648
|
+
|
|
4649
|
+
if (parsed.robots && parsed.robots.includes('noindex') && (!storedRobots || !storedRobots.includes('noindex'))) { issues.push('robots_mismatch'); arsSummary.robots_mismatch++; }
|
|
4650
|
+
|
|
4651
|
+
if (parsed.schema_json_ld.length === 0) { issues.push('schema_missing'); arsSummary.schema_missing++; }
|
|
4652
|
+
|
|
4653
|
+
const score = Math.max(0, 100 - issues.length * 15);
|
|
4654
|
+
|
|
4655
|
+
arsResults.push({
|
|
4656
|
+
id: p.id, title: strip(p.title?.rendered || ''), url: p.link, score, issues,
|
|
4657
|
+
rendered: { title: parsed.title, description: parsed.meta_description, canonical: parsed.canonical, robots: parsed.robots, schemas_count: parsed.schema_json_ld.length },
|
|
4658
|
+
stored: { title: storedTitle, description: storedDesc, canonical: storedCanonical, robots: storedRobots }
|
|
4659
|
+
});
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
const arsAvgScore = arsResults.length > 0 ? arsResults.reduce((s, r) => s + r.score, 0) / arsResults.length : 0;
|
|
4663
|
+
|
|
4664
|
+
result = json({
|
|
4665
|
+
seo_plugin: arsPlugin,
|
|
4666
|
+
total_audited: arsResults.length,
|
|
4667
|
+
avg_score: Math.round(arsAvgScore),
|
|
4668
|
+
issues_summary: arsSummary,
|
|
4669
|
+
posts: arsResults
|
|
4670
|
+
});
|
|
4671
|
+
auditLog({ tool: name, action: 'audit_rendered_seo', status: 'success', latency_ms: Date.now() - t0, params: { limit: arsLimit, post_type: arsPostType, plugin: arsPlugin } });
|
|
4672
|
+
break;
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
case 'wp_get_pillar_content': {
|
|
4676
|
+
validateInput(args, {
|
|
4677
|
+
post_id: { type: 'number' },
|
|
4678
|
+
set_pillar: { type: 'boolean' },
|
|
4679
|
+
list_pillars: { type: 'boolean' },
|
|
4680
|
+
post_type: { type: 'string', enum: ['post', 'page'] },
|
|
4681
|
+
limit: { type: 'number', min: 1, max: 500 }
|
|
4682
|
+
});
|
|
4683
|
+
const pcPostType = args.post_type || 'post';
|
|
4684
|
+
const { url: pcBaseUrl } = getActiveAuth();
|
|
4685
|
+
|
|
4686
|
+
const pcPlugin = await detectSeoPlugin(pcBaseUrl, fetch);
|
|
4687
|
+
if (pcPlugin !== 'rankmath') throw new Error('Pillar content requires RankMath (detected: ' + (pcPlugin || 'none') + ')');
|
|
4688
|
+
|
|
4689
|
+
if (args.list_pillars) {
|
|
4690
|
+
// Mode: list all pillar posts
|
|
4691
|
+
const pcLimit = args.limit || 100;
|
|
4692
|
+
const pcPosts = await wpApiCall(`/${pcPostType}s?per_page=${pcLimit}&status=publish&_fields=id,title,link,slug,meta`);
|
|
4693
|
+
const pillars = pcPosts.filter(p => (p.meta || {}).rank_math_pillar_content === 'on').map(p => ({
|
|
4694
|
+
id: p.id, title: strip(p.title?.rendered || ''), slug: p.slug, link: p.link, post_type: pcPostType
|
|
4695
|
+
}));
|
|
4696
|
+
result = json({ mode: 'list_pillars', seo_plugin: pcPlugin, pillar_count: pillars.length, pillars });
|
|
4697
|
+
auditLog({ tool: name, action: 'read_pillar_content', status: 'success', latency_ms: Date.now() - t0, params: { list_pillars: true, post_type: pcPostType, limit: pcLimit } });
|
|
4698
|
+
} else if (args.post_id !== undefined && args.set_pillar !== undefined) {
|
|
4699
|
+
// Mode: write — set/unset pillar flag
|
|
4700
|
+
if (getActiveControls().read_only) throw new Error('Blocked: READ-ONLY mode. Cannot update pillar content flag.');
|
|
4701
|
+
const pcPost = await wpApiCall(`/${pcPostType}s/${args.post_id}?_fields=id,title,link,meta`);
|
|
4702
|
+
await wpApiCall(`/${pcPostType}s/${args.post_id}`, { method: 'POST', body: JSON.stringify({ meta: { rank_math_pillar_content: args.set_pillar ? 'on' : '' } }) });
|
|
4703
|
+
result = json({
|
|
4704
|
+
mode: 'write', post_id: args.post_id, title: strip(pcPost.title?.rendered || ''),
|
|
4705
|
+
is_pillar: args.set_pillar, action: args.set_pillar ? 'marked_as_pillar' : 'unmarked_as_pillar', seo_plugin: pcPlugin
|
|
4706
|
+
});
|
|
4707
|
+
auditLog({ tool: name, action: 'update_pillar_content', target: args.post_id, target_type: pcPostType, status: 'success', latency_ms: Date.now() - t0, params: { set_pillar: args.set_pillar } });
|
|
4708
|
+
} else if (args.post_id !== undefined) {
|
|
4709
|
+
// Mode: read single post
|
|
4710
|
+
const pcPost = await wpApiCall(`/${pcPostType}s/${args.post_id}?_fields=id,title,link,meta`);
|
|
4711
|
+
const isPillar = (pcPost.meta || {}).rank_math_pillar_content === 'on';
|
|
4712
|
+
result = json({ mode: 'read', post_id: args.post_id, title: strip(pcPost.title?.rendered || ''), is_pillar: isPillar, seo_plugin: pcPlugin });
|
|
4713
|
+
auditLog({ tool: name, action: 'read_pillar_content', status: 'success', latency_ms: Date.now() - t0, params: { post_id: args.post_id, post_type: pcPostType } });
|
|
4714
|
+
} else {
|
|
4715
|
+
throw new Error('Provide post_id (read/write) or list_pillars:true');
|
|
4716
|
+
}
|
|
4717
|
+
break;
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
case 'wp_audit_schema_plugins': {
|
|
4721
|
+
validateInput(args, {
|
|
4722
|
+
limit: { type: 'number', min: 1, max: 100 },
|
|
4723
|
+
post_type: { type: 'string', enum: ['post', 'page', 'both'] }
|
|
4724
|
+
});
|
|
4725
|
+
const aspLimit = args.limit || 20;
|
|
4726
|
+
const aspPostType = args.post_type || 'post';
|
|
4727
|
+
const { url: aspBaseUrl, auth: aspAuth } = getActiveAuth();
|
|
4728
|
+
|
|
4729
|
+
const aspPlugin = await detectSeoPlugin(aspBaseUrl, fetch);
|
|
4730
|
+
if (!aspPlugin) throw new Error('No supported SEO plugin detected');
|
|
4731
|
+
if (aspPlugin !== 'rankmath' && aspPlugin !== 'yoast') throw new Error(`Schema plugin audit requires RankMath or Yoast (detected: ${aspPlugin})`);
|
|
4732
|
+
|
|
4733
|
+
const aspRequired = {
|
|
4734
|
+
'Article': ['headline', 'datePublished', 'author'],
|
|
4735
|
+
'BlogPosting': ['headline', 'datePublished', 'author'],
|
|
4736
|
+
'NewsArticle': ['headline', 'datePublished', 'author'],
|
|
4737
|
+
'FAQPage': ['mainEntity'],
|
|
4738
|
+
'HowTo': ['name', 'step'],
|
|
4739
|
+
'LocalBusiness': ['name', 'address'],
|
|
4740
|
+
'BreadcrumbList': ['itemListElement'],
|
|
4741
|
+
'Organization': ['name'],
|
|
4742
|
+
'WebPage': ['name'],
|
|
4743
|
+
'WebSite': ['name', 'url']
|
|
4744
|
+
};
|
|
4745
|
+
|
|
4746
|
+
let aspAllPosts = [];
|
|
4747
|
+
if (aspPostType === 'both') {
|
|
4748
|
+
const aspP = await wpApiCall(`/posts?per_page=${aspLimit}&status=publish&_fields=id,title,link,slug,meta`);
|
|
4749
|
+
const aspG = await wpApiCall(`/pages?per_page=${aspLimit}&status=publish&_fields=id,title,link,slug,meta`);
|
|
4750
|
+
aspAllPosts = [...aspP, ...aspG];
|
|
4751
|
+
} else {
|
|
4752
|
+
aspAllPosts = await wpApiCall(`/${aspPostType}s?per_page=${aspLimit}&status=publish&_fields=id,title,link,slug,meta`);
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
const aspResults = [];
|
|
4756
|
+
const aspTypesCounts = {};
|
|
4757
|
+
const aspIssuesSummary = { no_plugin_schema: 0, invalid_schema_json: 0, missing_required_fields: 0, no_article_schema: 0 };
|
|
4758
|
+
let aspWithSchema = 0;
|
|
4759
|
+
|
|
4760
|
+
for (const p of aspAllPosts) {
|
|
4761
|
+
const postIssues = [];
|
|
4762
|
+
const postSchemas = [];
|
|
4763
|
+
let schemas = [];
|
|
4764
|
+
|
|
4765
|
+
if (aspPlugin === 'rankmath') {
|
|
4766
|
+
const rawSchema = (p.meta || {}).rank_math_schema;
|
|
4767
|
+
if (!rawSchema || rawSchema === '{}') {
|
|
4768
|
+
postIssues.push('no_plugin_schema');
|
|
4769
|
+
aspIssuesSummary.no_plugin_schema++;
|
|
4770
|
+
} else {
|
|
4771
|
+
try {
|
|
4772
|
+
const parsed = typeof rawSchema === 'string' ? JSON.parse(rawSchema) : rawSchema;
|
|
4773
|
+
if (parsed['@type']) {
|
|
4774
|
+
schemas = [parsed];
|
|
4775
|
+
} else if (parsed['@graph']) {
|
|
4776
|
+
schemas = parsed['@graph'];
|
|
4777
|
+
} else {
|
|
4778
|
+
schemas = Object.values(parsed).filter(v => v && typeof v === 'object' && v['@type']);
|
|
4779
|
+
}
|
|
4780
|
+
if (schemas.length === 0) { postIssues.push('no_plugin_schema'); aspIssuesSummary.no_plugin_schema++; }
|
|
4781
|
+
} catch {
|
|
4782
|
+
postIssues.push('invalid_schema_json');
|
|
4783
|
+
aspIssuesSummary.invalid_schema_json++;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
} else {
|
|
4787
|
+
const headRes = await getRenderedHead(aspBaseUrl, p.link, aspPlugin, fetch, aspAuth);
|
|
4788
|
+
if (headRes.success) {
|
|
4789
|
+
const parsed = parseRenderedHead(headRes.head);
|
|
4790
|
+
schemas = parsed.schema_json_ld || [];
|
|
4791
|
+
if (schemas.length === 0) { postIssues.push('no_plugin_schema'); aspIssuesSummary.no_plugin_schema++; }
|
|
4792
|
+
} else {
|
|
4793
|
+
postIssues.push('no_plugin_schema');
|
|
4794
|
+
aspIssuesSummary.no_plugin_schema++;
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
let hasArticleType = false;
|
|
4799
|
+
for (const schema of schemas) {
|
|
4800
|
+
const schemaType = schema['@type'] || 'Unknown';
|
|
4801
|
+
if (['Article', 'BlogPosting', 'NewsArticle'].includes(schemaType)) hasArticleType = true;
|
|
4802
|
+
aspTypesCounts[schemaType] = (aspTypesCounts[schemaType] || 0) + 1;
|
|
4803
|
+
|
|
4804
|
+
const requiredFields = aspRequired[schemaType];
|
|
4805
|
+
const missingFields = [];
|
|
4806
|
+
if (requiredFields) {
|
|
4807
|
+
for (const field of requiredFields) {
|
|
4808
|
+
if (!schema[field]) missingFields.push(field);
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
if (missingFields.length > 0) { postIssues.push('missing_required_fields'); aspIssuesSummary.missing_required_fields++; }
|
|
4812
|
+
postSchemas.push({ type: schemaType, valid: missingFields.length === 0, missing_fields: missingFields });
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
if (schemas.length > 0 && !hasArticleType && aspPostType === 'post') {
|
|
4816
|
+
postIssues.push('no_article_schema');
|
|
4817
|
+
aspIssuesSummary.no_article_schema++;
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
if (schemas.length > 0) aspWithSchema++;
|
|
4821
|
+
const postScore = Math.max(0, 100 - postIssues.length * 15);
|
|
4822
|
+
aspResults.push({ id: p.id, title: strip(p.title?.rendered || ''), slug: p.slug, score: postScore, schemas: postSchemas, issues: postIssues });
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
const aspAvg = aspResults.length > 0 ? aspResults.reduce((s, r) => s + r.score, 0) / aspResults.length : 0;
|
|
4826
|
+
const aspWithout = aspAllPosts.length - aspWithSchema;
|
|
4827
|
+
|
|
4828
|
+
result = json({
|
|
4829
|
+
seo_plugin: aspPlugin,
|
|
4830
|
+
total_audited: aspResults.length,
|
|
4831
|
+
avg_score: Math.round(aspAvg),
|
|
4832
|
+
schema_coverage: {
|
|
4833
|
+
posts_with_schema: aspWithSchema,
|
|
4834
|
+
posts_without_schema: aspWithout,
|
|
4835
|
+
coverage_percent: aspAllPosts.length > 0 ? Math.round(aspWithSchema / aspAllPosts.length * 100) : 0
|
|
4836
|
+
},
|
|
4837
|
+
schema_types_found: aspTypesCounts,
|
|
4838
|
+
issues_summary: aspIssuesSummary,
|
|
4839
|
+
posts: aspResults
|
|
4840
|
+
});
|
|
4841
|
+
auditLog({ tool: name, action: 'audit_schema_plugins', status: 'success', latency_ms: Date.now() - t0, params: { limit: aspLimit, post_type: aspPostType, plugin: aspPlugin } });
|
|
4842
|
+
break;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
case 'wp_get_seo_score': {
|
|
4846
|
+
validateInput(args, {
|
|
4847
|
+
post_id: { type: 'number' },
|
|
4848
|
+
limit: { type: 'number', min: 1, max: 100 },
|
|
4849
|
+
post_type: { type: 'string', enum: ['post', 'page'] },
|
|
4850
|
+
order: { type: 'string', enum: ['asc', 'desc'] }
|
|
4851
|
+
});
|
|
4852
|
+
const gssPostType = args.post_type || 'post';
|
|
4853
|
+
const { url: gssBaseUrl } = getActiveAuth();
|
|
4854
|
+
|
|
4855
|
+
const gssPlugin = await detectSeoPlugin(gssBaseUrl, fetch);
|
|
4856
|
+
if (gssPlugin !== 'rankmath') throw new Error('SEO score requires RankMath (detected: ' + (gssPlugin || 'none') + ')');
|
|
4857
|
+
|
|
4858
|
+
if (args.post_id !== undefined) {
|
|
4859
|
+
const gssPost = await wpApiCall(`/${gssPostType}s/${args.post_id}?_fields=id,title,link,slug,meta`);
|
|
4860
|
+
const gssMeta = gssPost.meta || {};
|
|
4861
|
+
const gssRaw = gssMeta.rank_math_seo_score;
|
|
4862
|
+
const gssScore = gssRaw !== undefined && gssRaw !== null && gssRaw !== '' ? parseInt(gssRaw, 10) : null;
|
|
4863
|
+
const gssKw = gssMeta.rank_math_focus_keyword || null;
|
|
4864
|
+
const gssRating = gssScore === null || gssScore === 0 ? 'no_score' : gssScore >= 80 ? 'excellent' : gssScore >= 60 ? 'good' : gssScore >= 40 ? 'average' : 'poor';
|
|
4865
|
+
|
|
4866
|
+
result = json({
|
|
4867
|
+
mode: 'single', post_id: args.post_id, title: strip(gssPost.title?.rendered || ''),
|
|
4868
|
+
link: gssPost.link, seo_score: gssScore, focus_keyword: gssKw, rating: gssRating
|
|
4869
|
+
});
|
|
4870
|
+
auditLog({ tool: name, action: 'get_seo_score', status: 'success', latency_ms: Date.now() - t0, params: { post_id: args.post_id, post_type: gssPostType } });
|
|
4871
|
+
} else {
|
|
4872
|
+
const gssLimit = args.limit || 20;
|
|
4873
|
+
const gssSortOrder = args.order || 'desc';
|
|
4874
|
+
const gssPosts = await wpApiCall(`/${gssPostType}s?per_page=${gssLimit}&status=publish&_fields=id,title,link,slug,meta`);
|
|
4875
|
+
|
|
4876
|
+
const gssItems = gssPosts.map(p => {
|
|
4877
|
+
const m = p.meta || {};
|
|
4878
|
+
const raw = m.rank_math_seo_score;
|
|
4879
|
+
const score = raw !== undefined && raw !== null && raw !== '' ? parseInt(raw, 10) : null;
|
|
4880
|
+
const kw = m.rank_math_focus_keyword || null;
|
|
4881
|
+
const rating = score === null || score === 0 ? 'no_score' : score >= 80 ? 'excellent' : score >= 60 ? 'good' : score >= 40 ? 'average' : 'poor';
|
|
4882
|
+
return { id: p.id, title: strip(p.title?.rendered || ''), slug: p.slug, link: p.link, seo_score: score, focus_keyword: kw, rating };
|
|
4883
|
+
});
|
|
4884
|
+
|
|
4885
|
+
gssItems.sort((a, b) => {
|
|
4886
|
+
const sa = a.seo_score === null ? -1 : a.seo_score;
|
|
4887
|
+
const sb = b.seo_score === null ? -1 : b.seo_score;
|
|
4888
|
+
return gssSortOrder === 'asc' ? sa - sb : sb - sa;
|
|
4889
|
+
});
|
|
4890
|
+
|
|
4891
|
+
const gssDist = { excellent: 0, good: 0, average: 0, poor: 0, no_score: 0 };
|
|
4892
|
+
const gssScores = [];
|
|
4893
|
+
for (const item of gssItems) {
|
|
4894
|
+
gssDist[item.rating]++;
|
|
4895
|
+
if (item.seo_score !== null && item.seo_score > 0) gssScores.push(item.seo_score);
|
|
4896
|
+
}
|
|
4897
|
+
const gssTotal = gssItems.length;
|
|
4898
|
+
const gssAvg = gssScores.length > 0 ? gssScores.reduce((a, b) => a + b, 0) / gssScores.length : 0;
|
|
4899
|
+
const gssSorted = [...gssScores].sort((a, b) => a - b);
|
|
4900
|
+
const gssMedian = gssSorted.length > 0 ? (gssSorted.length % 2 === 0 ? (gssSorted[gssSorted.length / 2 - 1] + gssSorted[gssSorted.length / 2]) / 2 : gssSorted[Math.floor(gssSorted.length / 2)]) : 0;
|
|
4901
|
+
|
|
4902
|
+
result = json({
|
|
4903
|
+
mode: 'bulk', total_analyzed: gssItems.length,
|
|
4904
|
+
avg_score: Math.round(gssAvg), median_score: gssMedian,
|
|
4905
|
+
distribution: {
|
|
4906
|
+
excellent: { count: gssDist.excellent, percent: gssTotal > 0 ? Math.round(gssDist.excellent / gssTotal * 100) : 0 },
|
|
4907
|
+
good: { count: gssDist.good, percent: gssTotal > 0 ? Math.round(gssDist.good / gssTotal * 100) : 0 },
|
|
4908
|
+
average: { count: gssDist.average, percent: gssTotal > 0 ? Math.round(gssDist.average / gssTotal * 100) : 0 },
|
|
4909
|
+
poor: { count: gssDist.poor, percent: gssTotal > 0 ? Math.round(gssDist.poor / gssTotal * 100) : 0 },
|
|
4910
|
+
no_score: { count: gssDist.no_score, percent: gssTotal > 0 ? Math.round(gssDist.no_score / gssTotal * 100) : 0 }
|
|
4911
|
+
},
|
|
4912
|
+
posts: gssItems
|
|
4913
|
+
});
|
|
4914
|
+
auditLog({ tool: name, action: 'get_seo_score', status: 'success', latency_ms: Date.now() - t0, params: { limit: gssLimit, post_type: gssPostType, order: gssSortOrder } });
|
|
4915
|
+
}
|
|
4916
|
+
break;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
case 'wp_get_twitter_meta': {
|
|
4920
|
+
validateInput(args, {
|
|
4921
|
+
post_id: { type: 'number', required: true },
|
|
4922
|
+
post_type: { type: 'string', enum: ['post', 'page'] },
|
|
4923
|
+
twitter_title: { type: 'string' },
|
|
4924
|
+
twitter_description: { type: 'string' },
|
|
4925
|
+
twitter_image: { type: 'string' }
|
|
4926
|
+
});
|
|
4927
|
+
const gtmPostId = args.post_id;
|
|
4928
|
+
const gtmPostType = args.post_type || 'post';
|
|
4929
|
+
const { url: gtmBaseUrl } = getActiveAuth();
|
|
4930
|
+
const gtmPlugin = await detectSeoPlugin(gtmBaseUrl, fetch);
|
|
4931
|
+
if (!gtmPlugin) throw new Error('No supported SEO plugin detected');
|
|
4932
|
+
|
|
4933
|
+
const gtmIsWrite = args.twitter_title !== undefined || args.twitter_description !== undefined || args.twitter_image !== undefined;
|
|
4934
|
+
|
|
4935
|
+
if (gtmIsWrite) {
|
|
4936
|
+
if (getActiveControls().read_only) throw new Error('Blocked: READ-ONLY mode. Cannot update Twitter meta.');
|
|
4937
|
+
if (gtmPlugin !== 'rankmath' && gtmPlugin !== 'yoast') throw new Error('Twitter meta write requires RankMath or Yoast (detected: ' + gtmPlugin + ')');
|
|
4938
|
+
|
|
4939
|
+
const gtmMeta = {};
|
|
4940
|
+
const gtmUpdated = [];
|
|
4941
|
+
if (gtmPlugin === 'rankmath') {
|
|
4942
|
+
if (args.twitter_title !== undefined) { gtmMeta.rank_math_twitter_title = args.twitter_title; gtmUpdated.push('twitter_title'); }
|
|
4943
|
+
if (args.twitter_description !== undefined) { gtmMeta.rank_math_twitter_description = args.twitter_description; gtmUpdated.push('twitter_description'); }
|
|
4944
|
+
if (args.twitter_image !== undefined) { gtmMeta.rank_math_twitter_image = args.twitter_image; gtmUpdated.push('twitter_image'); }
|
|
4945
|
+
} else {
|
|
4946
|
+
if (args.twitter_title !== undefined) { gtmMeta['_yoast_wpseo_twitter-title'] = args.twitter_title; gtmUpdated.push('twitter_title'); }
|
|
4947
|
+
if (args.twitter_description !== undefined) { gtmMeta['_yoast_wpseo_twitter-description'] = args.twitter_description; gtmUpdated.push('twitter_description'); }
|
|
4948
|
+
if (args.twitter_image !== undefined) { gtmMeta['_yoast_wpseo_twitter-image'] = args.twitter_image; gtmUpdated.push('twitter_image'); }
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
const gtmPost = await wpApiCall(`/${gtmPostType}s/${gtmPostId}?_fields=id,title,link,meta`);
|
|
4952
|
+
await wpApiCall(`/${gtmPostType}s/${gtmPostId}`, { method: 'POST', body: JSON.stringify({ meta: gtmMeta }) });
|
|
4953
|
+
|
|
4954
|
+
result = json({
|
|
4955
|
+
mode: 'write', post_id: gtmPostId, title: strip(gtmPost.title?.rendered || ''),
|
|
4956
|
+
seo_plugin: gtmPlugin, updated_fields: gtmUpdated,
|
|
4957
|
+
twitter: { title: args.twitter_title || null, description: args.twitter_description || null, image: args.twitter_image || null }
|
|
4958
|
+
});
|
|
4959
|
+
auditLog({ tool: name, action: 'update_twitter_meta', target: gtmPostId, target_type: gtmPostType, status: 'success', latency_ms: Date.now() - t0, params: { updated_fields: gtmUpdated } });
|
|
4960
|
+
} else {
|
|
4961
|
+
const gtmPost = await wpApiCall(`/${gtmPostType}s/${gtmPostId}?_fields=id,title,link,meta`);
|
|
4962
|
+
const gtmM = gtmPost.meta || {};
|
|
4963
|
+
|
|
4964
|
+
let gtmTitle, gtmDesc, gtmImage, gtmCard;
|
|
4965
|
+
if (gtmPlugin === 'rankmath') {
|
|
4966
|
+
gtmTitle = gtmM.rank_math_twitter_title || null;
|
|
4967
|
+
gtmDesc = gtmM.rank_math_twitter_description || null;
|
|
4968
|
+
gtmImage = gtmM.rank_math_twitter_image || null;
|
|
4969
|
+
gtmCard = gtmM.rank_math_twitter_card_type || null;
|
|
4970
|
+
} else if (gtmPlugin === 'yoast') {
|
|
4971
|
+
gtmTitle = gtmM['_yoast_wpseo_twitter-title'] || null;
|
|
4972
|
+
gtmDesc = gtmM['_yoast_wpseo_twitter-description'] || null;
|
|
4973
|
+
gtmImage = gtmM['_yoast_wpseo_twitter-image'] || null;
|
|
4974
|
+
gtmCard = null;
|
|
4975
|
+
} else {
|
|
4976
|
+
gtmTitle = gtmM._seopress_social_twitter_title || null;
|
|
4977
|
+
gtmDesc = gtmM._seopress_social_twitter_desc || null;
|
|
4978
|
+
gtmImage = gtmM._seopress_social_twitter_img || null;
|
|
4979
|
+
gtmCard = null;
|
|
4980
|
+
}
|
|
4981
|
+
|
|
4982
|
+
result = json({
|
|
4983
|
+
mode: 'read', post_id: gtmPostId, title: strip(gtmPost.title?.rendered || ''),
|
|
4984
|
+
link: gtmPost.link, seo_plugin: gtmPlugin,
|
|
4985
|
+
twitter: { title: gtmTitle, description: gtmDesc, image: gtmImage, card_type: gtmCard }
|
|
4986
|
+
});
|
|
4987
|
+
auditLog({ tool: name, action: 'read_twitter_meta', status: 'success', latency_ms: Date.now() - t0, params: { post_id: gtmPostId, post_type: gtmPostType, plugin: gtmPlugin } });
|
|
4988
|
+
}
|
|
4989
|
+
break;
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4422
4992
|
default:
|
|
4423
4993
|
throw new Error(`Unknown tool: "${name}".`);
|
|
4424
4994
|
}
|
|
@@ -4484,4 +5054,4 @@ if (process.env.NODE_ENV !== 'test') {
|
|
|
4484
5054
|
main().catch((error) => { log.error(`Fatal: ${error.message}`); process.exit(1); });
|
|
4485
5055
|
}
|
|
4486
5056
|
|
|
4487
|
-
export { server, getActiveControls, getControlSources, _testSetTarget };
|
|
5057
|
+
export { server, getActiveControls, getControlSources, _testSetTarget, getFilteredTools };
|