PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.14
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.14
2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 All 139 releases
metasync / wp-mcp-server / tools / class-mcp-tool-seo-analysis.php

class-mcp-tool-seo-analysis.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.14, at wp-mcp-server/tools/class-mcp-tool-seo-analysis.php

256 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Tool: SEO Analysis
4 *
5 * Provides tools for analyzing SEO aspects of WordPress posts.
6 *
7 * @package MetaSync
8 * @subpackage MCP_Server/Tools
9 */
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 /**
16 * Analyze SEO Tool
17 */
18 class MCP_Tool_Analyze_SEO extends MCP_Tool_Base {
19
20 public function get_name() {
21 return 'wordpress_analyze_seo';
22 }
23
24 public function get_description() {
25 return 'Analyze SEO aspects of a post (title length, description, keyword usage, content analysis)';
26 }
27
28 public function get_input_schema() {
29 return [
30 'type' => 'object',
31 'properties' => [
32 'post_id' => [
33 'type' => 'integer',
34 'description' => 'WordPress post or page ID',
35 'minimum' => 1
36 ]
37 ],
38 'required' => ['post_id']
39 ];
40 }
41
42 public function execute($params) {
43 $this->validate_params($params);
44 $this->require_capability('read');
45
46 $post_id = $this->sanitize_integer($params['post_id']);
47
48 // Verify post exists
49 $post = $this->verify_post_exists($post_id);
50
51 // Get SEO meta
52 $meta_title = get_post_meta($post_id, '_metasync_metatitle', true);
53 $meta_desc = get_post_meta($post_id, '_metasync_metadesc', true);
54 $focus_keyword = get_post_meta($post_id, '_metasync_focus_keyword', true);
55 $robots_index = get_post_meta($post_id, '_metasync_robots_index', true);
56
57 // Use post title if no meta title
58 $effective_title = !empty($meta_title) ? $meta_title : $post->post_title;
59 $title_length = mb_strlen($effective_title);
60 $desc_length = mb_strlen($meta_desc);
61
62 // Analyze content
63 $content = $post->post_content;
64 $content_text = wp_strip_all_tags($content);
65 $word_count = str_word_count($content_text);
66
67 // Check keyword usage
68 $keyword_in_title = false;
69 $keyword_in_content = false;
70 $keyword_in_description = false;
71
72 if (!empty($focus_keyword)) {
73 $keyword_lower = mb_strtolower($focus_keyword);
74 $keyword_in_title = mb_stripos($effective_title, $focus_keyword) !== false;
75 $keyword_in_content = mb_stripos($content_text, $focus_keyword) !== false;
76 $keyword_in_description = mb_stripos($meta_desc, $focus_keyword) !== false;
77 }
78
79 // Build analysis
80 $analysis = [
81 'post_info' => [
82 'id' => $post_id,
83 'title' => $post->post_title,
84 'type' => $post->post_type,
85 'status' => $post->post_status,
86 'url' => get_permalink($post_id)
87 ],
88 'meta_title' => [
89 'value' => $effective_title,
90 'length' => $title_length,
91 'status' => $this->get_title_status($title_length),
92 'recommendation' => $this->get_title_recommendation($title_length)
93 ],
94 'meta_description' => [
95 'value' => $meta_desc,
96 'length' => $desc_length,
97 'status' => $this->get_description_status($desc_length),
98 'recommendation' => $this->get_description_recommendation($desc_length)
99 ],
100 'focus_keyword' => [
101 'value' => $focus_keyword,
102 'in_title' => $keyword_in_title,
103 'in_description' => $keyword_in_description,
104 'in_content' => $keyword_in_content,
105 'status' => $this->get_keyword_status($focus_keyword, $keyword_in_title, $keyword_in_content)
106 ],
107 'content' => [
108 'word_count' => $word_count,
109 'status' => $this->get_content_status($word_count)
110 ],
111 'indexability' => [
112 'is_indexable' => $robots_index !== 'noindex',
113 'robots_setting' => $robots_index ?: 'index'
114 ],
115 'overall_score' => $this->calculate_seo_score(
116 $title_length,
117 $desc_length,
118 $focus_keyword,
119 $keyword_in_title,
120 $keyword_in_content,
121 $word_count
122 )
123 ];
124
125 return $this->success($analysis);
126 }
127
128 private function get_title_status($length) {
129 if ($length === 0) return 'missing';
130 if ($length < 30) return 'too_short';
131 if ($length > 60) return 'too_long';
132 return 'good';
133 }
134
135 private function get_title_recommendation($length) {
136 if ($length === 0) return 'Add a meta title (30-60 characters recommended)';
137 if ($length < 30) return 'Title is too short. Aim for 30-60 characters.';
138 if ($length > 60) return 'Title is too long. Keep it under 60 characters to avoid truncation in search results.';
139 return 'Title length is optimal';
140 }
141
142 private function get_description_status($length) {
143 if ($length === 0) return 'missing';
144 if ($length < 120) return 'too_short';
145 if ($length > 160) return 'too_long';
146 return 'good';
147 }
148
149 private function get_description_recommendation($length) {
150 if ($length === 0) return 'Add a meta description (120-160 characters recommended)';
151 if ($length < 120) return 'Description is too short. Aim for 120-160 characters.';
152 if ($length > 160) return 'Description is too long. Keep it under 160 characters to avoid truncation.';
153 return 'Description length is optimal';
154 }
155
156 private function get_keyword_status($keyword, $in_title, $in_content) {
157 if (empty($keyword)) return 'not_set';
158 if (!$in_title || !$in_content) return 'not_optimized';
159 return 'good';
160 }
161
162 private function get_content_status($word_count) {
163 if ($word_count === 0) return 'empty';
164 if ($word_count < 300) return 'too_short';
165 if ($word_count > 2000) return 'excellent';
166 return 'good';
167 }
168
169 private function calculate_seo_score($title_len, $desc_len, $keyword, $kw_in_title, $kw_in_content, $word_count) {
170 $score = 0;
171
172 // Title (25 points)
173 if ($title_len >= 30 && $title_len <= 60) $score += 25;
174 elseif ($title_len > 0) $score += 10;
175
176 // Description (25 points)
177 if ($desc_len >= 120 && $desc_len <= 160) $score += 25;
178 elseif ($desc_len > 0) $score += 10;
179
180 // Keyword usage (30 points)
181 if (!empty($keyword)) {
182 $score += 10; // Has keyword
183 if ($kw_in_title) $score += 10;
184 if ($kw_in_content) $score += 10;
185 }
186
187 // Content (20 points)
188 if ($word_count >= 300) $score += 20;
189 elseif ($word_count > 0) $score += 10;
190
191 return [
192 'score' => $score,
193 'max_score' => 100,
194 'percentage' => $score,
195 'rating' => $this->get_rating($score)
196 ];
197 }
198
199 private function get_rating($score) {
200 if ($score >= 80) return 'excellent';
201 if ($score >= 60) return 'good';
202 if ($score >= 40) return 'fair';
203 return 'needs_improvement';
204 }
205 }
206
207 /**
208 * Check Indexability Tool
209 */
210 class MCP_Tool_Check_Indexability extends MCP_Tool_Base {
211
212 public function get_name() {
213 return 'wordpress_check_indexability';
214 }
215
216 public function get_description() {
217 return 'Check if a post is set to be indexed by search engines';
218 }
219
220 public function get_input_schema() {
221 return [
222 'type' => 'object',
223 'properties' => [
224 'post_id' => [
225 'type' => 'integer',
226 'description' => 'WordPress post or page ID',
227 'minimum' => 1
228 ]
229 ],
230 'required' => ['post_id']
231 ];
232 }
233
234 public function execute($params) {
235 $this->validate_params($params);
236 $this->require_capability('read');
237
238 $post_id = $this->sanitize_integer($params['post_id']);
239
240 // Verify post exists
241 $post = $this->verify_post_exists($post_id);
242
243 $robots_index = get_post_meta($post_id, '_metasync_robots_index', true);
244 $is_indexable = ($robots_index !== 'noindex');
245
246 return $this->success([
247 'post_id' => $post_id,
248 'post_title' => $post->post_title,
249 'is_indexable' => $is_indexable,
250 'robots_setting' => $robots_index ?: 'index',
251 'status' => $post->post_status,
252 'url' => get_permalink($post_id)
253 ]);
254 }
255 }
256