PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Http/Controllers/McpController.php +24 -53 2.4.0 → 2.5.0 View file →
@@ -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