'_yoast_wpseo_title', 'metadesc' => '_yoast_wpseo_metadesc', 'focuskw' => '_yoast_wpseo_focuskw', 'canonical' => '_yoast_wpseo_canonical', 'og_title' => '_yoast_wpseo_opengraph-title', 'og_description' => '_yoast_wpseo_opengraph-description', 'og_image' => '_yoast_wpseo_opengraph-image', 'twitter_title' => '_yoast_wpseo_twitter-title', 'twitter_description' => '_yoast_wpseo_twitter-description', 'cornerstone' => '_yoast_wpseo_is_cornerstone', 'primary_category' => '_yoast_wpseo_primary_category', 'noindex' => '_yoast_wpseo_meta-robots-noindex', // '1' noindex, '2' index, '' default 'nofollow' => '_yoast_wpseo_meta-robots-nofollow', // '1' nofollow, '' default 'seo_score' => '_yoast_wpseo_linkdex', 'readability' => '_yoast_wpseo_content_score', ]; public static function register($registry): void { if (!defined('WPSEO_VERSION')) return; $def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) { $schema = ['type' => 'object', 'properties' => $props]; if ($required) $schema['required'] = $required; return ['name' => $name, 'description' => $desc, 'scope' => $scope, 'handler' => 'AskTamMCP\\Extensions\\Batch14::' . $method, 'inputSchema' => $schema]; }; $registry->register($def('yoast_detect', 'Report Yoast SEO version, whether Premium is active, and whether the XML sitemap is enabled.', [], [], 'read', 'detect')); $registry->register($def('yoast_get_meta', 'Get all Yoast meta for a post: title, metadesc, focus keyword, canonical, OG/Twitter, cornerstone flag, primary category, robots, and SEO/readability scores.', ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'getMeta')); $registry->register($def('yoast_set_meta', 'Set Yoast meta on a post. Only supplied fields change. cornerstone is boolean; noindex/nofollow accept true/false.', ['post_id' => ['type' => 'integer'], 'title' => ['type' => 'string'], 'metadesc' => ['type' => 'string'], 'focuskw' => ['type' => 'string'], 'canonical' => ['type' => 'string'], 'og_title' => ['type' => 'string'], 'og_description' => ['type' => 'string'], 'og_image' => ['type' => 'string'], 'twitter_title' => ['type' => 'string'], 'twitter_description' => ['type' => 'string'], 'cornerstone' => ['type' => 'boolean'], 'primary_category' => ['type' => 'integer'], 'noindex' => ['type' => 'boolean'], 'nofollow' => ['type' => 'boolean']], ['post_id'], 'write', 'setMeta')); $registry->register($def('yoast_bulk_audit', 'Scan posts/pages for Yoast problems: missing meta description, missing focus keyword, or low SEO score (<40). Returns the offenders.', ['post_types' => ['type' => 'array'], 'per_page' => ['type' => 'integer', 'default' => 100]], [], 'read', 'bulkAudit')); $registry->register($def('yoast_list_redirects', 'List Yoast Premium redirects (origin -> target, type). Requires Yoast Premium; reports supported=false otherwise.', [], [], 'read', 'listRedirects')); } public static function detect(array $args): array { $opts = get_option('wpseo', []); return [ 'version' => WPSEO_VERSION, 'premium' => defined('WPSEO_PREMIUM_VERSION') ? WPSEO_PREMIUM_VERSION : false, 'sitemap_enabled' => !empty($opts['enable_xml_sitemap']), 'sitemap_url' => home_url('/sitemap_index.xml'), ]; } public static function getMeta(array $args): array { $id = (int) ($args['post_id'] ?? 0); if (!get_post($id)) throw new \RuntimeException('Post not found: ' . $id); $out = ['post_id' => $id]; foreach (self::MAP as $k => $mk) { $v = get_post_meta($id, $mk, true); if ($k === 'cornerstone') $v = ($v === '1'); $out[$k] = $v === '' ? null : $v; } return $out; } public static function setMeta(array $args): array { $id = (int) ($args['post_id'] ?? 0); if (!get_post($id)) throw new \RuntimeException('Post not found: ' . $id); $changed = []; foreach (self::MAP as $k => $mk) { if (!array_key_exists($k, $args)) continue; if (in_array($k, ['seo_score', 'readability'], true)) continue; // computed, not user-set $val = $args[$k]; if ($k === 'cornerstone') $val = !empty($val) ? '1' : ''; elseif ($k === 'noindex') $val = !empty($val) ? '1' : '2'; elseif ($k === 'nofollow') $val = !empty($val) ? '1' : ''; else $val = (string) $val; update_post_meta($id, $mk, $val); $changed[] = $k; } return ['post_id' => $id, 'changed' => $changed]; } public static function bulkAudit(array $args): array { $types = !empty($args['post_types']) && is_array($args['post_types']) ? $args['post_types'] : ['post', 'page']; $posts = get_posts(['post_type' => $types, 'post_status' => 'publish', 'numberposts' => (int) ($args['per_page'] ?? 100), 'fields' => 'ids']); $issues = []; foreach ($posts as $pid) { $md = get_post_meta($pid, '_yoast_wpseo_metadesc', true); $fk = get_post_meta($pid, '_yoast_wpseo_focuskw', true); $sc = (int) get_post_meta($pid, '_yoast_wpseo_linkdex', true); $probs = []; if ($md === '') $probs[] = 'no_metadesc'; if ($fk === '') $probs[] = 'no_focus_keyword'; if ($sc > 0 && $sc < 40) $probs[] = 'low_seo_score(' . $sc . ')'; if ($probs) $issues[] = ['id' => $pid, 'title' => get_the_title($pid), 'issues' => $probs]; } return ['scanned' => count($posts), 'with_issues' => count($issues), 'offenders' => $issues]; } public static function listRedirects(array $args): array { if (!defined('WPSEO_PREMIUM_VERSION')) { return ['supported' => false, 'note' => 'Yoast Premium not active; redirect manager unavailable.', 'redirects' => []]; } $opt = get_option('wpseo-premium-redirects-base', []); $out = []; if (is_array($opt)) { foreach ($opt as $origin => $r) { $out[] = ['origin' => $origin, 'target' => $r['url'] ?? '', 'type' => $r['type'] ?? '']; } } return ['supported' => true, 'count' => count($out), 'redirects' => $out]; } }