Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
57.81% covered (warning)
57.81%
37 / 64
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
McpServer
57.81% covered (warning)
57.81%
37 / 64
60.00% covered (warning)
60.00%
3 / 5
17.51
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 register_server
8.00% covered (danger)
8.00%
2 / 25
0.00% covered (danger)
0.00%
0 / 1
16.46
 suppress_sibling_default_server_refire
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 register_abilities
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 register_ability_categories
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types=1 );
4
5namespace BLU;
6
7use BLU\Abilities\BlockEditor;
8use BLU\Abilities\AbilityGateway;
9use BLU\Abilities\CustomPostTypes;
10use BLU\Abilities\GlobalStyles;
11use BLU\Abilities\Media;
12use BLU\Abilities\Pages;
13use BLU\Abilities\Posts;
14use BLU\Abilities\Prompts;
15use BLU\Abilities\Resources;
16use BLU\Abilities\RestApiCrud;
17use BLU\Abilities\Settings;
18use BLU\Abilities\SiteInfo;
19use BLU\Abilities\Users;
20use BLU\Abilities\WooOrders;
21use BLU\Abilities\WooProducts;
22use BLU\Abilities\Themes;
23use BLU\Abilities\ImageGen;
24use BLU\Abilities\LogoGen;
25use BLU\Abilities\ImageEdit;
26use BLU\Abilities\DocumentRead;
27use BLU\Abilities\ColorGen;
28
29use BLU\Integrations\WooCommerceAbilities;
30use BLU\Validation\McpValidation;
31use Bluehost\Plugin\WP\MCP\Core\McpAdapter;
32use Bluehost\Plugin\WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler;
33use Bluehost\Plugin\WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler;
34use Bluehost\Plugin\WP\MCP\Servers\DefaultServerFactory;
35use Bluehost\Plugin\WP\MCP\Transport\HttpTransport;
36
37/**
38 * MCP Server registration for Bluehost abilities.
39 */
40class McpServer {
41
42    /**
43     * Initializes the class by setting up actions to register the server and abilities
44     * during the respective initialization hooks.
45     *
46     * @return void
47     */
48    public function __construct() {
49        add_action( 'mcp_adapter_init', array( $this, 'register_server' ) );
50        // Runs after register_server() and the prefixed DefaultServerFactory hook so the
51        // first (own-adapter) firing has completed successfully before we clean up.
52        add_action( 'mcp_adapter_init', array( $this, 'suppress_sibling_default_server_refire' ), 999 );
53        add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
54        add_action( 'wp_abilities_api_categories_init', array( $this, 'register_ability_categories' ) );
55
56        // Compat shims for sibling plugins that lazy-load their MCP machinery against
57        // their own transport route. Each one self-registers its hooks.
58        new WooCommerceAbilities();
59    }
60
61    /**
62     * Registers a server with specified configurations, including abilities, transports, and handlers,
63     * for the Blue host MCP server functionality.
64     *
65     * The mcp_adapter_init action name is a plain string and therefore shared across every
66     * vendored copy of wordpress/mcp-adapter on the site (e.g. the unprefixed copy WooCommerce
67     * ships). Without the guard below we would re-enter create_server() on each foreign
68     * adapter's do_action, hitting a duplicate_server_id error attributed to the Bluehost
69     * Strauss-prefixed McpAdapter class.
70     *
71     * @param McpAdapter|null $adapter Adapter instance that fired the action.
72     *
73     * @return void If the server creation is successful
74     * @throws \Exception If the server creation fails.
75     */
76    public function register_server( $adapter = null ): void {
77
78        if ( ! $adapter instanceof McpAdapter || McpAdapter::instance() !== $adapter ) {
79            return;
80        }
81
82        $use_gateway = apply_filters( 'blu_mcp_use_gateway', true );
83
84        if ( $use_gateway ) {
85            $abilities = AbilityGateway::GATEWAY_ABILITIES;
86        } else {
87            // Legacy: expose all individual tools directly.
88            $abilities = blu_get_ability_by_type( 'tool' );
89        }
90
91        $prompts   = blu_get_ability_by_type( 'prompt' );
92        $resources = blu_get_ability_by_type( 'resource' );
93        $adapter->create_server(
94            'blu-mcp', // server_id
95            'blu', // server_route_namespace
96            'mcp', // server_route
97            'Bluehost MCP Server', // server_name
98            'MCP server exposing Bluehost WordPress abilities', // server_description
99            '1.0.0', // server_version
100            array( HttpTransport::class ), // mcp_transports
101            ErrorLogMcpErrorHandler::class, // error_handler
102            NullMcpObservabilityHandler::class, // observability_handler
103            $abilities, // tools,
104            $resources, // resources
105            $prompts, // prompts
106            function ( \WP_REST_Request $request ) {
107                // transport_permission_callback
108                return ( new McpValidation( $request ) )->is_authenticated();
109            }
110        );
111    }
112
113    /**
114     * Detaches the Strauss-prefixed DefaultServerFactory::create hook once our own
115     * mcp_adapter_init firing has completed, so that a sibling adapter's later firing
116     * of the same shared-string action does not re-trigger it and emit a
117     * duplicate_server_id _doing_it_wrong attributed to our prefixed namespace.
118     *
119     * @param McpAdapter|null $adapter Adapter instance that fired the action.
120     *
121     * @return void
122     */
123    public function suppress_sibling_default_server_refire( $adapter = null ): void {
124        if ( ! $adapter instanceof McpAdapter || McpAdapter::instance() !== $adapter ) {
125            return;
126        }
127        remove_action(
128            'mcp_adapter_init',
129            array( DefaultServerFactory::class, 'create' )
130        );
131    }
132
133    /**
134     * Registers various abilities by initializing their respective classes.
135     *
136     * @return void
137     */
138    public function register_abilities(): void {
139        // Gateway tools (list/schema/call) must be registered before other abilities
140        // so they are available when register_server() runs.
141        new AbilityGateway();
142        // Initialize all ability classes
143        new Prompts();
144        new Resources();
145        new Posts();
146        new Pages();
147        new Media();
148        new Users();
149        new SiteInfo();
150        new Settings();
151        new CustomPostTypes();
152        new RestApiCrud();
153        new GlobalStyles();
154        new WooProducts();
155        new WooOrders();
156        new Themes();
157        new BlockEditor();
158        new ImageGen();
159        new LogoGen();
160        new ImageEdit();
161        new DocumentRead();
162        new ColorGen();
163    }
164
165    /**
166     * Registers ability categories for the Bluehost MCP, including a label and description for categorization.
167     *
168     * @return void
169     */
170    public function register_ability_categories(): void {
171        wp_register_ability_category(
172            'blu-mcp',
173            array(
174                'label'       => 'Bluehost MCP',
175                'description' => 'Bluehost-specific abilities for use with MCP',
176            )
177        );
178    }
179}