| @@ -7,22 +7,16 @@ | ||
| 7 | 7 | |
| 8 | 8 | defined('ABSPATH') || exit; |
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | - * Bootstrap for FluentBooking's Model Context Protocol integration. | |
| 11 | + * Bootstrap for the MCP integration. | |
| 12 | 12 | * |
| 13 | - * Wires the WordPress Abilities API (core 6.9+) to the WP MCP Adapter, which is | |
| 14 | - * provided by FluentToolkit (which bundles it) or the standalone mcp-adapter | |
| 15 | - * plugin — whichever is present. FluentBooking bundles nothing; it consumes | |
| 16 | - * whatever is loaded, and surfaces an admin notice rather than failing silently | |
| 17 | - * when neither is. | |
| 13 | + * Wires the Abilities API (WP 6.9+) to the MCP Adapter, supplied by | |
| 14 | + * FluentToolkit or the standalone mcp-adapter plugin. Shows an admin notice | |
| 15 | + * when neither is loaded. | |
| 18 | 16 | * |
| 19 | - * The whole surface is gated behind PermissionGate::isEnabled() (default off). | |
| 20 | - * An operator turns it on in Settings and connects with an application password. | |
| 21 | - * Even when on, the endpoint sits behind WP authentication, a FluentBooking | |
| 22 | - * permission (transport gate), and per-ability permission checks. | |
| 23 | - * | |
| 24 | - * Called once from app/Hooks/actions.php. | |
| 17 | + * Off by default (PermissionGate::isEnabled()). When on, the endpoint needs WP | |
| 18 | + * auth, the transport permission gate and per-ability permission checks. | |
| 25 | 19 | */ |
| 26 | 20 | class MCPInit |
| 27 | 21 | { |
| 28 | 22 | const SERVER_ID = 'fluent-booking'; |
| @@ -27,18 +21,10 @@ | ||
| 27 | 21 | { |
| 28 | 22 | const SERVER_ID = 'fluent-booking'; |
| 29 | 23 | |
| 30 | 24 | /** |
| 31 | - * Bootstrap entry point. | |
| 32 | - * | |
| 33 | - * Toolkit discovery and the settings card register UNCONDITIONALLY: the | |
| 34 | - * Toolkit needs to list FluentBooking on its MCP page while the feature is | |
| 35 | - * still off, or the operator has no way to find the switch. Both are | |
| 36 | - * add_filter calls that no-op unless something applies them, so the cost | |
| 37 | - * when nothing does is nil. | |
| 38 | - * | |
| 39 | - * The server itself is instantiated only when enabled, so a site that never | |
| 40 | - * turns MCP on pays nothing beyond one autoloaded option read. | |
| 25 | + * Toolkit discovery and the settings entry always register, since they are | |
| 26 | + * where the operator finds the switch. The server starts only when enabled. | |
| 41 | 27 | */ |
| 42 | 28 | public static function boot() |
| 43 | 29 | { |
| 44 | 30 | self::registerWithToolkit(); |
| @@ -50,22 +36,19 @@ | ||
| 50 | 36 | } |
| 51 | 37 | |
| 52 | 38 | public function init() |
| 53 | 39 | { |
| 54 | - // Abilities API hooks — fire only on WP 6.9+ (or with the Abilities API | |
| 55 | - // feature plugin active). | |
| 40 | + // Fire only on WP 6.9+ or with the Abilities API plugin. | |
| 56 | 41 | add_action('wp_abilities_api_categories_init', [$this, 'registerCategory']); |
| 57 | 42 | add_action('wp_abilities_api_init', [$this, 'registerAbilities']); |
| 58 | 43 | |
| 59 | 44 | add_action('admin_init', [$this, 'registerPrivacyPolicyContent']); |
| 60 | 45 | |
| 61 | - // Server registration — fires only when an adapter is loaded. | |
| 46 | + // Fires only when an adapter is loaded. | |
| 62 | 47 | add_action('mcp_adapter_init', [$this, 'registerCustomServer']); |
| 63 | 48 | |
| 64 | - // Keep get-booking-context honest: drop its cache whenever something it | |
| 65 | - // reports changes, so an operator's edit is visible to the agent on the | |
| 66 | - // next call instead of up to CACHE_TTL later. Static callback so a site | |
| 67 | - // can remove_action it. | |
| 49 | + // Drop get-booking-context's cache when anything it reports changes. | |
| 50 | + // Static callback so a site can remove_action it. | |
| 68 | 51 | $invalidate = [ContextTools::class, 'invalidateCache']; |
| 69 | 52 | |
| 70 | 53 | foreach ([ |
| 71 | 54 | 'fluent_booking/after_create_calendar', |
| @@ -78,9 +61,8 @@ | ||
| 78 | 61 | ] as $hook) { |
| 79 | 62 | add_action($hook, $invalidate); |
| 80 | 63 | } |
| 81 | 64 | |
| 82 | - // Warn the operator if they enabled MCP but no adapter is installed. | |
| 83 | 65 | add_action('admin_notices', [$this, 'maybeShowAdapterNotice']); |
| 84 | 66 | } |
| 85 | 67 | |
| 86 | 68 | public function registerCategory() |
| @@ -95,11 +77,10 @@ | ||
| 95 | 77 | { |
| 96 | 78 | AbilitiesRegistrar::register(); |
| 97 | 79 | |
| 98 | 80 | /** |
| 99 | - * Fires after FluentBooking registers its core MCP abilities. | |
| 100 | - * FluentBooking Pro hooks this to register its own abilities (payments) | |
| 101 | - * under the same `fluent-booking/` namespace and on the same server. | |
| 81 | + * Fires after FluentBooking registers its core MCP abilities. Pro | |
| 82 | + * registers its own here, under the same namespace and server. | |
| 102 | 83 | * |
| 103 | 84 | * @since 2.3.0 |
| 104 | 85 | */ |
| 105 | 86 | do_action('fluent_booking/mcp_loaded'); |
| @@ -105,12 +86,10 @@ | ||
| 105 | 86 | do_action('fluent_booking/mcp_loaded'); |
| 106 | 87 | } |
| 107 | 88 | |
| 108 | 89 | /** |
| 109 | - * Register the dedicated FluentBooking MCP server. The endpoint defaults to | |
| 110 | - * /wp-json/fluent-booking/mcp — a sibling of the admin REST namespace | |
| 111 | - * (fluent-booking/v2) but deliberately outside it, so it is not caught by | |
| 112 | - * that policy stack. | |
| 90 | + * Register the FluentBooking MCP server at /wp-json/fluent-booking/mcp, | |
| 91 | + * outside fluent-booking/v2 so the admin policy stack doesn't apply. | |
| 113 | 92 | * |
| 114 | 93 | * @param object $adapter the \WP\MCP\Core\McpAdapter instance |
| 115 | 94 | */ |
| 116 | 95 | public function registerCustomServer($adapter) |
| @@ -122,10 +101,9 @@ | ||
| 122 | 101 | $abilityNames = AbilitiesRegistrar::getToolNames(); |
| 123 | 102 | |
| 124 | 103 | /** |
| 125 | 104 | * Filters the ability names exposed by the FluentBooking MCP server. |
| 126 | - * Pro and extensions push their ability names here so they land on the | |
| 127 | - * same server as the free ones. | |
| 105 | + * Pro and extensions add theirs here to share the server. | |
| 128 | 106 | * |
| 129 | 107 | * @since 2.3.0 |
| 130 | 108 | * |
| 131 | 109 | * @param array $abilityNames fully-qualified ability names |
| @@ -132,11 +110,9 @@ | ||
| 132 | 110 | */ |
| 133 | 111 | $abilityNames = apply_filters('fluent_booking/mcp_ability_names', $abilityNames); |
| 134 | 112 | $abilityNames = array_values(array_unique(array_filter((array) $abilityNames))); |
| 135 | 113 | |
| 136 | - // Prompts are a separate argument to create_server(). Listed as tools | |
| 137 | - // they would show up in tools/list carrying a body of instructions — | |
| 138 | - // both wrong and, at a few hundred tokens each, expensive. | |
| 114 | + // Prompts go in their own argument, not in tools/list. | |
| 139 | 115 | $promptNames = AbilitiesRegistrar::getPromptNames(); |
| 140 | 116 | |
| 141 | 117 | /** |
| 142 | 118 | * Filters the prompt ability names exposed by the server. |
| @@ -168,21 +144,14 @@ | ||
| 168 | 144 | ); |
| 169 | 145 | } |
| 170 | 146 | |
| 171 | 147 | /** |
| 172 | - * Announce FluentBooking to FluentToolkit's MCP page. | |
| 173 | - * | |
| 174 | - * The Toolkit discovers products through `fluent_kit/mcp_products` and | |
| 175 | - * toggles them through `fluent_kit/mcp_toggle_handlers`. Without these, | |
| 176 | - * FluentBooking's server would be fully functional yet never appear in the | |
| 177 | - * Toolkit's list, which reads to an operator as "not supported". | |
| 178 | - * | |
| 179 | - * Runs even while MCP is off so the card is reachable to switch on. | |
| 148 | + * List FluentBooking on FluentToolkit's MCP page, even while MCP is off, | |
| 149 | + * so the operator can switch it on there. | |
| 180 | 150 | */ |
| 181 | 151 | public static function registerWithToolkit() |
| 182 | 152 | { |
| 183 | - // Static callbacks rather than closures so a site can remove_filter | |
| 184 | - // them — a closure registered here would be unreachable forever. | |
| 153 | + // Static callbacks, not closures, so a site can remove_filter them. | |
| 185 | 154 | add_filter('fluent_kit/mcp_products', [self::class, 'addToolkitProduct']); |
| 186 | 155 | add_filter('fluent_kit/mcp_toggle_handlers', [self::class, 'addToolkitToggleHandler']); |
| 187 | 156 | } |
| 188 | 157 | |
| @@ -226,14 +195,10 @@ | ||
| 226 | 195 | return $handlers; |
| 227 | 196 | } |
| 228 | 197 | |
| 229 | 198 | /** |
| 230 | - * Add the MCP entry to FluentBooking → Settings. | |
| 231 | - * | |
| 232 | - * Registered even while the feature is off — it is the only place an | |
| 233 | - * operator can turn it on, so hiding it when disabled would make the switch | |
| 234 | - * unreachable. Priority 30 keeps it after the existing settings entries | |
| 235 | - * rather than in the middle of them. | |
| 199 | + * Add the MCP entry to Settings. Registered while off too, since it holds | |
| 200 | + * the switch. Priority 30 puts it after the existing entries. | |
| 236 | 201 | */ |
| 237 | 202 | public static function registerSettingsMenu() |
| 238 | 203 | { |
| 239 | 204 | add_filter('fluent_booking/settings_menu_items', [self::class, 'addSettingsMenuItem'], 30); |
| @@ -263,18 +228,15 @@ | ||
| 263 | 228 | return $items; |
| 264 | 229 | } |
| 265 | 230 | |
| 266 | 231 | /** |
| 267 | - * How many abilities the server currently exposes, Pro's included. Reflects | |
| 268 | - * the operator's toolset selection, because that is the number that governs | |
| 269 | - * how much of every request's context window this server occupies. | |
| 232 | + * How many tools the server exposes for the enabled toolsets, Pro's | |
| 233 | + * included. Prompts don't count; they aren't in the tool list. | |
| 270 | 234 | * |
| 271 | 235 | * @return int |
| 272 | 236 | */ |
| 273 | 237 | public static function toolsCount() |
| 274 | 238 | { |
| 275 | - // Tools only: prompts do not occupy the tool list, which is the number | |
| 276 | - // this count exists to report. | |
| 277 | 239 | $names = AbilitiesRegistrar::getToolNames(); |
| 278 | 240 | |
| 279 | 241 | $names = apply_filters('fluent_booking/mcp_ability_names', $names); |
| 280 | 242 | |
| @@ -317,15 +279,10 @@ | ||
| 317 | 279 | && function_exists('wp_register_ability'); |
| 318 | 280 | } |
| 319 | 281 | |
| 320 | 282 | /** |
| 321 | - * Suggest privacy-policy wording while MCP is on. | |
| 322 | - * | |
| 323 | - * Enabling this makes whichever model provider the connected client uses a | |
| 324 | - * recipient of attendee data the moment a read tool is called — the site | |
| 325 | - * owner is the controller and has to disclose that. WordPress has a place | |
| 326 | - * for exactly this text; not using it left the transfer undisclosed | |
| 327 | - * everywhere except the settings screen. | |
| 283 | + * Suggest privacy-policy wording while MCP is on. The connected client's | |
| 284 | + * model provider receives attendee data, which the site owner must disclose. | |
| 328 | 285 | */ |
| 329 | 286 | public function registerPrivacyPolicyContent() |
| 330 | 287 | { |
| 331 | 288 | if (!function_exists('wp_add_privacy_policy_content')) { |