Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.51% covered (success)
95.51%
85 / 89
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ColorGen
95.51% covered (success)
95.51%
85 / 89
66.67% covered (warning)
66.67%
2 / 3
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register_abilities
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
1
 generate
93.44% covered (success)
93.44%
57 / 61
0.00% covered (danger)
0.00%
0 / 1
13.05
1<?php
2
3declare( strict_types=1 );
4
5namespace BLU\Abilities;
6
7/**
8 * Registers the blu/generate-color-palette MCP ability.
9 * Calls the Hiive ai-platform colorgen endpoint to produce ready-to-apply
10 * theme palettes from a text prompt.
11 */
12class ColorGen {
13
14    /**
15     * Register color generation abilities.
16     */
17    public function __construct() {
18        $this->register_abilities();
19    }
20
21    /**
22     * Register color generation abilities.
23     */
24    private function register_abilities(): void {
25        blu_register_ability(
26            'blu/generate-color-palette',
27            array(
28                'label'               => 'Generate Color Palette',
29                'description'         => 'Generate one or more color palettes from a text prompt using AI. Returns ready-to-apply palette objects whose keys match blu/update-global-styles slugs exactly (base, base-midtone, contrast, contrast-midtone, accent-1 … accent-6). After calling this ability, present the palettes to the user and — once they choose — apply the selected palette with blu/update-global-styles. Use for requests like "suggest a color palette", "give me some color options", "generate a palette for my brand", or when the user wants to explore color options before committing.',
30                'category'            => 'blu-mcp',
31                'input_schema'        => array(
32                    'type'       => 'object',
33                    'properties' => array(
34                        'prompt' => array(
35                            'type'        => 'string',
36                            'description' => 'Describe the desired palette mood, brand, or style. Examples: "calm wellness brand with greens and earth tones", "bold tech startup, dark theme", "elegant bakery with warm pastels", "palette based on #E83E8C pink".',
37                        ),
38                    ),
39                    'required'   => array( 'prompt' ),
40                ),
41                'execute_callback'    => array( $this, 'generate' ),
42                'permission_callback' => fn() => current_user_can( 'edit_posts' ),
43                'meta'                => array(
44                    'annotations' => array(
45                        'readonly'    => true,
46                        'destructive' => false,
47                        'idempotent'  => false,
48                    ),
49                ),
50            )
51        );
52    }
53
54    /**
55     * Generate color palettes via the Hiive ai-platform colorgen endpoint.
56     *
57     * @param array $input Tool input parameters.
58     * @return array Standardized ability response.
59     */
60    public function generate( array $input ): array {
61        set_time_limit( 60 );
62
63        $api_url = defined( 'NFD_AI_PLATFORM_URL' ) ? NFD_AI_PLATFORM_URL : 'https://ai-platform.hiive.cloud';
64
65        $hiive_token = '';
66        if ( class_exists( '\NewfoldLabs\WP\Module\Data\HiiveConnection' ) ) {
67            $hiive_token = \NewfoldLabs\WP\Module\Data\HiiveConnection::get_auth_token();
68        }
69
70        if ( empty( $hiive_token ) ) {
71            return blu_prepare_ability_response( 401, __( 'Unable to retrieve Hiive authentication token for palette generation.', 'wp-module-mcp' ) );
72        }
73
74        $prompt = substr( (string) ( $input['prompt'] ?? '' ), 0, 1000 );
75        if ( '' === $prompt ) {
76            return blu_prepare_ability_response( 400, __( 'A prompt is required.', 'wp-module-mcp' ) );
77        }
78
79        $body = array(
80            'prompt' => $prompt,
81            'locale' => get_locale(),
82        );
83
84        $response = wp_remote_post(
85            trailingslashit( $api_url ) . 'api/v1/colorgen/palettes',
86            array(
87                'headers' => array(
88                    'Content-Type'  => 'application/json',
89                    'Authorization' => 'Bearer ' . $hiive_token,
90                ),
91                'body'    => wp_json_encode( $body ),
92                'timeout' => 45,
93            )
94        );
95
96        if ( is_wp_error( $response ) ) {
97            $message = $response->get_error_message();
98            if ( false !== strpos( $message, 'timed out' ) || false !== strpos( $message, 'cURL error 28' ) ) {
99                return blu_prepare_ability_response( 504, __( 'Palette generation timed out.', 'wp-module-mcp' ) );
100            }
101            return blu_prepare_ability_response(
102                502,
103                /* translators: %s: error message */
104                sprintf( __( 'Palette generation service unavailable: %s', 'wp-module-mcp' ), $message )
105            );
106        }
107
108        $status_code = wp_remote_retrieve_response_code( $response );
109        if ( $status_code < 200 || $status_code >= 300 ) {
110            return blu_prepare_ability_response(
111                $status_code,
112                /* translators: %d: HTTP status code */
113                sprintf( __( 'Palette generation failed with status %d.', 'wp-module-mcp' ), $status_code )
114            );
115        }
116
117        $palettes = json_decode( wp_remote_retrieve_body( $response ), true );
118        if ( ! is_array( $palettes ) || empty( $palettes ) ) {
119            return blu_prepare_ability_response( 500, __( 'No palettes returned by the generation service.', 'wp-module-mcp' ) );
120        }
121
122        // Convert underscore keys to hyphenated slugs so they match
123        // blu/update-global-styles palette slug names directly.
124        $normalized = array_map(
125            function ( $palette ) {
126                $out = array();
127                foreach ( $palette as $key => $value ) {
128                    $out[ str_replace( '_', '-', $key ) ] = $value;
129                }
130                return $out;
131            },
132            $palettes
133        );
134
135        return blu_prepare_ability_response(
136            200,
137            array(
138                'palettes' => $normalized,
139                'message'  => sprintf(
140                    /* translators: %d: number of palettes */
141                    __( '%d color palette(s) generated. Present them to the user and apply the chosen one with blu/update-global-styles.', 'wp-module-mcp' ),
142                    count( $normalized )
143                ),
144            )
145        );
146    }
147}