WordPress Abilities API
The WordPress Abilities API, from registration to an MCP call.
The WordPress Abilities API is the core registry, added in WordPress 6.9, where plugins declare what they can do as named abilities with an input schema, an output schema and a permission check. This guide registers one ability, calls it from PHP and over REST, and diagnoses four registration or exposure failures. The recorded outputs below belong to the historical WordPress 7.1.2 run on 29 September 2026. The registration example now allowlists post and page and checks the selected type's edit capability; that corrected example has not been rerun, and an Author should not inherit page access from edit_posts.
WordPress 6.9+, Historical 7.1.2 baseline, 29 September 2026
$ wp eval 'print_r( wp_get_ability( "demo/count-drafts" )
->execute( array( "post_type" => "page" ) ) );' --user=1
Array
(
[post_type] => page
[drafts] => 9
)The parts
What a WordPress ability is made of
One call to wp_register_ability() with a name and an array. The handbook at developer.wordpress.org documents each argument; this is what each one does in practice.
| Name | A namespaced string such as demo/count-drafts. It is how every surface, PHP, REST, JavaScript and MCP, refers to the ability. |
|---|---|
| Label and description | What a person, or a model choosing a tool, reads. The description is the part that decides whether an agent picks the right ability. |
| Category | Registered first, on its own hook. An ability that names a category nobody registered is rejected. |
| Input and output schema | JSON Schema. WordPress validates the input before the callback runs, and since 7.1 the REST layer coerces query-string values to the declared types. |
| Permission callback | Runs before execution with the current user. Return false and the call fails, whichever surface it came from. |
| Execute callback | The work itself, returning data that matches the output schema, or a WP_Error. |
| Meta | Annotations (readonly, destructive, idempotent), show_in_rest for the REST routes, and mcp.public or public for MCP servers built on the Adapter. |
Worked example, 29 September 2026
Register one read-only ability, then call it three ways
A must-use plugin on a WordPress 7.1.2 test site with GoBuildWP active. The ability counts drafts of posts or pages, needs the edit capability of the type asked for, and is marked read-only, visible to REST and public to MCP.
<?php
// wp-content/mu-plugins/count-drafts.php
add_action( 'wp_abilities_api_categories_init', function () {
wp_register_ability_category( 'demo-site', array(
'label' => 'Demo site facts',
'description' => 'Read-only facts about this site.',
) );
} );
add_action( 'wp_abilities_api_init', function () {
wp_register_ability( 'demo/count-drafts', array(
'label' => 'Count drafts',
'description' => 'Counts draft posts of one post type.',
'category' => 'demo-site',
'input_schema' => array(
'type' => 'object',
'properties' => array( 'post_type' => array( 'type' => 'string', 'enum' => array( 'post', 'page' ), 'default' => 'post' ) ),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'post_type' => array( 'type' => 'string' ),
'drafts' => array( 'type' => 'integer' ),
),
),
'execute_callback' => function ( $input ) {
$type = sanitize_key( $input['post_type'] ?? 'post' );
return array( 'post_type' => $type, 'drafts' => (int) wp_count_posts( $type )->draft );
},
'permission_callback' => function ( $input ) {
$type = sanitize_key( $input['post_type'] ?? 'post' );
if ( ! in_array( $type, array( 'post', 'page' ), true ) ) {
return false;
}
$object = get_post_type_object( $type );
return $object && current_user_can( $object->cap->edit_posts );
},
'meta' => array(
'show_in_rest' => true,
'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ),
'mcp' => array( 'public' => true ),
),
) );
} );# Read the definition
GET /wp-json/wp-abilities/v1/abilities/demo/count-drafts
→ "meta": { "annotations": { "readonly": true, … },
"show_in_rest": true, "mcp": { "public": true },
"public": false }
→ "targetHints": { "allow": ["GET"] }
# Run it: a read-only ability takes GET, input in the query string
GET /wp-json/wp-abilities/v1/abilities/demo/count-drafts/run?input[post_type]=page
→ {"post_type":"page","drafts":9}
# The same call as POST
POST …/demo/count-drafts/run { "input": { "post_type": "page" } }
→ 405 {"code":"rest_ability_invalid_method",
"message":"Read-only abilities require GET method."}From PHP, wp_get_ability() returns the ability and execute() runs the same permission check and schema validation as the REST route. From an MCP client, the ability is one more tool: the Adapter's default server lists it because mcp.public is true, and a product server lists it only if its own rules allow.
Run over HTTP against the local test stack. The Author account and its Application Password existed only for this run and were deleted afterwards. These REST outputs were recorded when the example checked only edit_posts. With the corrected check shown here, an Author asking for page drafts should be refused, because Authors cannot edit pages; that has not been rerun.
Registration and exposure
Four Abilities API failures to diagnose
Some registration failures only emit debug notices; REST failures can return a structured error. Each case below was reproduced on the historical test site or read from core source on the same day.
| Failure | What you see | Why | How to catch it |
|---|---|---|---|
| Registered at the wrong moment | The ability simply does not exist. wp_has_ability() returns false and nothing is shown unless WP_DEBUG is on. | wp_register_ability() called while a plugin file loads, or on init. Core refuses any call made outside the wp_abilities_api_init action, returns null and raises only a _doing_it_wrong notice. | Register inside add_action( 'wp_abilities_api_init', … ). In our run, a file-scope registration of demo/too-early left wp_has_ability( 'demo/too-early' ) false while the hooked ability beside it worked. |
| Asked for too early | Code that looks up an ability gets null, or finds that a module's helper functions are not loaded yet. | The registry is built on first use, and it is not built at all before init: WP_Abilities_Registry::get_instance() returns null until then, and wp_abilities_api_init fires the first time something asks for the registry. | Look abilities up from init onwards, through wp_get_ability() or wp_has_ability(), rather than from code that runs as the plugin loads. |
| Registered, but invisible | The ability runs in PHP but the REST routes return 404, or an MCP client never lists it. | REST exposure needs meta.show_in_rest. The MCP Adapter keeps every ability private until meta.public or meta.mcp.public is true, and meta.mcp.public false hides it from MCP even when public is true. | Read the ability back from /wp-json/wp-abilities/v1/abilities/<name> and look at meta. Ours came back with public: false added and mcp.public: true, so the Adapter would list it. |
| Right ability, wrong HTTP method | A REST call answers 405 with rest_ability_invalid_method. | The run route follows the annotations: read-only abilities use GET, regular writes use POST, and destructive abilities use DELETE. Inspect the definition's targetHints for the allowed method and input shape. | The definition's targetHints lists the allowed method. Our POST to a read-only ability returned "Read-only abilities require GET method." |
From ability to AI client
How abilities reach an MCP client
The Abilities API does not speak MCP itself. A server translates, and the server decides which abilities a client sees.
The MCP Adapter's default server
Serves
MCP Adapter vs GoBuildWP/wp-json/mcp/mcp-adapter-default-serverand exposes public abilities through three meta-tools:mcp-adapter-discover-abilities,mcp-adapter-get-ability-infoandmcp-adapter-execute-ability. Sign-in is WordPress authentication, usually an Application Password.A product server such as GoBuildWP
GoBuildWP registers its own abilities on
How WordPress MCP workswp_abilities_api_initat priority 20 and serves them from/wp-json/mcp/wppilot. Before listing or running one it also applies the safety profile, per-ability switches, confirmation for destructive calls and rate limits, and records supported writes for undo.Your own abilities, through either
An ability you register with
GoBuildWP ability reference WordPress MCP for developersmcp.publicis picked up by the Adapter as it is. GoBuildWP lists abilities other plugins register too, grouped under their own module on its Abilities screen, where each can be switched off.
Checked 29 September 2026
Plugins that register their own abilities
Vendor-native abilities are how most plugins will reach AI clients. These are the ones we could confirm from each vendor's own page.
| Plugin | What it registers | Status |
|---|---|---|
| WooCommerce | Product and order abilities, 7 in the developer docs, served through the MCP Adapter | Developer preview behind a feature flag |
| Bricks | Builder abilities for MCP clients, plus an optional skills package | Experimental since Bricks 2.4 |
| Yoast SEO | 5 abilities: three analysis scores, and reading and updating a post's SEO data | Shipped |
| Ninja Forms | 29 permission-checked abilities for forms, submissions and settings; the 3 destructive ones stay off until an admin opts in | Since 3.14.0 |
| Gravity Forms | Forms, entries and feeds as abilities behind its own MCP server, signed in with an Application Password | Since Gravity Forms 3.1 |
| All in One SEO | Abilities API support and a built-in MCP server to read, audit and update SEO data | Shipped |
| GoBuildWP | 232 abilities in Free and 1,148 more in Pro, 1,380 in all, for core WordPress, builders, stores, forms, SEO and caches | Shipped |
- REST namespace
wp-abilities/v1- Register on
wp_abilities_api_init- Categories on
wp_abilities_api_categories_init- Audit hook (7.1)
wp_ability_invoked
Which WordPress version has the Abilities API?
WordPress 6.9 and later; the API is in core, so no separate plugin is needed. WordPress 7.1 added input and output validation filters, the wp_ability_invoked action and typed coercion of REST input. GoBuildWP requires WordPress 6.9+ and PHP 8.0+.
Is the Abilities API only for AI?
No. An ability is a typed, permission-checked operation that PHP code, the REST API and the JavaScript client can call as well as an MCP server. AI clients are the most visible consumer because the schemas tell a model exactly what it may send.
How is the Abilities API different from the REST API?
The REST API is a set of resources and routes you design one by one. An ability is a named operation with a schema and a permission check, registered once and discoverable in one list. Core serves abilities that opt into meta.show_in_rest through the shared wp-abilities/v1 routes, so a client does not have to learn each plugin's endpoints.
Do abilities respect user capabilities?
Only through the permission callback you write. WordPress runs it with the current user before every execution, on every surface. An MCP server adds its own layer on top: GoBuildWP, for example, also checks the active safety profile and the per-ability switches before a call runs.
Do I need the MCP Adapter to use abilities from an AI client?
You need some MCP server. The Adapter is the WordPress project's generic one: its default server exposes public abilities through three meta-tools to discover, inspect and execute. Product plugins such as GoBuildWP run their own server on the same abilities with their own policy, logging and undo.
Put your abilities behind a policy.
Install GoBuildWP Free, connect a client in Read Only, and see your own abilities listed next to GoBuildWP's, each one switchable on the site.