| @@ -9,24 +9,19 @@ | ||
| 9 | 9 | use FluentBooking\Framework\Http\Request\Request; |
| 10 | 10 | use FluentBooking\Framework\Support\Arr; |
| 11 | 11 | |
| 12 | 12 | /** |
| 13 | - * Settings surface for the MCP server (FluentBooking → Settings → MCP for AI | |
| 14 | - * Agents). | |
| 13 | + * Settings → MCP for AI Agents. | |
| 15 | 14 | * |
| 16 | - * Bound to SettingsPolicy, whose verifyRequest() requires `manage_all_data`. | |
| 17 | - * Writes additionally require `manage_options`: enabling MCP exposes booking | |
| 18 | - * and attendee data to any client holding an application password, which is a | |
| 19 | - * site-administration decision rather than a scheduling one. The read endpoint | |
| 20 | - * reports `can_manage` so the UI can disable the controls instead of letting an | |
| 21 | - * operator submit a change that would be refused. | |
| 15 | + * SettingsPolicy requires `manage_all_data`. Writes also need `manage_options`, | |
| 16 | + * since enabling MCP exposes booking data to any application-password client. | |
| 17 | + * The read endpoint reports `can_manage` so the UI can disable the controls. | |
| 22 | 18 | */ |
| 23 | 19 | class McpController extends Controller |
| 24 | 20 | { |
| 25 | 21 | /** |
| 26 | - * "FluentHub" in the UI is the fluent-toolkit plugin, which can bundle the | |
| 27 | - * MCP adapter runtime. The standalone WordPress MCP Adapter plugin is the | |
| 28 | - * other accepted provider. | |
| 22 | + * "FluentHub" in the UI. It can bundle the MCP adapter; the standalone | |
| 23 | + * MCP Adapter plugin is the other accepted provider. | |
| 29 | 24 | */ |
| 30 | 25 | const TOOLKIT_PLUGIN_FILE = 'fluent-toolkit/fluent-toolkit.php'; |
| 31 | 26 | |
| 32 | 27 | const ADAPTER_PLUGIN_FILE = 'mcp-adapter/mcp-adapter.php'; |
| @@ -59,15 +54,11 @@ | ||
| 59 | 54 | if (!is_array($data)) { |
| 60 | 55 | $data = []; |
| 61 | 56 | } |
| 62 | 57 | |
| 63 | - // Absent keys keep their stored value rather than defaulting to off. A | |
| 64 | - // partial POST — a caller sending only `toolsets`, say — would otherwise | |
| 65 | - // read as "enabled: false" and silently switch the server off. | |
| 66 | - // | |
| 67 | - // Arr::isTrue(), never a (bool) cast: jQuery form-encodes the body, so | |
| 68 | - // an off toggle arrives as the STRING "false" and `(bool) "false"` is | |
| 69 | - // true — which makes the master switch one-way. | |
| 58 | + // Absent keys keep their stored value, so a partial POST can't switch | |
| 59 | + // the server off. Arr::isTrue(), not a (bool) cast: the body is | |
| 60 | + // form-encoded and `(bool) "false"` is true. | |
| 70 | 61 | $enabled = array_key_exists('enabled', $data) |
| 71 | 62 | ? (bool) Arr::isTrue($data, 'enabled') |
| 72 | 63 | : PermissionGate::isEnabled(); |
| 73 | 64 | |
| @@ -74,16 +65,13 @@ | ||
| 74 | 65 | $toolsets = isset($data['toolsets']) && is_array($data['toolsets']) |
| 75 | 66 | ? $data['toolsets'] |
| 76 | 67 | : PermissionGate::enabledToolsets(); |
| 77 | 68 | |
| 78 | - // Toolsets first: enabling the server and its tool selection in one | |
| 79 | - // request should never leave a window where the server is live with a | |
| 80 | - // stale toolset list. | |
| 69 | + // Toolsets first, so the server is never live with a stale toolset list. | |
| 81 | 70 | PermissionGate::setToolsets($toolsets); |
| 82 | 71 | PermissionGate::setEnabled($enabled); |
| 83 | 72 | |
| 84 | - // get-booking-context reports the toolset list and names tools only the | |
| 85 | - // enabled toolsets expose, so its cache has to go with a toolset change. | |
| 73 | + // get-booking-context's cached output depends on the enabled toolsets. | |
| 86 | 74 | ContextTools::invalidateCache(); |
| 87 | 75 | |
| 88 | 76 | return [ |
| 89 | 77 | 'message' => __('MCP settings have been updated', 'fluent-booking'), |
| @@ -95,17 +83,13 @@ | ||
| 95 | 83 | ]; |
| 96 | 84 | } |
| 97 | 85 | |
| 98 | 86 | /** |
| 99 | - * Install FluentHub (the fluent-toolkit plugin, which carries the MCP | |
| 100 | - * adapter) so the abilities become reachable without a manual upload. | |
| 87 | + * Install FluentHub, which carries the MCP adapter. | |
| 101 | 88 | * |
| 102 | - * FluentBooking bundles no installer of its own. The actual download lives | |
| 103 | - * behind `fluent_toolkit/do_auto_install`, which a paid tier registers; the | |
| 104 | - * `fluent_toolkit/can_auto_install` filter reports whether that handler is | |
| 105 | - * present. With nothing registered (free-only), the endpoint returns the | |
| 106 | - * GitHub link so the operator can install it by hand. `install_plugins` is | |
| 107 | - * required either way — installing a plugin is a site-administration action. | |
| 89 | + * FluentBooking has no installer of its own: the download runs on | |
| 90 | + * `fluent_toolkit/do_auto_install`, which a paid tier registers. Without a | |
| 91 | + * handler the endpoint returns the GitHub link for a manual install. | |
| 108 | 92 | */ |
| 109 | 93 | public function installAdapter(Request $request) |
| 110 | 94 | { |
| 111 | 95 | if (!current_user_can('install_plugins')) { |
| @@ -142,10 +126,9 @@ | ||
| 142 | 126 | return array_merge(['message' => $message], $status); |
| 143 | 127 | } |
| 144 | 128 | |
| 145 | 129 | /** |
| 146 | - * Adapter / FluentHub detection shared by the read endpoint and the install | |
| 147 | - * action so both report the same state after a change. | |
| 130 | + * Adapter and FluentHub state, shared by the read and install endpoints. | |
| 148 | 131 | * |
| 149 | 132 | * @return array |
| 150 | 133 | */ |
| 151 | 134 | private static function statusFields() |
| @@ -190,10 +173,9 @@ | ||
| 190 | 173 | ]; |
| 191 | 174 | } |
| 192 | 175 | |
| 193 | 176 | /** |
| 194 | - * FluentHub loaded in this request (its constant is defined) or present on | |
| 195 | - * disk as an installed plugin. | |
| 177 | + * FluentHub loaded in this request or installed on disk. | |
| 196 | 178 | * |
| 197 | 179 | * @return bool |
| 198 | 180 | */ |
| 199 | 181 | private static function isToolkitPresent() |
| @@ -273,11 +255,10 @@ | ||
| 273 | 255 | return get_plugins(); |
| 274 | 256 | } |
| 275 | 257 | |
| 276 | 258 | /** |
| 277 | - * Heuristic for a local development host, used to offer the self-signed-TLS | |
| 278 | - * override in the Claude Desktop connection snippet. Filterable so a real | |
| 279 | - * deployment on an unusual TLD can correct it. | |
| 259 | + * Guess whether this is a local dev host, to offer the self-signed TLS | |
| 260 | + * override in the Claude Desktop snippet. Filterable for unusual TLDs. | |
| 280 | 261 | * |
| 281 | 262 | * @return bool |
| 282 | 263 | */ |
| 283 | 264 | private static function detectLocalDevEnvironment() |
| @@ -284,10 +265,9 @@ | ||
| 284 | 265 | { |
| 285 | 266 | $host = strtolower((string) wp_parse_url(home_url(), PHP_URL_HOST)); |
| 286 | 267 | $isDev = false; |
| 287 | 268 | |
| 288 | - // .dev is intentionally excluded — it is a real public TLD (HSTS-preloaded), | |
| 289 | - // not a local-only suffix, so it must never trip the TLS-bypass hint. | |
| 269 | + // Not .dev: it is a real public TLD and must never get the TLS-bypass hint. | |
| 290 | 270 | $devTlds = ['.test', '.lab', '.local', '.localhost', '.docker']; |
| 291 | 271 | |
| 292 | 272 | foreach ($devTlds as $tld) { |
| 293 | 273 | $len = strlen($tld); |
| @@ -313,20 +293,12 @@ | ||
| 313 | 293 | return (bool) apply_filters('fluent_booking/mcp_is_local_dev', $isDev, $host); |
| 314 | 294 | } |
| 315 | 295 | |
| 316 | 296 | /** |
| 317 | - * Attach each toolset's tool count and rough context cost. | |
| 297 | + * Attach each toolset's tool count and rough context cost, since every | |
| 298 | + * enabled tool definition sits in the agent's context all session. | |
| 299 | + * AbilitiesRegistrar does the measuring so this and check-mcp-budget.php agree. | |
| 318 | 300 | * |
| 319 | - * Every tool definition sits in the AI client's context for the whole | |
| 320 | - * session whether it gets called or not, so the operator deciding whether | |
| 321 | - * to switch a toolset on is really deciding how much of their agent's | |
| 322 | - * working memory to spend. That number belongs on the toggle, not in a | |
| 323 | - * design document. | |
| 324 | - * | |
| 325 | - * The measurement itself lives on AbilitiesRegistrar so this and | |
| 326 | - * scripts/check-mcp-budget.php cannot report different figures for the | |
| 327 | - * same toolset. | |
| 328 | - * | |
| 329 | 301 | * @param array $toolsets |
| 330 | 302 | * |
| 331 | 303 | * @return array |
| 332 | 304 | */ |
| @@ -334,10 +306,9 @@ | ||
| 334 | 306 | { |
| 335 | 307 | foreach ($toolsets as $key => $meta) { |
| 336 | 308 | $definitions = AbilitiesRegistrar::getDefinitions([$key]); |
| 337 | 309 | |
| 338 | - // Prompts are excluded: they are not in tools/list, and their | |
| 339 | - // bodies are fetched only when someone runs them. | |
| 310 | + // Prompts aren't in tools/list and load only when run. | |
| 340 | 311 | $definitions = array_filter($definitions, function ($definition) { |
| 341 | 312 | return empty($definition['is_prompt']); |
| 342 | 313 | }); |
| 343 | 314 | |