PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.0.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.0.0
2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 1.45 All 41 releases
fluent-boards / app / Http / Controllers / MCPSettingsController.php

MCPSettingsController.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.0.0, at app/Http/Controllers/MCPSettingsController.php

352 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Http\Controllers;
4
5 use FluentBoards\App\Modules\MCP\MCPInit;
6 use FluentBoards\Framework\Http\Request\Request;
7
8 /**
9 * Settings endpoints for connecting Fluent Boards MCP tools to AI agents.
10 */
11 class MCPSettingsController extends Controller
12 {
13 const ADAPTER_PLUGIN_FILE = 'mcp-adapter/mcp-adapter.php';
14 const TOOLKIT_PLUGIN_FILE = 'fluent-toolkit/fluent-toolkit.php';
15
16 public function status()
17 {
18 if (!function_exists('is_plugin_active')) {
19 require_once ABSPATH . 'wp-admin/includes/plugin.php';
20 }
21
22 $adapterInstalled = $this->isAdapterPresent();
23 $toolkitInstalled = $this->isToolkitPresent();
24 $adapterRuntimeAvailable = $this->isAdapterRuntimeAvailable();
25 $standaloneActive = is_plugin_active(self::ADAPTER_PLUGIN_FILE) && $adapterRuntimeAvailable;
26 $toolkitActive = $this->isToolkitLoaded();
27 $toolkitAdapterActive = $toolkitActive && $this->isToolkitAdapterAvailable();
28 $adapterActive = $standaloneActive || $toolkitAdapterActive;
29 $adapterProvider = $standaloneActive ? 'plugin' : ($toolkitAdapterActive ? 'toolkit' : '');
30 $abilitiesAvailable = function_exists('wp_register_ability');
31 $canAutoInstall = $this->canAutoInstallFluentKit();
32 $currentUser = wp_get_current_user();
33
34 return $this->sendSuccess([
35 'adapter_installed' => $adapterInstalled || $toolkitInstalled,
36 'adapter_active' => $adapterActive,
37 'adapter_provider' => $adapterProvider,
38 'standalone_adapter_installed' => $adapterInstalled,
39 'toolkit_installed' => $toolkitInstalled,
40 'toolkit_active' => $toolkitActive,
41 'toolkit_adapter_available' => $toolkitAdapterActive,
42 'adapter_runtime_available' => $adapterRuntimeAvailable,
43 'adapter_version' => $this->detectAdapterVersion(),
44 'toolkit_version' => $this->detectToolkitVersion(),
45 'abilities_api_loaded' => $abilitiesAvailable,
46 'endpoint_url' => MCPInit::getEndpointUrl(),
47 'tools_count' => $abilitiesAvailable ? MCPInit::getToolsCount() : 0,
48 'mcp_enabled' => fluent_boards_get_option('mcp_enabled', 'yes') === 'yes',
49 'app_passwords_url' => admin_url('profile.php#application-passwords-section'),
50 'plugins_url' => admin_url('plugins.php'),
51 'can_auto_install_adapter' => $canAutoInstall,
52 'toolkit_download_url' => 'https://github.com/WPManageNinja/fluent-toolkit',
53 'current_user_login' => $currentUser ? $currentUser->user_login : '',
54 'is_local_dev' => self::detectLocalDevEnvironment(),
55 ]);
56 }
57
58 public function toggle(Request $request)
59 {
60 $value = $request->get('mcp_enabled');
61 $enabled = is_string($value) ? ($value === 'yes' || $value === 'true' || $value === '1') : (bool) $value;
62
63 fluent_boards_update_option('mcp_enabled', $enabled ? 'yes' : 'no');
64
65 return $this->sendSuccess([
66 'ok' => true,
67 'mcp_enabled' => $enabled,
68 'message' => $enabled
69 ? __('MCP tools enabled. New requests will see the Fluent Boards abilities.', 'fluent-boards')
70 : __('MCP tools disabled. The adapter will no longer report Fluent Boards abilities.', 'fluent-boards'),
71 ]);
72 }
73
74 public function installAdapter()
75 {
76 if (!current_user_can('install_plugins')) {
77 return $this->sendError([
78 'message' => __('Sorry! you do not have permission to install plugins', 'fluent-boards'),
79 ]);
80 }
81
82 $canAutoInstall = $this->canAutoInstallFluentKit();
83 if (!$canAutoInstall) {
84 return $this->sendError([
85 'message' => __('Please install FluentHub from GitHub, then reload this page to connect Fluent Boards with AI agents.', 'fluent-boards'),
86 'toolkit_download_url' => 'https://github.com/WPManageNinja/fluent-toolkit',
87 ]);
88 }
89
90 $this->autoInstallFluentKit();
91
92 wp_clean_plugins_cache();
93
94 if (!function_exists('is_plugin_active')) {
95 require_once ABSPATH . 'wp-admin/includes/plugin.php';
96 }
97
98 $toolkitInstalled = $this->isToolkitPresent();
99 $toolkitActive = $this->isToolkitLoaded();
100 $adapterRuntimeAvailable = $this->isAdapterRuntimeAvailable();
101 $toolkitAdapterAvailable = $toolkitActive && $this->isToolkitAdapterAvailable();
102 $isInstalled = $this->isAdapterPresent() || $toolkitInstalled;
103 $isActive = (is_plugin_active(self::ADAPTER_PLUGIN_FILE) && $adapterRuntimeAvailable) || $toolkitAdapterAvailable;
104
105 if ($isInstalled && $isActive) {
106 $message = __('FluentHub installed and activated. Reload the page to register Fluent Boards MCP tools.', 'fluent-boards');
107 } elseif ($toolkitInstalled && $toolkitActive) {
108 $message = __('FluentHub is installed and active, but this version does not include the bundled MCP adapter yet. Please update FluentHub when the MCP-ready build is available, then reload this page.', 'fluent-boards');
109 } elseif ($toolkitInstalled) {
110 $message = __('FluentHub is installed but could not be activated automatically. Please activate FluentHub from the Plugins page, then reload this page.', 'fluent-boards');
111 } else {
112 $message = __('Could not install FluentHub automatically. Please install FluentHub manually, then reload this page.', 'fluent-boards');
113 }
114
115 return $this->sendSuccess([
116 'is_installed' => $isInstalled,
117 'adapter_active' => $isActive,
118 'toolkit_active' => $toolkitActive,
119 'toolkit_adapter_available' => $toolkitAdapterAvailable,
120 'message' => $message,
121 ]);
122 }
123
124 public function getConfigSnippet(Request $request)
125 {
126 $client = sanitize_key((string) $request->get('client', 'claude-code'));
127 $endpoint = MCPInit::getEndpointUrl();
128 $isLocalDev = $this->resolveLocalDevOverride($request);
129
130 $basicPlaceholder = '<base64(your-username:application-password)>';
131 $usernamePlaceholder = '<your-username>';
132 $passwordPlaceholder = '<your-application-password>';
133 $appPasswordsUrl = admin_url('profile.php#application-passwords-section');
134
135 switch ($client) {
136 case 'codex':
137 $snippet = sprintf(
138 "Settings > Connect to a custom MCP\n\nName: fluent-boards\nTransport: Streamable HTTP\n\nURL: %s\n\nHeader:\n Key: Authorization\n Value: Basic %s\n\nClick Save.",
139 $endpoint,
140 $basicPlaceholder
141 );
142 $instructions = sprintf(
143 __('Open OpenAI Codex settings, connect to a custom MCP, choose Streamable HTTP, then use a WordPress Application Password from %s.', 'fluent-boards'),
144 $appPasswordsUrl
145 );
146 break;
147
148 case 'cursor':
149 $snippet = wp_json_encode([
150 'mcpServers' => [
151 'fluent-boards' => [
152 'url' => $endpoint,
153 'type' => 'http',
154 'headers' => [
155 'Authorization' => 'Basic ' . $basicPlaceholder,
156 ],
157 ],
158 ],
159 ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
160 $instructions = __('Paste this into Cursor Settings > MCP, then restart Cursor.', 'fluent-boards');
161 break;
162
163 case 'generic':
164 $snippet = sprintf(
165 "URL: %s\nAuth: Authorization: Basic %s\n\n# Quick test\ncurl -s -u '%s:%s' \\\n -X POST %s \\\n -H 'Content-Type: application/json' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}'",
166 $endpoint,
167 $basicPlaceholder,
168 $usernamePlaceholder,
169 $passwordPlaceholder,
170 $endpoint
171 );
172 $instructions = __('Use the URL and Basic Auth header with any HTTP MCP client. The endpoint speaks MCP over JSON-RPC.', 'fluent-boards');
173 break;
174
175 case 'claude-desktop':
176 $env = [
177 'WP_API_URL' => $endpoint,
178 'WP_API_USERNAME' => $usernamePlaceholder,
179 'WP_API_PASSWORD' => $passwordPlaceholder,
180 'OAUTH_ENABLED' => 'false',
181 'NODE_TLS_REJECT_UNAUTHORIZED' => '0',
182 ];
183
184 $snippet = wp_json_encode([
185 'mcpServers' => [
186 'fluent-boards' => [
187 'command' => 'npx',
188 'args' => ['-y', '@automattic/mcp-wordpress-remote@latest'],
189 'env' => $env,
190 ],
191 ],
192 ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
193
194 $instructions = __('Paste into the Claude Desktop config file, then restart Claude Desktop.', 'fluent-boards');
195 break;
196
197 case 'claude-code':
198 default:
199 $snippet = sprintf(
200 "claude mcp add \\\n --transport http \\\n fluent-boards %s \\\n --header \"Authorization: Basic %s\"",
201 $endpoint,
202 $basicPlaceholder
203 );
204 $instructions = __('Fill in your username and application password above, then paste the command into your terminal.', 'fluent-boards');
205 $client = 'claude-code';
206 break;
207 }
208
209 return $this->sendSuccess([
210 'client' => $client,
211 'snippet' => $snippet,
212 'instructions' => $instructions,
213 'endpoint' => $endpoint,
214 'app_passwords_url' => $appPasswordsUrl,
215 'is_local_dev' => $isLocalDev,
216 ]);
217 }
218
219 private function resolveLocalDevOverride(Request $request)
220 {
221 $forceLocalDev = $request->get('local_dev');
222 if ($forceLocalDev === 'yes' || $forceLocalDev === '1' || $forceLocalDev === 'true') {
223 return true;
224 }
225
226 if ($forceLocalDev === 'no' || $forceLocalDev === '0' || $forceLocalDev === 'false') {
227 return false;
228 }
229
230 return self::detectLocalDevEnvironment();
231 }
232
233 private static function detectLocalDevEnvironment()
234 {
235 $host = wp_parse_url(home_url(), PHP_URL_HOST);
236 $host = strtolower((string) $host);
237
238 $isDev = false;
239 foreach (['.test', '.lab', '.local', '.localhost', '.docker', '.dev'] as $tld) {
240 if (substr($host, -strlen($tld)) === $tld) {
241 $isDev = true;
242 break;
243 }
244 }
245
246 if (!$isDev && ($host === 'localhost' || $host === '127.0.0.1' || $host === '::1')) {
247 $isDev = true;
248 }
249
250 if (!$isDev && filter_var($host, FILTER_VALIDATE_IP)) {
251 $isDev = !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
252 }
253
254 return (bool) apply_filters('fluent_boards/mcp_is_local_dev', $isDev, $host);
255 }
256
257 private function isAdapterPresent()
258 {
259 return $this->isPluginPresent(self::ADAPTER_PLUGIN_FILE);
260 }
261
262 private function isToolkitPresent()
263 {
264 return $this->isToolkitLoaded() || $this->isPluginPresent(self::TOOLKIT_PLUGIN_FILE);
265 }
266
267 private function detectAdapterVersion()
268 {
269 return $this->detectPluginVersion(self::ADAPTER_PLUGIN_FILE);
270 }
271
272 private function detectToolkitVersion()
273 {
274 if ($this->isToolkitLoaded()) {
275 return (string) FLUENT_TOOLKIT_VERSION;
276 }
277
278 return $this->detectPluginVersion(self::TOOLKIT_PLUGIN_FILE);
279 }
280
281 private function isToolkitLoaded()
282 {
283 return defined('FLUENT_TOOLKIT_VERSION');
284 }
285
286 private function canAutoInstallFluentKit()
287 {
288 $canAutoInstall = (bool) apply_filters('fluent_kit/can_auto_install', false);
289
290 if (!$canAutoInstall) {
291 $canAutoInstall = (bool) apply_filters('fluent_toolkit/can_auto_install', false);
292 }
293
294 return $canAutoInstall;
295 }
296
297 private function autoInstallFluentKit()
298 {
299 if ((bool) apply_filters('fluent_kit/can_auto_install', false)) {
300 do_action('fluent_kit/do_auto_install');
301 return;
302 }
303
304 do_action('fluent_toolkit/do_auto_install');
305 }
306
307 private function isPluginPresent($pluginFile)
308 {
309 if (!function_exists('get_plugins')) {
310 require_once ABSPATH . 'wp-admin/includes/plugin.php';
311 }
312
313 $plugins = get_plugins();
314 return isset($plugins[$pluginFile]);
315 }
316
317 private function detectPluginVersion($pluginFile)
318 {
319 if (!function_exists('get_plugins')) {
320 require_once ABSPATH . 'wp-admin/includes/plugin.php';
321 }
322
323 $plugins = get_plugins();
324 if (!isset($plugins[$pluginFile])) {
325 return null;
326 }
327
328 return $plugins[$pluginFile]['Version'] ?? null;
329 }
330
331 private function isToolkitAdapterAvailable()
332 {
333 if (!$this->isToolkitLoaded()) {
334 return false;
335 }
336
337 if (class_exists('\FluentToolkit\Mcp\AdapterBootstrap') && method_exists('\FluentToolkit\Mcp\AdapterBootstrap', 'available')) {
338 return (bool) \FluentToolkit\Mcp\AdapterBootstrap::available();
339 }
340
341 return $this->isAdapterRuntimeAvailable();
342 }
343
344 private function isAdapterRuntimeAvailable()
345 {
346 return defined('WP_MCP_VERSION')
347 && class_exists('\WP\MCP\Core\McpAdapter')
348 && function_exists('wp_register_ability');
349 }
350
351 }
352