From 41b6666bd888c3225659a9f0b504be3068d975e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 14 Jul 2026 11:22:03 +0200 Subject: [PATCH 1/3] Abilities API: Use the `public` meta flag for core abilities. Switch the three core abilities (`core/get-site-info`, `core/get-user-info`, and `core/get-environment-info`) from `meta.show_in_rest` to the higher-level `meta.public` flag. The `public` flag seeds `show_in_rest`, so REST exposure stays the same. Other channels (MCP, AI agents) can now read the intent through the `wp_register_ability_args` filter. Follow-up to PR #12463. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wp-includes/abilities.php | 12 ++++++------ .../tests/abilities-api/wpRegisterCoreAbilities.php | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/abilities.php b/src/wp-includes/abilities.php index 0eb87a4581589..236b99836a3b9 100644 --- a/src/wp-includes/abilities.php +++ b/src/wp-includes/abilities.php @@ -132,12 +132,12 @@ function wp_register_core_abilities(): void { return current_user_can( 'manage_options' ); }, 'meta' => array( - 'annotations' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), - 'show_in_rest' => true, + 'public' => true, ), ) ); @@ -256,12 +256,12 @@ function wp_register_core_abilities(): void { return is_user_logged_in(); }, 'meta' => array( - 'annotations' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), - 'show_in_rest' => true, + 'public' => true, ), ) ); @@ -342,12 +342,12 @@ function wp_register_core_abilities(): void { return current_user_can( 'manage_options' ); }, 'meta' => array( - 'annotations' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), - 'show_in_rest' => true, + 'public' => true, ), ) ); diff --git a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php index 83011b727a994..9f15250022c7a 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php @@ -61,6 +61,7 @@ public function test_core_get_site_info_ability_is_registered(): void { $ability = wp_get_ability( 'core/get-site-info' ); $this->assertInstanceOf( WP_Ability::class, $ability ); + $this->assertTrue( $ability->get_meta_item( 'public', false ) ); $this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) ); $input_schema = $ability->get_input_schema(); @@ -203,6 +204,7 @@ public function test_core_get_user_info_ability_is_registered(): void { $ability = wp_get_ability( 'core/get-user-info' ); $this->assertInstanceOf( WP_Ability::class, $ability ); + $this->assertTrue( $ability->get_meta_item( 'public', false ) ); $this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) ); $input_schema = $ability->get_input_schema(); @@ -308,6 +310,7 @@ public function test_core_get_environment_info_ability_is_registered(): void { $ability = wp_get_ability( 'core/get-environment-info' ); $this->assertInstanceOf( WP_Ability::class, $ability ); + $this->assertTrue( $ability->get_meta_item( 'public', false ) ); $this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) ); $input_schema = $ability->get_input_schema(); From c83213894df2538af7d8901c266cd4589226e5db Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 14 Jul 2026 14:12:53 +0200 Subject: [PATCH 2/3] Abilities API: Refine the `public` meta flag default and docs. Make the `public` meta flag default to false so it is always present in the resolved meta, and authors opt in explicitly. A null value is treated as unset. Reword the flag documentation across the class, registry, REST schema, and `wp_register_ability()`. Describe it as "available to clients such as the REST API, MCP, or AI agents", note the false default, and mention that per-channel settings such as `show_in_rest` can override it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wp-includes/abilities-api.php | 8 ++-- .../class-wp-abilities-registry.php | 15 +++---- .../abilities-api/class-wp-ability.php | 40 ++++++++++++------- ...s-wp-rest-abilities-v1-list-controller.php | 2 +- .../phpunit/tests/abilities-api/wpAbility.php | 38 ++++++++++++++++-- .../tests/abilities-api/wpRegisterAbility.php | 1 + 6 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/wp-includes/abilities-api.php b/src/wp-includes/abilities-api.php index 4e98aaacdfb72..52d5db5891d08 100644 --- a/src/wp-includes/abilities-api.php +++ b/src/wp-includes/abilities-api.php @@ -264,10 +264,10 @@ * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } - * @type bool $public Optional. Whether the ability is intended to be publicly - * available to clients. When true, channel-specific exposure - * flags such as `$show_in_rest` default to true. An explicitly - * set channel flag always takes precedence. + * @type bool $public Optional. Whether the ability is meant to be available to + * clients such as the REST API, MCP, or AI agents. Seeds + * the default for per-channel flags like `$show_in_rest`. + * Defaults to false. * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * When true, the ability can be invoked via HTTP requests. * Default is the value of `$public` when set, false otherwise. diff --git a/src/wp-includes/abilities-api/class-wp-abilities-registry.php b/src/wp-includes/abilities-api/class-wp-abilities-registry.php index 505ad56c8145d..7d1f26d7690ca 100644 --- a/src/wp-includes/abilities-api/class-wp-abilities-registry.php +++ b/src/wp-includes/abilities-api/class-wp-abilities-registry.php @@ -71,10 +71,10 @@ final class WP_Abilities_Registry { * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } - * @type bool $public Optional. Whether the ability is intended to be publicly - * available to clients. When true, channel-specific exposure - * flags such as `$show_in_rest` default to true. An explicitly - * set channel flag always takes precedence. + * @type bool $public Optional. Whether the ability is meant to be available + * to clients such as the REST API, MCP, or AI agents. + * Seeds the default for per-channel flags like + * `$show_in_rest`. Defaults to false. * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * Default is the value of `$public` when set, false otherwise. * } @@ -125,9 +125,10 @@ public function register( string $name, array $args ): ?WP_Ability { * Optional. Additional metadata for the ability. * * @type array $annotations Optional. Annotation metadata for the ability. - * @type bool $public Optional. Whether the ability is intended to be - * publicly available to clients. Seeds the default for - * channel-specific exposure flags like `$show_in_rest`. + * @type bool $public Optional. Whether the ability is meant to be + * available to clients such as the REST API, MCP, or AI + * agents. Seeds the default for per-channel flags like + * `$show_in_rest`. Defaults to false. * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * Default is the value of `$public` when set, false otherwise. * } diff --git a/src/wp-includes/abilities-api/class-wp-ability.php b/src/wp-includes/abilities-api/class-wp-ability.php index 077a137a2b1c1..2127417a887b9 100644 --- a/src/wp-includes/abilities-api/class-wp-ability.php +++ b/src/wp-includes/abilities-api/class-wp-ability.php @@ -28,6 +28,14 @@ class WP_Ability { */ protected const DEFAULT_SHOW_IN_REST = false; + /** + * The default value for the `public` meta. + * + * @since 7.1.0 + * @var bool + */ + protected const DEFAULT_PUBLIC = false; + /** * The default ability annotations. * They are not guaranteed to provide a faithful description of ability behavior. @@ -160,10 +168,10 @@ class WP_Ability { * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } - * @type bool $public Optional. Whether the ability is intended to be publicly - * available to clients. When true, channel-specific exposure - * flags such as `$show_in_rest` default to true. An explicitly - * set channel flag always takes precedence. + * @type bool $public Optional. Whether the ability is meant to be available + * to clients such as the REST API, MCP, or AI agents. + * Seeds the default for per-channel flags like + * `$show_in_rest`. Defaults to false. * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * Default is the value of `$public` when set, false otherwise. * } @@ -229,10 +237,10 @@ public function __construct( string $name, array $args ) { * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } - * @type bool $public Optional. Whether the ability is intended to be publicly - * available to clients. When true, channel-specific exposure - * flags such as `$show_in_rest` default to true. An explicitly - * set channel flag always takes precedence. + * @type bool $public Optional. Whether the ability is meant to be available + * to clients such as the REST API, MCP, or AI agents. + * Seeds the default for per-channel flags like + * `$show_in_rest`. Defaults to false. * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. * Default is the value of `$public` when set, false otherwise. * } @@ -262,8 +270,9 @@ public function __construct( string $name, array $args ) { * @type bool|null $idempotent If true, calling the ability repeatedly with the same arguments * will have no additional effect on its environment. * } - * @type bool $public Whether the ability is intended to be publicly available - * to clients. Only present when provided during registration. + * @type bool $public Whether the ability is meant to be available to clients + * such as the REST API, MCP, or AI agents. Defaults to + * false. * @type bool $show_in_rest Whether to expose this ability in the REST API. * } * } @@ -348,17 +357,18 @@ protected function prepare_properties( array $args ): array { ) ); + $args['meta']['annotations'] = wp_parse_args( + $args['meta']['annotations'], + static::$default_annotations + ); + /* * Resolve `show_in_rest` from most specific to least specific: an explicit * `show_in_rest` value wins, then the high-level `public` flag seeds the * default, then the built-in default applies. */ $args['meta']['show_in_rest'] = $args['meta']['show_in_rest'] ?? $args['meta']['public'] ?? self::DEFAULT_SHOW_IN_REST; - - $args['meta']['annotations'] = wp_parse_args( - $args['meta']['annotations'], - static::$default_annotations - ); + $args['meta']['public'] = $args['meta']['public'] ?? self::DEFAULT_PUBLIC; return $args; } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php index 5b7bca0b71138..21b417e83a1db 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php @@ -313,7 +313,7 @@ public function get_item_schema(): array { 'additionalProperties' => true, ), 'public' => array( - 'description' => __( 'Whether the ability author intends the ability to be publicly available to clients. Only present when set by the ability author.' ), + 'description' => __( 'Whether the ability is meant to be available to clients such as the REST API, MCP, or AI agents. Defaults to false, but individual channel settings such as show_in_rest can override it.' ), 'type' => 'boolean', ), ), diff --git a/tests/phpunit/tests/abilities-api/wpAbility.php b/tests/phpunit/tests/abilities-api/wpAbility.php index 491d6337c08ea..f613736211f77 100644 --- a/tests/phpunit/tests/abilities-api/wpAbility.php +++ b/tests/phpunit/tests/abilities-api/wpAbility.php @@ -312,17 +312,21 @@ public function test_meta_explicit_show_in_rest_false_wins_over_public_true() { } /** - * Tests that `public` metadata is not added to the stored meta when not provided. + * Tests that `public` metadata defaults to false when not provided. * * @ticket 65568 */ - public function test_meta_public_is_not_defaulted_when_unset() { + public function test_meta_public_defaults_to_false_when_unset() { $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties ); - $this->assertArrayNotHasKey( + $this->assertArrayHasKey( 'public', $ability->get_meta(), - '`public` metadata should only be present when provided during registration.' + '`public` metadata should always be present in the stored meta.' + ); + $this->assertFalse( + $ability->get_meta_item( 'public' ), + '`public` metadata should default to false when not provided.' ); $this->assertFalse( $ability->get_meta_item( 'show_in_rest' ), @@ -330,6 +334,32 @@ public function test_meta_public_is_not_defaulted_when_unset() { ); } + /** + * Tests that a null `public` value is treated as unset. + * + * @ticket 65568 + */ + public function test_meta_public_null_is_treated_as_unset() { + $args = array_merge( + self::$test_ability_properties, + array( + 'meta' => array( + 'public' => null, + ), + ) + ); + $ability = new WP_Ability( self::$test_ability_name, $args ); + + $this->assertFalse( + $ability->get_meta_item( 'public' ), + 'A null `public` value should use the default value of false.' + ); + $this->assertFalse( + $ability->get_meta_item( 'show_in_rest' ), + '`show_in_rest` should use its default value when `public` is null.' + ); + } + /** * Tests that invalid `public` value throws an exception. * diff --git a/tests/phpunit/tests/abilities-api/wpRegisterAbility.php b/tests/phpunit/tests/abilities-api/wpRegisterAbility.php index 133343635cf59..348b1306ba61e 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterAbility.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterAbility.php @@ -192,6 +192,7 @@ public function test_register_valid_ability(): void { array( 'annotations' => $expected_annotations, 'show_in_rest' => true, + 'public' => false, ) ); From ebe0be5582eb0388527e2748fd30210e858ea8ee Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 14 Jul 2026 15:03:37 +0200 Subject: [PATCH 3/3] Abilities API: Favor the `public` meta flag in `wp_register_ability()` docs. Update the examples and guidance in the Abilities API documentation to use the high-level `public` flag instead of `show_in_rest`. Rename the "REST API Integration" section to "Client Exposure", explain that `public` seeds the per-channel defaults, and show how to override a single channel such as `show_in_rest`. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wp-includes/abilities-api.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/abilities-api.php b/src/wp-includes/abilities-api.php index 52d5db5891d08..37cb1a28072f2 100644 --- a/src/wp-includes/abilities-api.php +++ b/src/wp-includes/abilities-api.php @@ -15,7 +15,7 @@ * - Define permission checks and execution callbacks. * - Organize abilities into logical categories. * - Validate inputs and outputs using JSON Schema. - * - Expose abilities through the REST API. + * - Expose abilities to clients such as the REST API. * * ## Working with Abilities * @@ -48,7 +48,7 @@ * 'required' => true, * ), * 'meta' => array( - * 'show_in_rest' => true, + * 'public' => true, * ), * ) * ); @@ -118,10 +118,10 @@ * 'execute_callback' => 'my_plugin_analyze_text', * 'permission_callback' => 'my_plugin_can_analyze_text', * 'meta' => array( - * 'annotations' => array( + * 'annotations' => array( * 'readonly' => true, * ), - * 'show_in_rest' => true, + * 'public' => true, * ), * ) * ); @@ -208,16 +208,24 @@ * return current_user_can( 'edit_posts' ); * } * - * ### REST API Integration + * ### Client Exposure * - * Abilities can be exposed through the REST API by setting `show_in_rest` - * to `true` in the meta configuration: + * Set the high-level `public` flag to make an ability available to clients + * such as the REST API, MCP, or AI agents: * * 'meta' => array( - * 'show_in_rest' => true, + * 'public' => true, * ), * - * This allows abilities to be invoked via HTTP requests to the WordPress REST API. + * The `public` flag seeds the default for each per-channel flag. For the REST + * API it seeds `show_in_rest`, which lets the ability be invoked via HTTP + * requests. Set a per-channel flag directly to override that default. For + * example, keep a public ability out of the REST API: + * + * 'meta' => array( + * 'public' => true, + * 'show_in_rest' => false, + * ), * * @since 6.9.0 *