PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 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 All 141 releases
metasync / wp-mcp-server / class-mcp-json-rpc-handler.php

class-mcp-json-rpc-handler.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.1, at wp-mcp-server/class-mcp-json-rpc-handler.php

225 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MCP JSON-RPC 2.0 Handler
4 *
5 * Handles parsing, validation, and routing of JSON-RPC 2.0 requests
6 * according to the Model Context Protocol specification.
7 *
8 * @package MetaSync
9 * @subpackage MCP_Server
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 class MCP_JSON_RPC_Handler {
17
18 /**
19 * JSON-RPC version
20 */
21 const JSONRPC_VERSION = '2.0';
22
23 /**
24 * Error codes (JSON-RPC 2.0 standard + custom)
25 */
26 const ERROR_PARSE_ERROR = -32700;
27 const ERROR_INVALID_REQUEST = -32600;
28 const ERROR_METHOD_NOT_FOUND = -32601;
29 const ERROR_INVALID_PARAMS = -32602;
30 const ERROR_INTERNAL_ERROR = -32603;
31 const ERROR_SERVER_ERROR = -32000;
32 const ERROR_RATE_LIMITED = -32029;
33
34 /**
35 * Method handlers
36 *
37 * @var array
38 */
39 private $handlers = [];
40
41 /**
42 * Constructor
43 */
44 public function __construct() {
45 // Register default handlers
46 $this->register_handler('initialize', [$this, 'handle_initialize']);
47 $this->register_handler('ping', [$this, 'handle_ping']);
48 }
49
50 /**
51 * Register a method handler
52 *
53 * @param string $method Method name
54 * @param callable $callback Handler callback
55 */
56 public function register_handler($method, $callback) {
57 if (!is_callable($callback)) {
58 throw new InvalidArgumentException("Handler for method '{$method}' must be callable");
59 }
60 $this->handlers[$method] = $callback;
61 }
62
63 /**
64 * Handle incoming JSON-RPC request
65 *
66 * @param string $request_body Raw request body
67 * @return array|null Response array, or null for a notification
68 */
69 public function handle_request($request_body) {
70 // Parse JSON
71 $request = json_decode($request_body, true);
72
73 if (json_last_error() !== JSON_ERROR_NONE) {
74 return $this->error_response(null, self::ERROR_PARSE_ERROR, 'Parse error');
75 }
76
77 // Validate request structure
78 if (!$this->validate_request($request)) {
79 return $this->error_response(
80 isset($request['id']) ? $request['id'] : null,
81 self::ERROR_INVALID_REQUEST,
82 'Invalid Request'
83 );
84 }
85
86 $method = $request['method'];
87 $params = isset($request['params']) ? $request['params'] : [];
88 $id = isset($request['id']) ? $request['id'] : null;
89
90 // Check if handler exists
91 if (!isset($this->handlers[$method])) {
92 if (!array_key_exists('id', $request)) {
93 return null;
94 }
95
96 return $this->error_response($id, self::ERROR_METHOD_NOT_FOUND, "Method not found: {$method}");
97 }
98
99 try {
100 // Call handler
101 $result = call_user_func($this->handlers[$method], $params);
102 if (!array_key_exists('id', $request)) {
103 return null;
104 }
105
106 return $this->success_response($id, $result);
107 } catch (InvalidArgumentException $e) {
108 if (!array_key_exists('id', $request)) {
109 return null;
110 }
111
112 return $this->error_response($id, self::ERROR_INVALID_PARAMS, $e->getMessage());
113 } catch (Exception $e) {
114 // Log error
115 error_log('MCP JSON-RPC Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
116 if (!array_key_exists('id', $request)) {
117 return null;
118 }
119
120 return $this->error_response($id, self::ERROR_INTERNAL_ERROR, $e->getMessage());
121 }
122 }
123
124 /**
125 * Validate JSON-RPC request structure
126 *
127 * @param mixed $request Request data
128 * @return bool
129 */
130 private function validate_request($request) {
131 if (!is_array($request)) {
132 return false;
133 }
134
135 // Must have jsonrpc, method
136 if (!isset($request['jsonrpc']) || $request['jsonrpc'] !== self::JSONRPC_VERSION) {
137 return false;
138 }
139
140 if (!isset($request['method']) || !is_string($request['method'])) {
141 return false;
142 }
143
144 // Params are optional but must be array or object if present
145 if (isset($request['params']) && !is_array($request['params'])) {
146 return false;
147 }
148
149 return true;
150 }
151
152 /**
153 * Create success response
154 *
155 * @param mixed $id Request ID
156 * @param mixed $result Result data
157 * @return array
158 */
159 public function success_response($id, $result) {
160 return [
161 'jsonrpc' => self::JSONRPC_VERSION,
162 'id' => $id,
163 'result' => $result
164 ];
165 }
166
167 /**
168 * Create error response
169 *
170 * @param mixed $id Request ID
171 * @param int $code Error code
172 * @param string $message Error message
173 * @param mixed $data Optional error data
174 * @return array
175 */
176 public function error_response($id, $code, $message, $data = null) {
177 $error = [
178 'code' => $code,
179 'message' => $message
180 ];
181
182 if ($data !== null) {
183 $error['data'] = $data;
184 }
185
186 return [
187 'jsonrpc' => self::JSONRPC_VERSION,
188 'id' => $id,
189 'error' => $error
190 ];
191 }
192
193 /**
194 * Handle initialize method
195 *
196 * @param array $params Request parameters
197 * @return array
198 */
199 private function handle_initialize($params) {
200 return [
201 'protocolVersion' => '2024-11-05',
202 'capabilities' => [
203 'tools' => [
204 'list' => true,
205 'call' => true
206 ]
207 ],
208 'serverInfo' => [
209 'name' => 'metasync-wordpress-mcp',
210 'version' => METASYNC_VERSION
211 ]
212 ];
213 }
214
215 /**
216 * Handle ping method
217 *
218 * @param array $params Request parameters
219 * @return array
220 */
221 private function handle_ping($params) {
222 return ['status' => 'ok'];
223 }
224 }
225