PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.5.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.5.23
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 2.5.23 All 138 releases
metasync / wp-mcp-server / class-mcp-tool-base.php

class-mcp-tool-base.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.5.23, at wp-mcp-server/class-mcp-tool-base.php

276 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP Tool Base Class
4 *
5 * Abstract base class for all MCP tools. Provides structure for
6 * tool registration, schema validation, and execution.
7 *
8 * @package MetaSync
9 * @subpackage MCP_Server
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 abstract class MCP_Tool_Base {
17
18 /**
19 * Get tool name
20 *
21 * Must be unique across all tools
22 *
23 * @return string
24 */
25 abstract public function get_name();
26
27 /**
28 * Get tool description
29 *
30 * @return string
31 */
32 abstract public function get_description();
33
34 /**
35 * Get input schema
36 *
37 * JSON Schema format describing expected parameters
38 *
39 * @return array
40 */
41 abstract public function get_input_schema();
42
43 /**
44 * Execute the tool
45 *
46 * @param array $params Input parameters
47 * @return array Result data
48 * @throws InvalidArgumentException If parameters are invalid
49 * @throws Exception If execution fails
50 */
51 abstract public function execute($params);
52
53 /**
54 * Get full tool definition (for tools/list)
55 *
56 * @return array
57 */
58 public function get_definition() {
59 return [
60 'name' => $this->get_name(),
61 'description' => $this->get_description(),
62 'inputSchema' => $this->get_input_schema()
63 ];
64 }
65
66 /**
67 * Validate parameters against schema
68 *
69 * @param array $params Parameters to validate
70 * @return bool
71 * @throws InvalidArgumentException If validation fails
72 */
73 protected function validate_params($params) {
74 $schema = $this->get_input_schema();
75
76 // Check required fields
77 if (isset($schema['required'])) {
78 foreach ($schema['required'] as $required_field) {
79 if (!isset($params[$required_field])) {
80 throw new InvalidArgumentException("Missing required parameter: {$required_field}");
81 }
82 }
83 }
84
85 // Validate types
86 if (isset($schema['properties'])) {
87 foreach ($schema['properties'] as $field => $field_schema) {
88 if (!isset($params[$field])) {
89 continue;
90 }
91
92 $value = $params[$field];
93 $expected_type = isset($field_schema['type']) ? $field_schema['type'] : null;
94
95 if ($expected_type && !$this->validate_type($value, $expected_type)) {
96 throw new InvalidArgumentException("Invalid type for parameter '{$field}': expected {$expected_type}");
97 }
98
99 // Validate enum
100 if (isset($field_schema['enum']) && !in_array($value, $field_schema['enum'], true)) {
101 $allowed = implode(', ', $field_schema['enum']);
102 throw new InvalidArgumentException("Invalid value for parameter '{$field}': must be one of [{$allowed}]");
103 }
104
105 // Validate min/max for integers
106 if ($expected_type === 'integer') {
107 if (isset($field_schema['minimum']) && $value < $field_schema['minimum']) {
108 throw new InvalidArgumentException("Parameter '{$field}' must be >= {$field_schema['minimum']}");
109 }
110 if (isset($field_schema['maximum']) && $value > $field_schema['maximum']) {
111 throw new InvalidArgumentException("Parameter '{$field}' must be <= {$field_schema['maximum']}");
112 }
113 }
114 }
115 }
116
117 return true;
118 }
119
120 /**
121 * Validate value type
122 *
123 * @param mixed $value Value to validate
124 * @param string $type Expected type
125 * @return bool
126 */
127 private function validate_type($value, $type) {
128 switch ($type) {
129 case 'string':
130 return is_string($value);
131 case 'integer':
132 return is_int($value);
133 case 'number':
134 return is_numeric($value);
135 case 'boolean':
136 return is_bool($value);
137 case 'array':
138 return is_array($value);
139 case 'object':
140 return is_array($value) || is_object($value);
141 default:
142 return true;
143 }
144 }
145
146 /**
147 * Sanitize string parameter
148 *
149 * @param string $value Value to sanitize
150 * @return string
151 */
152 protected function sanitize_string($value) {
153 return sanitize_text_field($value);
154 }
155
156 /**
157 * Sanitize integer parameter
158 *
159 * @param mixed $value Value to sanitize
160 * @return int
161 */
162 protected function sanitize_integer($value) {
163 return intval($value);
164 }
165
166 /**
167 * Sanitize URL parameter
168 *
169 * @param string $value Value to sanitize
170 * @return string
171 */
172 protected function sanitize_url($value) {
173 return esc_url_raw($value);
174 }
175
176 /**
177 * Sanitize textarea/content parameter
178 *
179 * @param string $value Value to sanitize
180 * @return string
181 */
182 protected function sanitize_textarea($value) {
183 return sanitize_textarea_field($value);
184 }
185
186 /**
187 * Check if user has required capability
188 *
189 * @param string $capability Required capability (default: manage_options)
190 * @return bool
191 * @throws Exception If user lacks capability
192 */
193 protected function require_capability($capability = 'manage_options') {
194 // Skip capability check if authenticated via API key
195 if (defined('METASYNC_MCP_API_KEY_AUTH') && METASYNC_MCP_API_KEY_AUTH) {
196 return true;
197 }
198
199 // For WordPress user sessions, check actual capabilities
200 if (!current_user_can($capability)) {
201 throw new Exception('Insufficient permissions');
202 }
203 return true;
204 }
205
206 /**
207 * Check if user can edit a specific post
208 *
209 * Accounts for API key authentication where there is no WordPress user.
210 * Should be used instead of direct current_user_can('edit_post', $post_id) calls.
211 *
212 * @param int $post_id Post ID to check
213 * @return bool
214 * @throws Exception If user lacks permission
215 */
216 protected function check_post_permission($post_id) {
217 // Skip check if authenticated via API key (already verified at REST API level)
218 if (defined('METASYNC_MCP_API_KEY_AUTH') && METASYNC_MCP_API_KEY_AUTH) {
219 return true;
220 }
221
222 // For WordPress user sessions, check post-level permission
223 if (!current_user_can('edit_post', $post_id)) {
224 throw new Exception('You do not have permission to edit this post');
225 }
226 return true;
227 }
228
229 /**
230 * Verify a post exists by ID
231 *
232 * @param int $post_id Post ID to verify
233 * @return WP_Post The post object
234 * @throws Exception If post not found
235 */
236 protected function verify_post_exists($post_id) {
237 $post = get_post(absint($post_id));
238 if (!$post) {
239 throw new Exception(sprintf("Post not found: %d", absint($post_id)));
240 }
241 return $post;
242 }
243
244 /**
245 * Format success result
246 *
247 * @param mixed $data Result data
248 * @param string $message Optional message
249 * @return array
250 */
251 protected function success($data, $message = null) {
252 $result = [
253 'success' => true,
254 'data' => $data
255 ];
256
257 if ($message) {
258 $result['message'] = $message;
259 }
260
261 return $result;
262 }
263
264 /**
265 * Format error result
266 *
267 * @param string $message Error message
268 * @param string $code Optional error code
269 * @return array
270 * @throws Exception
271 */
272 protected function error($message, $code = 'error') {
273 throw new Exception($message);
274 }
275 }
276