Repository navigation
Conversation
An ability can declare an eligibility_callback at registration. The callback receives an associative array describing the caller's usage context and returns whether the ability applies there. wp_get_abilities() accepts an eligibility_context argument and drops abilities whose callback returns false. The abilities REST collection accepts the same context through an eligibility_context query parameter. Eligibility is consulted only when listing abilities, never on execute, and it is not a security boundary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I'm excited for this! I think this is going to be useful in contexts like limiting MCP tools by Agent user capability, as well as surfacing features to users within a UI. For example, only showing a refund button if the user can use the I've imagined the signature to be something like: /**
* @param WP_User $user a specified user to check for, defaults to current user
* @param array $input optional set of inputs as would be used when calling the ability, in case this changes permissions
*/
public function is_eligible(WP_User $user = null, array $input = null): bool;I could go either way on the order of parameters. Note, that I'm calling it Ideally, this would do a permissions check by default, with the option for a specific callback as you've made in this PR. I don't think we want to default to |
|
Thanks, Jason! I want to make sure we are on the same page before iterating on the design. The way I see it, the existing A few questions:
|
|
The more I think about this, @gziolo , the more I feel like improving We have two distinct scenarios:
I think the most common scenarios is the former, and we should do things like allow for an optional This PR addresses the latter, but at this point it just feels too vague to me. Permission checks are tied to a concrete and well defined capability system. The context check is some kind of callback doing some kind of check against some kind of data. If we used If the main use case here is WebMCP, I think we should consider a different approach for this that's tied to something more concrete within WordPress. |
|
Thanks for thinking this through, @JasonTheAdams. I agree that checking whether a user can use an ability matters more right now. I looked at doing it through A
Proposal: declare required capabilities when registering an abilityThis builds on an idea @galatanovidiu shared in WordPress/abilities-api#62 (comment). He suggested a simple capability-based pre-filter. With it, abilities the user can't access are filtered out before they are shown to AI agents, and their permission callbacks don't have to run. This proposal turns that idea into a concrete API. Abilities can declare the capabilities a user needs to run them: wp_register_ability(
'core/get-site-info',
array(
'label' => __( 'Get Site Information' ),
'description' => __( 'Returns basic information about the site.' ),
'category' => 'site',
'required_capabilities' => array( 'manage_options' ),
'execute_callback' => 'wp_abilities_get_site_info',
)
);Registration
Execution
So Listing
This answers "can the current user use this ability?" when listing abilities, without calling any permission callbacks. Listing, execution and the client all read one list, so they can't get out of sync. This proposal doesn't cover every use case. Abilities whose permission depends on the input will still be listed, and the final check still happens at execution. But it closes an obvious gap. Today we list all abilities, even the ones the current user can't run. |
|
Opened a new ticket for the proposal: https://core.trac.wordpress.org/ticket/66325. PR is ready for review: |
What
This is a draft to explore the design in the open.
An ability can declare an
eligibility_callbackwhen it is registered. The callback receives an array that describes where the caller is. It returns true or false to say if the ability is useful there.wp_get_abilities()accepts a neweligibility_contextargument. Abilities whose callback returns false are left out. The REST collection accepts the same data through aneligibility_contextquery parameter.The first user of this feature is a WebMCP adapter (WordPress/ai#448). It registers tools for each admin screen and each frontend page. A site with hundreds of abilities can then expose only the few that matter on the current page.
Contract
Three things stay separate:
permission_callbackwhen the ability runs.Eligibility is not a security check. The callback runs only when listing abilities. It never runs when an ability executes. It never runs on the single ability REST route. The context comes from the caller and is not verified. Nothing security related may depend on it.
The result without a context is the upper bound. With no context, callbacks are not called and every ability is included. Context keys can only remove abilities from a result. They can never add more. This keeps the MCP adapter's
tools/listcomplete, because it passes no context.An ability without a callback is always included. A callback that returns something that is not a boolean is treated as true.
Core does not define the context keys. Callers and ability authors agree on them. Plugins should prefix their own keys, like
plugin-slug/key. The REST parameter declares no properties, so values arrive as strings. A plugin can declare typed keys with the existingrest_abilities_collection_paramsfilter.New API
eligibility_callbackregistration argument. Must be callable.WP_Ability::is_eligible( array $eligibility_context = array() ): bool, with awp_ability_eligibility_resultfilter. It follows the same pattern as the other lifecycle filters.eligibility_contextargument forwp_get_abilities(). It runs in the same loop, after thecategory,namespace, andmetafilters and beforeitem_include_callback. Thewp_get_abilities_item_includefilter can see it through$args.eligibility_contextobject parameter on thewp-abilities/v1/abilitiesroute. Bracket syntax works:eligibility_context[post_type]=product.Open questions
@ticketannotations once it does.@wordpress/abilitiesand@wordpress/core-abilities) lives in the Gutenberg repository and will follow separately.🤖 Generated with Claude Code