Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/add-mcp-settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: other

Add MCP settings to settings endpoint
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
'jetpack_waf_share_data' => '(bool) Whether the WAF should share basic data with Jetpack',
'jetpack_waf_share_debug_data' => '(bool) Whether the WAF should share debug data with Jetpack',
'jetpack_waf_automatic_rules_last_updated_timestamp' => '(int) Timestamp of the last time the automatic rules were updated',
'mcp_abilities' => '(array) List of MCP Abilities',
),

'response_format' => array(
Expand Down Expand Up @@ -386,6 +387,9 @@ public function get_settings_response() {

$api_cache = $site->is_jetpack() ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;

// Get Sites MCP settings
$mcp_abilities = $this->get_site_mcp_abilities();

$response[ $key ] = array(
// also exists as "options".
'admin_url' => get_admin_url(),
Expand Down Expand Up @@ -514,6 +518,7 @@ public function get_settings_response() {
'jetpack_waf_automatic_rules_last_updated_timestamp' => (int) get_option( 'jetpack_waf_automatic_rules_last_updated_timestamp' ),
'is_fully_managed_agency_site' => (bool) get_option( 'is_fully_managed_agency_site' ),
'wpcom_hide_action_bar' => (bool) get_option( 'wpcom_hide_action_bar' ),
'mcp_abilities' => $mcp_abilities,
);

require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
Expand Down Expand Up @@ -616,6 +621,122 @@ protected function get_wpcom_gifting_subscription_default() {
return false;
}

/**
* Get list of all site level MCP abilities.
*
* @return array
*/
private function get_all_site_mcp_abilities(): array {
$all_abilities = array();
$ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
if ( file_exists( $ability_registry_file ) ) {
require_once $ability_registry_file;
// @phan-suppress-next-line PhanUndeclaredClassMethod
$abilities_resources = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_resources_for_server( 'site-level' );
// @phan-suppress-next-line PhanUndeclaredClassMethod
$abilities_tools = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_tools_for_server( 'site-level' );
// @phan-suppress-next-line PhanUndeclaredClassMethod
$abilities_prompts = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_prompts_for_server( 'site-level' );
$all_abilities = array_merge( $abilities_resources, $abilities_tools, $abilities_prompts );
}
return apply_filters( 'jetpack_site_mcp_abilities', $all_abilities );
}

/**
* Get ability meta from config.
*
* @param string $ability_name Ability name, i.e. wpcom-mcp/posts-search.
*
* @return array
*/
private function get_mcp_abilities_metadata( string $ability_name ): array {
$ability_meta = array();
$ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
if ( file_exists( $ability_registry_file ) ) {
require_once $ability_registry_file;
// @phan-suppress-next-line PhanUndeclaredClassMethod
$ability_meta = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_metadata( $ability_name );
}
return apply_filters( 'jetpack_site_mcp_ability_meta', $ability_meta, $ability_name );
}

/**
* Get MCP abilities for the current site.
*
* @return array
*/
public function get_site_mcp_abilities(): array {
$current_mcp_abilities = get_option( 'mcp_abilities', array() );
if ( ! is_array( $current_mcp_abilities ) ) {
$current_mcp_abilities = array();
}

$all_abilities = $this->get_all_site_mcp_abilities();
if ( empty( $all_abilities ) ) {
return array();
}

$computed_abilities = array();
foreach ( $all_abilities as $ability_name ) {
// Get base metadata first
$ability_meta = $this->get_mcp_abilities_metadata( $ability_name );
if ( ! empty( $ability_meta ) ) {
// Use stored value or fall back to metadata default
$enabled = $current_mcp_abilities[ $ability_name ] ?? $ability_meta['enabled'] ?? false;

$computed_abilities[ $ability_name ] = array(
'name' => $ability_name,
'description' => $ability_meta['description'] ?? '',
'category' => $ability_meta['category'] ?? '',
'type' => $ability_meta['type'] ?? '',
'enabled' => (bool) $enabled,
);
}
}
return $computed_abilities;
}

/**
* Sets the MCP abilities for the current site.
*
* @param mixed $value MCP abilities array.
*
* @return true|WP_Error
*/
public function set_site_mcp_abilities( $value ) {
// Validate input format
if ( ! is_array( $value ) ) {
return new WP_Error( 'invalid_format', __( 'Site MCP abilities must be an array', 'jetpack' ) );
}

$all_abilities = $this->get_all_site_mcp_abilities();

// Filter ability names that don't exist
$value = array_filter(
$value,
function ( $ability_name ) use ( $all_abilities ) {
return in_array( $ability_name, $all_abilities, true );
},
ARRAY_FILTER_USE_KEY
);

// Validate each ability exists and value is boolean-like
foreach ( $value as $ability_name => $enabled ) {
if ( ! is_string( $ability_name ) || ( ! WPCOM_JSON_API::is_truthy( $enabled ) && ! WPCOM_JSON_API::is_falsy( $enabled ) ) ) {
$error_message = sprintf(
// Translators: %s is an MCP ability name
__( 'Invalid ability: %s', 'jetpack' ),
$ability_name
);
return new WP_Error( 'invalid_ability', $error_message );
}
}

update_option( 'mcp_abilities', $value );

return true;
}

/**
* Get locale.
*
Expand Down Expand Up @@ -868,7 +989,7 @@ function ( &$value ) {
}
);

$old_subscription_options = get_option( 'subscription_options' );
$old_subscription_options = get_option( 'subscription_options', array() );
$new_subscription_options = array_merge( $old_subscription_options, $filtered_value );

if ( update_option( $key, $new_subscription_options ) ) {
Expand Down Expand Up @@ -1186,6 +1307,14 @@ function ( &$value ) {
}
break;

case 'mcp_abilities':
$result = $this->set_site_mcp_abilities( $value );
if ( is_wp_error( $result ) ) {
return $result;
}
$updated[ $key ] = $this->get_site_mcp_abilities();
break;

default:
// allow future versions of this endpoint to support additional settings keys.
if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
'jetpack_comment_form_color_scheme' => '(string) The color scheme for the comment form',
'is_fully_managed_agency_site' => '(bool) Whether the site is a fully managed agency site',
'wpcom_hide_action_bar' => '(bool) Whether to hide the Action bar',
'mcp_abilities' => '(array) List of MCP Abilities',
),

'response_format' => array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,47 @@ public function set_up() {
parent::set_up();

WPCOM_JSON_API::init()->token_details = array( 'blog_id' => $blog_id );

// Mock available abilities (just names)
add_filter(
'jetpack_site_mcp_abilities',
function () {
return array(
'wpcom-mcp/posts-search',
'wpcom-mcp/user-sites',
);
}
);

// Mock ability metadata
add_filter(
'jetpack_site_mcp_ability_meta',
function ( $ability_meta, $ability_name ) {
$test_metadata = array(
'wpcom-mcp/posts-search' => array(
'description' => 'Search posts',
'category' => 'search',
'type' => 'tool',
'enabled' => true,
),
'wpcom-mcp/user-sites' => array(
'description' => 'Access user sites',
'category' => 'user',
'type' => 'resource',
'enabled' => false,
),
);
return $test_metadata[ $ability_name ] ?? array();
},
10,
2
);
}

public function tear_down() {
// Remove the filter to avoid affecting other tests
remove_all_filters( 'jetpack_mcp_abilities' );
parent::tear_down();
}

/**
Expand Down Expand Up @@ -272,6 +313,7 @@ public function make_post_request( $setting ) {
'page_on_front' => '(string) The page ID of the page to use as the site\'s homepage. It will apply only if \'show_on_front\' is set to \'page\'.',
'page_for_posts' => '(string) The page ID of the page to use as the site\'s posts page. It will apply only if \'show_on_front\' is set to \'page\'.',
'subscription_options' => '(array) Array of two options used in subscription email templates: \'invitation\' and \'comment_follow\' strings.',
'mcp_abilities' => '(array) List of MCP Abilities',
),

'response_format' => array(
Expand Down Expand Up @@ -302,6 +344,26 @@ public static function setting_default_key_values() {
'woocommerce_default_country' => array( 'woocommerce_default_country', '' ),
'woocommerce_store_postcode' => array( 'woocommerce_store_postcode', '' ),
'woocommerce_onboarding_profile' => array( 'woocommerce_onboarding_profile', array() ),
// Add MCP settings default
'mcp_abilities' => array(
'mcp_abilities',
array(
'wpcom-mcp/posts-search' => array(
'name' => 'wpcom-mcp/posts-search',
'description' => 'Search posts',
'category' => 'search',
'type' => 'tool',
'enabled' => true,
),
'wpcom-mcp/user-sites' => array(
'name' => 'wpcom-mcp/user-sites',
'description' => 'Access user sites',
'category' => 'user',
'type' => 'resource',
'enabled' => false,
),
),
),
);
}

Expand All @@ -318,6 +380,27 @@ public static function setting_value_pairs_get_request() {
'woocommerce_default_country' => array( 'woocommerce_default_country', 'woocommerce_default_country', 'US:NY' ),
'woocommerce_store_postcode' => array( 'woocommerce_store_postcode', 'woocommerce_store_postcode', '98738' ),
'woocommerce_onboarding_profile' => array( 'woocommerce_onboarding_profile', 'woocommerce_onboarding_profile', array( 'test' => 'test value' ) ),
// Add MCP settings GET test
'mcp_abilities' => array(
'mcp_abilities', // option name
'mcp_abilities', // setting name
array(
'wpcom-mcp/posts-search' => array(
'name' => 'wpcom-mcp/posts-search',
'description' => 'Search posts',
'category' => 'search',
'type' => 'tool',
'enabled' => true,
),
'wpcom-mcp/user-sites' => array(
'name' => 'wpcom-mcp/user-sites',
'description' => 'Access user sites',
'category' => 'user',
'type' => 'resource',
'enabled' => true,
),
),
),
);
}

Expand Down Expand Up @@ -351,6 +434,30 @@ public static function setting_value_pairs_post_request() {
'comment_follow' => "Test string 2\n\n Other line",
),
),
// Add MCP settings POST tests
'mcp_abilities valid' => array(
'mcp_abilities',
array(
'wpcom-mcp/posts-search' => 1,
'wpcom-mcp/user-sites' => 0,
),
array(
'wpcom-mcp/posts-search' => array(
'name' => 'wpcom-mcp/posts-search',
'description' => 'Search posts',
'category' => 'search',
'type' => 'tool',
'enabled' => true,
),
'wpcom-mcp/user-sites' => array(
'name' => 'wpcom-mcp/user-sites',
'description' => 'Access user sites',
'category' => 'user',
'type' => 'resource',
'enabled' => false,
),
),
),
);
}
}
Loading