| @@ -6,13 +6,10 @@ | ||
| 6 | 6 | use FluentSupport\App\Modules\MCP\Support\PermissionGate; |
| 7 | 7 | use FluentSupport\Framework\Http\Request\Request; |
| 8 | 8 | |
| 9 | 9 | /** |
| 10 | - * Backend for the Settings → MCP for AI Agents page. | |
| 11 | - * | |
| 12 | - * Covers: enable/disable toggle, one-click FluentHub install, and per-client | |
| 13 | - * connection snippets. SLA and AI guidelines are intentionally not exposed here — | |
| 14 | - * they are tool-layer concerns read by PermissionGate and live in _mcp_settings. | |
| 10 | + * Settings → MCP for AI Agents: enable toggle, FluentHub install, client snippets. | |
| 11 | + * SLA and AI guidelines live in _mcp_settings and are read by PermissionGate, not here. | |
| 15 | 12 | */ |
| 16 | 13 | class McpSettingsController extends Controller |
| 17 | 14 | { |
| 18 | 15 | const TOOLKIT_PLUGIN_FILE = 'fluent-toolkit/fluent-toolkit.php'; |
| @@ -18,10 +15,9 @@ | ||
| 18 | 15 | const TOOLKIT_PLUGIN_FILE = 'fluent-toolkit/fluent-toolkit.php'; |
| 19 | 16 | const TOOLKIT_DOWNLOAD_URL = 'https://github.com/WPManageNinja/fluent-toolkit'; |
| 20 | 17 | |
| 21 | 18 | /** |
| 22 | - * GET fluent-support/v2/settings/mcp | |
| 23 | - * Returns all data the settings page needs in one request. | |
| 19 | + * GET settings/mcp. Everything the settings page needs. | |
| 24 | 20 | */ |
| 25 | 21 | public function getStatus(Request $request) |
| 26 | 22 | { |
| 27 | 23 | $user = wp_get_current_user(); |
| @@ -43,10 +39,9 @@ | ||
| 43 | 39 | ]; |
| 44 | 40 | } |
| 45 | 41 | |
| 46 | 42 | /** |
| 47 | - * POST fluent-support/v2/settings/mcp/toggle | |
| 48 | - * Body: { mcp_enabled: true|'yes'|1|'on' } | |
| 43 | + * POST settings/mcp/toggle with mcp_enabled: true|'yes'|1|'on'. | |
| 49 | 44 | */ |
| 50 | 45 | public function toggle(Request $request) |
| 51 | 46 | { |
| 52 | 47 | if (!current_user_can('manage_options')) { |
| @@ -62,9 +57,9 @@ | ||
| 62 | 57 | : (bool) $value; |
| 63 | 58 | |
| 64 | 59 | PermissionGate::setEnabled($enabled); |
| 65 | 60 | |
| 66 | - // Re-read the persisted state so the UI can never show "enabled" for a write that didn't land. | |
| 61 | + // Re-read so the UI never shows a state that failed to save. | |
| 67 | 62 | $stored = PermissionGate::isEnabled(); |
| 68 | 63 | |
| 69 | 64 | return [ |
| 70 | 65 | 'mcp_enabled' => $stored, |
| @@ -74,11 +69,10 @@ | ||
| 74 | 69 | ]; |
| 75 | 70 | } |
| 76 | 71 | |
| 77 | 72 | /** |
| 78 | - * POST fluent-support/v2/settings/mcp/install-adapter | |
| 79 | - * One-click FluentHub install. Requires a Fluent Pro plugin to hook | |
| 80 | - * fluent_toolkit/can_auto_install + fluent_toolkit/do_auto_install. | |
| 73 | + * POST settings/mcp/install-adapter. Needs a Fluent Pro plugin hooked into | |
| 74 | + * fluent_toolkit/can_auto_install and fluent_toolkit/do_auto_install. | |
| 81 | 75 | */ |
| 82 | 76 | public function installAdapter(Request $request) |
| 83 | 77 | { |
| 84 | 78 | if (!current_user_can('install_plugins')) { |
| @@ -94,11 +88,29 @@ | ||
| 94 | 88 | 'toolkit_download_url' => self::TOOLKIT_DOWNLOAD_URL, |
| 95 | 89 | ]); |
| 96 | 90 | } |
| 97 | 91 | |
| 92 | + // The installer reports why it failed through this action. | |
| 93 | + $failure = null; | |
| 94 | + $onFailure = function ($error) use (&$failure) { | |
| 95 | + $failure = $error; | |
| 96 | + }; | |
| 97 | + add_action('fluent_toolkit/auto_install_failed', $onFailure); | |
| 98 | + | |
| 98 | 99 | do_action('fluent_toolkit/do_auto_install'); |
| 100 | + | |
| 101 | + remove_action('fluent_toolkit/auto_install_failed', $onFailure); | |
| 99 | 102 | wp_clean_plugins_cache(); |
| 100 | 103 | |
| 104 | + if (!$this->isToolkitPresent()) { | |
| 105 | + $reason = is_wp_error($failure) ? $failure->get_error_message() : ''; | |
| 106 | + | |
| 107 | + return $this->sendError([ | |
| 108 | + 'message' => trim(__('FluentHub could not be installed automatically.', 'fluent-support') . ' ' . $reason . ' ' . __('Install it manually, then reload this page.', 'fluent-support')), | |
| 109 | + 'toolkit_download_url' => self::TOOLKIT_DOWNLOAD_URL, | |
| 110 | + ]); | |
| 111 | + } | |
| 112 | + | |
| 101 | 113 | $adapterAvailable = MCPInit::adapterAvailable(); |
| 102 | 114 | |
| 103 | 115 | return $this->sendSuccess([ |
| 104 | 116 | 'adapter_available' => $adapterAvailable, |
| @@ -104,15 +116,16 @@ | ||
| 104 | 116 | 'adapter_available' => $adapterAvailable, |
| 105 | 117 | 'toolkit_installed' => $this->isToolkitPresent(), |
| 106 | 118 | 'message' => $adapterAvailable |
| 107 | 119 | ? __('FluentHub installed and activated. The MCP endpoint is ready.', 'fluent-support') |
| 108 | - : __('FluentHub was installed. Please reload this page to finish connecting the MCP endpoint.', 'fluent-support'), | |
| 120 | + : (is_wp_error($failure) | |
| 121 | + ? $failure->get_error_message() | |
| 122 | + : __('FluentHub was installed. Please reload this page to finish connecting the MCP endpoint.', 'fluent-support')), | |
| 109 | 123 | ]); |
| 110 | 124 | } |
| 111 | 125 | |
| 112 | 126 | /** |
| 113 | - * GET fluent-support/v2/settings/mcp/config-snippets | |
| 114 | - * Query param: local_dev=yes|no (falls back to auto-detected isLocalDev when omitted) | |
| 127 | + * GET settings/mcp/config-snippets. local_dev=yes|no overrides isLocalDev(). | |
| 115 | 128 | */ |
| 116 | 129 | public function getConfigSnippets(Request $request) |
| 117 | 130 | { |
| 118 | 131 | $endpoint = MCPInit::getEndpointUrl(); |
| @@ -220,25 +233,18 @@ | ||
| 220 | 233 | ]; |
| 221 | 234 | } |
| 222 | 235 | } |
| 223 | 236 | |
| 224 | - // ------------------------------------------------------------------------- | |
| 225 | - // Toolkit detection helpers — mirrors FluentCRM MCPSettingsController | |
| 226 | - // ------------------------------------------------------------------------- | |
| 227 | - | |
| 228 | - /** True when FluentHub is loaded in the current request (constant defined). */ | |
| 229 | 237 | private function isToolkitLoaded() |
| 230 | 238 | { |
| 231 | 239 | return defined('FLUENT_TOOLKIT_VERSION'); |
| 232 | 240 | } |
| 233 | 241 | |
| 234 | - /** True when FluentHub is loaded OR present in the plugin directory. */ | |
| 235 | 242 | private function isToolkitPresent() |
| 236 | 243 | { |
| 237 | 244 | return $this->isToolkitLoaded() || $this->isPluginPresent(self::TOOLKIT_PLUGIN_FILE); |
| 238 | 245 | } |
| 239 | 246 | |
| 240 | - /** Version string from the loaded constant, or from the plugin header, or null. */ | |
| 241 | 247 | private function detectToolkitVersion() |
| 242 | 248 | { |
| 243 | 249 | if ($this->isToolkitLoaded()) { |
| 244 | 250 | return (string) FLUENT_TOOLKIT_VERSION; |
| @@ -266,24 +272,11 @@ | ||
| 266 | 272 | |
| 267 | 273 | return $plugins[$pluginFile]['Version'] ?? null; |
| 268 | 274 | } |
| 269 | 275 | |
| 270 | - // ------------------------------------------------------------------------- | |
| 271 | - // Local-dev detection | |
| 272 | - // ------------------------------------------------------------------------- | |
| 273 | - | |
| 274 | 276 | /** |
| 275 | - * Heuristic: are we on a local/dev install? | |
| 276 | - * | |
| 277 | - * Checks (in order): | |
| 278 | - * 1. Hostname ends in a dev TLD (.test, .local, .localhost, .lab, .docker) | |
| 279 | - * 2. Hostname is a loopback literal (localhost, 127.0.0.1, ::1) | |
| 280 | - * 3. Hostname is a private RFC 1918 IP address | |
| 281 | - * | |
| 282 | - * Note: .dev is intentionally excluded — it is a real HSTS-preloaded | |
| 283 | - * public gTLD (owned by Google) and we must not disable TLS on those sites. | |
| 284 | - * | |
| 285 | - * Filterable via fluent_support/mcp_is_local_dev for edge cases. | |
| 277 | + * Dev TLD, loopback host, or private/reserved IP. The snippet disables TLS | |
| 278 | + * checks on local installs, so .dev is excluded: it's a real public gTLD. | |
| 286 | 279 | */ |
| 287 | 280 | private function isLocalDev() |
| 288 | 281 | { |
| 289 | 282 | $host = strtolower((string) wp_parse_url(home_url(), PHP_URL_HOST)); |