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-system-info.php

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

371 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Tools for WordPress System Information & Diagnostics
4 *
5 * Provides tools for AI-assisted code analysis, plugin auditing,
6 * cron inspection, and site-wide diagnostics.
7 *
8 * @package MetaSync
9 * @subpackage MCP_Server/Tools
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 require_once plugin_dir_path(dirname(__FILE__)) . 'class-mcp-tool-base.php';
17
18 /**
19 * System Diagnostics Tool
20 *
21 * Returns a full system snapshot: WordPress, PHP, server, DB, active
22 * plugins, active theme, and cron summary — in one call.
23 */
24 class MCP_Tool_System_Diagnostics extends MCP_Tool_Base {
25
26 public function get_name() {
27 return 'wordpress_system_diagnostics';
28 }
29
30 public function get_description() {
31 return 'Get a full system snapshot: WordPress version, PHP/server config, database info, active plugins, active theme, and WP-Cron summary. Use this as the starting point for any site-wide analysis.';
32 }
33
34 public function get_input_schema() {
35 return [
36 'type' => 'object',
37 'properties' => (object)[],
38 ];
39 }
40
41 public function execute($params) {
42 $this->require_capability('manage_options');
43
44 global $wpdb;
45
46 // ── WordPress ────────────────────────────────────────────────
47 $wp = [
48 'version' => get_bloginfo('version'),
49 'multisite' => is_multisite(),
50 'site_url' => get_site_url(),
51 'home_url' => get_home_url(),
52 'wp_debug' => defined('WP_DEBUG') && WP_DEBUG,
53 'wp_debug_log' => defined('WP_DEBUG_LOG') && WP_DEBUG_LOG,
54 'wp_debug_display' => defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY,
55 'script_debug' => defined('SCRIPT_DEBUG') && SCRIPT_DEBUG,
56 'memory_limit' => defined('WP_MEMORY_LIMIT') ? WP_MEMORY_LIMIT : ini_get('memory_limit'),
57 'max_memory_limit' => defined('WP_MAX_MEMORY_LIMIT') ? WP_MAX_MEMORY_LIMIT : 'N/A',
58 'abspath' => ABSPATH,
59 'content_dir' => WP_CONTENT_DIR,
60 'uploads_dir' => wp_upload_dir()['basedir'],
61 'permalink_structure' => get_option('permalink_structure') ?: '(plain)',
62 'timezone' => wp_timezone_string(),
63 'language' => get_locale(),
64 ];
65
66 // ── PHP ──────────────────────────────────────────────────────
67 $php = [
68 'version' => PHP_VERSION,
69 'memory_limit' => ini_get('memory_limit'),
70 'max_execution_time' => ini_get('max_execution_time'),
71 'upload_max_filesize'=> ini_get('upload_max_filesize'),
72 'post_max_size' => ini_get('post_max_size'),
73 'max_input_vars' => ini_get('max_input_vars'),
74 'display_errors' => ini_get('display_errors'),
75 'extensions' => array_values(array_intersect(
76 ['curl', 'gd', 'imagick', 'mbstring', 'openssl', 'xml', 'zip', 'intl', 'exif'],
77 get_loaded_extensions()
78 )),
79 ];
80
81 // ── Server ───────────────────────────────────────────────────
82 $server = [
83 'software' => isset($_SERVER['SERVER_SOFTWARE']) ? sanitize_text_field($_SERVER['SERVER_SOFTWARE']) : 'unknown',
84 'os' => PHP_OS,
85 'sapi' => PHP_SAPI,
86 'https' => is_ssl(),
87 ];
88
89 // ── Database ─────────────────────────────────────────────────
90 $db_version = $wpdb->get_var('SELECT VERSION()');
91 $db = [
92 'mysql_version' => $db_version,
93 'db_name' => defined('DB_NAME') ? DB_NAME : 'N/A',
94 'db_host' => defined('DB_HOST') ? DB_HOST : 'N/A',
95 'db_charset' => defined('DB_CHARSET') ? DB_CHARSET : 'N/A',
96 'table_prefix' => $wpdb->prefix,
97 'table_count' => (int) $wpdb->get_var(
98 $wpdb->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = %s', DB_NAME)
99 ),
100 ];
101
102 // ── Active Plugins ───────────────────────────────────────────
103 if (!function_exists('get_plugins')) {
104 require_once ABSPATH . 'wp-admin/includes/plugin.php';
105 }
106 $all_plugins = get_plugins();
107 $active_slugs = (array) get_option('active_plugins', []);
108 $active_plugins = [];
109 foreach ($active_slugs as $slug) {
110 if (isset($all_plugins[$slug])) {
111 $p = $all_plugins[$slug];
112 $active_plugins[] = [
113 'slug' => $slug,
114 'name' => $p['Name'],
115 'version' => $p['Version'],
116 'author' => $p['Author'],
117 ];
118 }
119 }
120
121 // ── Active Theme ─────────────────────────────────────────────
122 $theme = wp_get_theme();
123 $theme_data = [
124 'name' => $theme->get('Name'),
125 'version' => $theme->get('Version'),
126 'author' => $theme->get('Author'),
127 'template' => $theme->get_template(),
128 'stylesheet' => $theme->get_stylesheet(),
129 'parent' => $theme->parent() ? $theme->parent()->get('Name') : null,
130 ];
131
132 // ── WP-Cron summary ──────────────────────────────────────────
133 $cron_events = _get_cron_array();
134 $cron_count = 0;
135 $cron_hooks = [];
136 if (is_array($cron_events)) {
137 foreach ($cron_events as $timestamp => $hooks) {
138 foreach ($hooks as $hook => $jobs) {
139 $cron_count += count($jobs);
140 $cron_hooks[] = $hook;
141 }
142 }
143 }
144 $cron_summary = [
145 'total_events' => $cron_count,
146 'unique_hooks' => count(array_unique($cron_hooks)),
147 'hooks_preview' => array_slice(array_unique($cron_hooks), 0, 20),
148 ];
149
150 return $this->success([
151 'wordpress' => $wp,
152 'php' => $php,
153 'server' => $server,
154 'database' => $db,
155 'active_plugins' => $active_plugins,
156 'active_theme' => $theme_data,
157 'cron_summary' => $cron_summary,
158 ], 'System diagnostics retrieved successfully');
159 }
160 }
161
162 /**
163 * List All Plugins Tool
164 *
165 * Returns all installed plugins (active and inactive) with full metadata.
166 */
167 class MCP_Tool_List_All_Plugins extends MCP_Tool_Base {
168
169 public function get_name() {
170 return 'wordpress_list_plugins';
171 }
172
173 public function get_description() {
174 return 'List all installed WordPress plugins (active and inactive) with name, version, author, description, and active status. Useful for auditing dependencies and finding outdated or vulnerable plugins.';
175 }
176
177 public function get_input_schema() {
178 return [
179 'type' => 'object',
180 'properties' => [
181 'status' => [
182 'type' => 'string',
183 'enum' => ['all', 'active', 'inactive'],
184 'description' => 'Filter by active status (default: all)',
185 ],
186 ],
187 ];
188 }
189
190 public function execute($params) {
191 $this->require_capability('manage_options');
192
193 if (!function_exists('get_plugins')) {
194 require_once ABSPATH . 'wp-admin/includes/plugin.php';
195 }
196
197 $filter = isset($params['status']) ? $params['status'] : 'all';
198 $all_plugins = get_plugins();
199 $active_slugs = (array) get_option('active_plugins', []);
200 $result = [];
201
202 foreach ($all_plugins as $slug => $p) {
203 $is_active = in_array($slug, $active_slugs, true);
204
205 if ($filter === 'active' && !$is_active) continue;
206 if ($filter === 'inactive' && $is_active) continue;
207
208 $result[] = [
209 'slug' => $slug,
210 'name' => $p['Name'],
211 'version' => $p['Version'],
212 'author' => wp_strip_all_tags($p['Author']),
213 'description' => wp_strip_all_tags($p['Description']),
214 'plugin_uri' => $p['PluginURI'],
215 'text_domain' => $p['TextDomain'],
216 'requires_wp' => $p['RequiresWP'] ?? '',
217 'requires_php'=> $p['RequiresPHP'] ?? '',
218 'active' => $is_active,
219 ];
220 }
221
222 usort($result, function($a, $b) {
223 if ($a['active'] !== $b['active']) return $a['active'] ? -1 : 1;
224 return strcasecmp($a['name'], $b['name']);
225 });
226
227 return $this->success([
228 'total' => count($result),
229 'plugins' => $result,
230 ]);
231 }
232 }
233
234 /**
235 * Get WP-Cron Jobs Tool
236 *
237 * Lists all scheduled WP-Cron events with timestamps and recurrence.
238 */
239 class MCP_Tool_Get_Cron_Jobs extends MCP_Tool_Base {
240
241 public function get_name() {
242 return 'wordpress_get_cron_jobs';
243 }
244
245 public function get_description() {
246 return 'List all scheduled WP-Cron events: hook name, next run time, recurrence interval, and arguments. Useful for diagnosing missed cron jobs or unexpected background processing.';
247 }
248
249 public function get_input_schema() {
250 return [
251 'type' => 'object',
252 'properties' => [
253 'hook' => [
254 'type' => 'string',
255 'description' => 'Filter by hook name (partial match, optional)',
256 ],
257 ],
258 ];
259 }
260
261 public function execute($params) {
262 $this->require_capability('manage_options');
263
264 $cron_array = _get_cron_array();
265 $hook_filter = isset($params['hook']) ? strtolower(sanitize_text_field($params['hook'])) : '';
266 $events = [];
267 $now = time();
268
269 if (!is_array($cron_array)) {
270 return $this->success(['total' => 0, 'events' => []], 'No cron events scheduled');
271 }
272
273 foreach ($cron_array as $timestamp => $hooks) {
274 foreach ($hooks as $hook => $jobs) {
275 if ($hook_filter && strpos(strtolower($hook), $hook_filter) === false) {
276 continue;
277 }
278 foreach ($jobs as $job) {
279 $schedule = $job['schedule'] ?? 'one-time';
280 $interval = $job['interval'] ?? null;
281 $events[] = [
282 'hook' => $hook,
283 'next_run_ts' => $timestamp,
284 'next_run' => date('Y-m-d H:i:s', $timestamp),
285 'overdue_seconds'=> max(0, $now - $timestamp),
286 'overdue' => $timestamp < $now,
287 'schedule' => $schedule,
288 'interval_sec' => $interval,
289 'args' => $job['args'] ?? [],
290 ];
291 }
292 }
293 }
294
295 usort($events, function($a, $b) { return $a['next_run_ts'] - $b['next_run_ts']; });
296
297 return $this->success([
298 'total' => count($events),
299 'events' => $events,
300 ]);
301 }
302 }
303
304 /**
305 * Get WP Option Tool
306 *
307 * Reads a single wp_options entry by key.
308 */
309 class MCP_Tool_Get_WP_Option extends MCP_Tool_Base {
310
311 public function get_name() {
312 return 'wordpress_get_option';
313 }
314
315 public function get_description() {
316 return 'Read a WordPress option (wp_options row) by key. Returns the stored value. Useful for inspecting plugin configurations and site settings stored in the options table.';
317 }
318
319 public function get_input_schema() {
320 return [
321 'type' => 'object',
322 'properties' => [
323 'option_name' => [
324 'type' => 'string',
325 'description' => 'The option key to read (e.g. "blogname", "active_plugins", "siteurl")',
326 ],
327 ],
328 'required' => ['option_name'],
329 ];
330 }
331
332 // Option keys that must never be exposed even to admins via this tool
333 private $blocked_keys = [
334 'auth_key', 'secure_auth_key', 'logged_in_key', 'nonce_key',
335 'auth_salt', 'secure_auth_salt', 'logged_in_salt', 'nonce_salt',
336 ];
337
338 public function execute($params) {
339 $this->validate_params($params);
340 $this->require_capability('manage_options');
341
342 $key = sanitize_text_field($params['option_name']);
343
344 if (in_array($key, $this->blocked_keys, true)) {
345 throw new Exception("Option '{$key}' is blocked for security reasons");
346 }
347
348 $value = get_option($key, '__NOT_FOUND__');
349
350 if ($value === '__NOT_FOUND__') {
351 return $this->success([
352 'option_name' => $key,
353 'exists' => false,
354 'value' => null,
355 ], "Option '{$key}' does not exist");
356 }
357
358 // Serialize objects/arrays for readable output
359 if (is_array($value) || is_object($value)) {
360 $value = json_decode(json_encode($value), true);
361 }
362
363 return $this->success([
364 'option_name' => $key,
365 'exists' => true,
366 'value' => $value,
367 'type' => gettype($value),
368 ]);
369 }
370 }
371