Fluent Toolkit's bundled mcp-adapter library: hook-order race condition causes recurring "ability does not exist" errors
Hi,
We're seeing 3 recurring PHP errors in our logs on every REST API request, coming from the mcp-adapter library bundled inside fluent-toolkit (wp-content/plugins/fluent-toolkit/libs/mcp-adapter/, class WP\MCP\Core\McpAdapter, v0.5.0):
[ERROR] WordPress ability 'mcp-adapter/discover-abilities' does not exist. | Context: ["RegisterAbilityAsMcpTool::mcp-adapter/discover-abilities"]
[ERROR] WordPress ability 'mcp-adapter/get-ability-info' does not exist.
[ERROR] WordPress ability 'mcp-adapter/execute-ability' does not exist.
Root cause: McpAdapter::instance() hooks its own init() to rest_api_init at priority 15 (includes/Core/McpAdapter.php). Inside init(), maybe_create_default_server() adds a listener for wp_abilities_api_init (which is supposed to register the three abilities via DiscoverAbilitiesAbility::register() etc.), then immediately, synchronously, fires do_action('mcp_adapter_init'), which runs DefaultServerFactory::create() and tries to wrap those same abilities as MCP tools via RegisterAbilityAsMcpTool.
The problem: WP_Abilities_Registry::get_instance() (core Abilities API) is a lazy singleton β wp_abilities_api_init fires on the first call to it, from whichever plugin triggers it first. On a site with several other plugins that also use the Abilities API (in our case: a custom MCP plugin, FluentCRM/FluentBoards/FluentSupport, BetterDocs, SEOPress, Security Ninja), that first trigger typically happens before rest_api_init@15 runs. By the time McpAdapter::register_default_abilities finally gets added as a listener, wp_abilities_api_init has already fired and won't fire again this request β so the three abilities never get registered, and the subsequent RegisterAbilityAsMcpTool call correctly (but noisily) reports them as missing.
Suggested fix: initialize McpAdapter earlier β e.g. also hook init() to the init action at an early priority (1 or lower), in addition to (or instead of) rest_api_init@15. init() is already idempotent (guarded by self::$initialized), so this is safe. WP_Abilities_Registry::get_instance() explicitly refuses to run before init has fired (checked via did_action('init')), so hooking at init priority 1 reliably wins the race against any other plugin's lazy trigger.
As a side note: the "MCP" toggle in the Fluent Hub admin page only disables individual products' own ability registrations (FluentCRM/FluentBoards/FluentSupport MCPInit::registerAbilities) β it doesn't affect the generic default MCP server described above, so it's not a workaround for this specific issue.
Happy to share more diagnostic details if useful.
I am checking it right now. Will release an update if I can recreate the issue.
