PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.15
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.15
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 / mcp-stdio-bridge.php

mcp-stdio-bridge.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.15, at wp-mcp-server/mcp-stdio-bridge.php

299 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 #!/usr/bin/env php
2 <?php
3 /**
4 * MCP stdio Bridge for WordPress (PHP Implementation)
5 *
6 * This bridge allows MCP clients (Claude Desktop, Claude Code) to communicate
7 * with WordPress MCP tools via stdio (standard input/output).
8 *
9 * Usage:
10 * php mcp-stdio-bridge.php
11 *
12 * Configuration (Claude Desktop):
13 * {
14 * "mcpServers": {
15 * "wordpress-metasync": {
16 * "command": "php",
17 * "args": ["/path/to/mcp-stdio-bridge.php"]
18 * }
19 * }
20 * }
21 *
22 * @package Metasync
23 * @subpackage Metasync/wp-mcp-server
24 * @since 2.0.0
25 */
26
27 // Ensure we're running in CLI mode
28 if (php_sapi_name() !== 'cli') {
29 fwrite(STDERR, "Error: This script must be run from the command line\n");
30 exit(1);
31 }
32
33 // Suppress unnecessary WordPress output
34 // NOTE: Don't define WP_CLI constant - it causes Elementor to crash expecting WP_CLI class
35 define('DOING_AJAX', true);
36 define('WP_USE_THEMES', false);
37 define('DISABLE_WP_CRON', true);
38 define('METASYNC_MCP_BRIDGE', true); // Custom flag for our bridge
39
40 // Determine WordPress root path
41 // Try multiple possible locations for WordPress installation
42
43 $possible_paths = [
44 // Docker container path
45 '/var/www/html/wp-load.php',
46 // Standard installation (5 levels up from script)
47 dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/wp-load.php',
48 // Alternative: 4 levels up
49 dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php',
50 // Bedrock/custom structure
51 dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))) . '/wp-load.php',
52 ];
53
54 $wp_load_path = null;
55 foreach ($possible_paths as $path) {
56 if (file_exists($path)) {
57 $wp_load_path = $path;
58 break;
59 }
60 }
61
62 if (!$wp_load_path) {
63 fwrite(STDERR, "Error: Cannot find WordPress wp-load.php\n");
64 fwrite(STDERR, "Tried the following paths:\n");
65 foreach ($possible_paths as $path) {
66 fwrite(STDERR, " - $path\n");
67 }
68 fwrite(STDERR, "\nIf using Docker, run via mcp-stdio-bridge-docker.sh wrapper\n");
69 exit(1);
70 }
71
72 // Bootstrap WordPress
73 require_once $wp_load_path;
74
75 // Verify MCP server is available
76 global $metasync_mcp_server;
77 if (!isset($metasync_mcp_server) || !$metasync_mcp_server) {
78 fwrite(STDERR, "Error: Metasync MCP server not initialized\n");
79 exit(1);
80 }
81
82 // Disable output buffering for real-time communication
83 ob_implicit_flush(true);
84 while (ob_get_level()) {
85 ob_end_clean();
86 }
87
88 // Log startup to stderr (for debugging)
89 fwrite(STDERR, "MCP stdio bridge started (PHP)\n");
90 fwrite(STDERR, "WordPress version: " . get_bloginfo('version') . "\n");
91 fwrite(STDERR, "Metasync version: " . (defined('METASYNC_VERSION') ? METASYNC_VERSION : 'unknown') . "\n");
92 fwrite(STDERR, "Listening on stdin...\n");
93
94 // Main event loop - read from stdin, process, write to stdout
95 while (!feof(STDIN)) {
96 $line = fgets(STDIN);
97
98 // Skip empty lines
99 if ($line === false || trim($line) === '') {
100 continue;
101 }
102
103 try {
104 // Parse JSON-RPC request
105 $request = json_decode($line, true);
106 if (json_last_error() !== JSON_ERROR_NONE) {
107 throw new Exception('Invalid JSON: ' . json_last_error_msg());
108 }
109
110 // Validate request structure
111 if (!isset($request['jsonrpc']) || $request['jsonrpc'] !== '2.0') {
112 throw new Exception('Invalid JSON-RPC version');
113 }
114
115 if (!isset($request['method'])) {
116 throw new Exception('Missing method in request');
117 }
118
119 // Process MCP request
120 $response = process_mcp_request($request);
121
122 // Write JSON-RPC response to stdout
123 $response_json = json_encode($response);
124 fwrite(STDOUT, $response_json . "\n");
125 fflush(STDOUT);
126
127 } catch (Exception $e) {
128 // Write error to stderr (for debugging)
129 fwrite(STDERR, "Error processing request: " . $e->getMessage() . "\n");
130 fwrite(STDERR, "Request: " . $line . "\n");
131
132 // Send JSON-RPC error response
133 $error_response = [
134 'jsonrpc' => '2.0',
135 'id' => isset($request['id']) ? $request['id'] : null,
136 'error' => [
137 'code' => -32603,
138 'message' => $e->getMessage(),
139 'data' => [
140 'line' => $line,
141 'trace' => $e->getTraceAsString()
142 ]
143 ]
144 ];
145
146 fwrite(STDOUT, json_encode($error_response) . "\n");
147 fflush(STDOUT);
148 }
149 }
150
151 fwrite(STDERR, "MCP stdio bridge terminated\n");
152
153 /**
154 * Process MCP JSON-RPC request
155 *
156 * @param array $request JSON-RPC request
157 * @return array JSON-RPC response
158 */
159 function process_mcp_request($request) {
160 global $metasync_mcp_server;
161
162 $method = $request['method'] ?? '';
163 $params = $request['params'] ?? [];
164 $id = $request['id'] ?? null;
165
166 switch ($method) {
167 case 'initialize':
168 return handle_initialize($id, $params);
169
170 case 'notifications/initialized':
171 // Client confirms initialization - no response needed
172 return null;
173
174 case 'tools/list':
175 return handle_tools_list($id);
176
177 case 'tools/call':
178 return handle_tools_call($id, $params);
179
180 case 'ping':
181 return [
182 'jsonrpc' => '2.0',
183 'id' => $id,
184 'result' => [
185 'status' => 'ok',
186 'timestamp' => time()
187 ]
188 ];
189
190 default:
191 throw new Exception("Unknown method: $method");
192 }
193 }
194
195 /**
196 * Handle initialize request
197 */
198 function handle_initialize($id, $params) {
199 $client_info = $params['clientInfo'] ?? [];
200
201 fwrite(STDERR, "Client connected: " . json_encode($client_info) . "\n");
202
203 return [
204 'jsonrpc' => '2.0',
205 'id' => $id,
206 'result' => [
207 'protocolVersion' => '2024-11-05',
208 'capabilities' => [
209 'tools' => (object)[]
210 ],
211 'serverInfo' => [
212 'name' => 'wordpress-metasync',
213 'version' => defined('METASYNC_VERSION') ? METASYNC_VERSION : '2.0.0'
214 ]
215 ]
216 ];
217 }
218
219 /**
220 * Handle tools/list request
221 */
222 function handle_tools_list($id) {
223 global $metasync_mcp_server;
224
225 $tools = [];
226 $tool_objects = $metasync_mcp_server->get_tools();
227
228 fwrite(STDERR, "Listing " . count($tool_objects) . " tools\n");
229
230 foreach ($tool_objects as $tool) {
231 $tools[] = [
232 'name' => $tool->get_name(),
233 'description' => $tool->get_description(),
234 'inputSchema' => $tool->get_input_schema()
235 ];
236 }
237
238 return [
239 'jsonrpc' => '2.0',
240 'id' => $id,
241 'result' => [
242 'tools' => $tools
243 ]
244 ];
245 }
246
247 /**
248 * Handle tools/call request
249 */
250 function handle_tools_call($id, $params) {
251 global $metasync_mcp_server;
252
253 $tool_name = $params['name'] ?? '';
254 $arguments = $params['arguments'] ?? [];
255
256 if (empty($tool_name)) {
257 throw new Exception('Tool name is required');
258 }
259
260 fwrite(STDERR, "Calling tool: $tool_name\n");
261
262 // Find tool
263 $tool = $metasync_mcp_server->get_tool($tool_name);
264 if (!$tool) {
265 throw new Exception("Tool not found: $tool_name");
266 }
267
268 // Execute tool
269 $start_time = microtime(true);
270 $result = $tool->execute($arguments);
271 $execution_time = round((microtime(true) - $start_time) * 1000, 2);
272
273 fwrite(STDERR, "Tool executed in {$execution_time}ms\n");
274
275 // Format result as MCP response
276 $content = [];
277
278 if (is_array($result) || is_object($result)) {
279 $content[] = [
280 'type' => 'text',
281 'text' => json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
282 ];
283 } else {
284 $content[] = [
285 'type' => 'text',
286 'text' => (string)$result
287 ];
288 }
289
290 return [
291 'jsonrpc' => '2.0',
292 'id' => $id,
293 'result' => [
294 'content' => $content,
295 'isError' => false
296 ]
297 ];
298 }
299