The WordPress
Abilities API

And how to interact with WooCommerce
using human language.

Marco Almeida  ·  WordPress Faro Meetup  ·  September 3, 2026

Marco Almeida

Chief Executive Meerkat at
Webdados / Naked Cat Plugins

WordPress.org
Twitter / X (I refuse to call it X)
GitHub
LinkedIn
Webdados Naked Cat Plugins
Marco Almeida
Section 1

What is the
Abilities API?

Your plugin can do a lot.
But does WordPress know that?
Does Claude? Does anything outside your code?

1 / Introduction

Timeline

From plugin to core

VersionStatus
WP 6.8Composer package & feature plugin
WP 6.9Merged into core · Three read-only core abilities shipped
WP 7.0JavaScript client (@wordpress/abilities) · Hybrid abilities · WP AI Client
Abilities ExplorerAdmin screen for browsing & testing abilities, available via the AI Experiments plugin, not core
1 / Introduction

Register once.

Every surface discovers it.

ConsumerAvailable since
PHP (direct)WP 6.9
REST APIWP 6.9
WP-CLI wp ability (separate package)WP 6.9+
MCP (via WordPress MCP Adapter)WP 7.0
JavaScript clientWP 7.0
Command PaletteComing
Workflows APIComing
A2A · WebMCP · UTCPComing
Section 2

Why better
than hooks?

What happens when you call do_action with
the wrong arguments?

Nothing. Silently. It just runs.
Hooks are powerful but dumb. They don't validate input. They don't document output. They don't check permissions unless you remember to add that yourself, and we all know how that ends. Half the WordPress plugin vulnerability database says hi.
2 / Technical

The validation chain

One contract, enforced end-to-end

input schema
permission callback
execute callback
output schema
Any failure → clean WP_Error. Your callback never sees bad data.
  • Typed input & output: JSON Schema, validated before your code runs
  • Permission callback: per-ability, centralized, auditable
  • Self-describing: label, description, schemas, all introspectable at runtime
2 / Technical

Annotations

Machine-readable intent

'readonly' => true
Safe to call freely. Generates a GET request. No side effects.
'destructive' => true
May delete or irreversibly change data. Agents ask for confirmation.
'idempotent' => true
Safe to retry with the same input. Same result every time.
These map directly to MCP hints. Agents read them before calling, so your annotations control AI behavior.
2 / Technical

Hooks vs Abilities

Same power. One has a contract.

Hooks Abilities
Discoverable?
Validated input?
Typed output?
Permission model?ManualBuilt-in
Works with AI agents?
Section 3

How to use it

Four functions.
That's the whole API surface you need to learn.

3 / How to use

The functions

Register · Discover · Execute

// Step 1: Register your category // Hook: wp_abilities_api_categories_init wp_register_ability_category() // Step 2: Register your ability // Hook: wp_abilities_api_init wp_register_ability() // Step 3: Discover what's registered wp_get_abilities() wp_get_ability( 'namespace/name' ) // Step 4: Execute from PHP $ability = wp_get_ability( 'namespace/name' ); $result = $ability->execute( [ 'order_id' => 42 ] ); if ( is_wp_error( $result ) ) { // handle error: $result->get_error_message() } else { // use result: $result['tracking_number'] }
3 / How to use

WP-CLI

Test without the LLM getting creative

# Requires WP-CLI 2.13+: wp package install wp-cli/ability-command # Bundled in WP-CLI nightly (next stable: 3.0), just update: $ wp cli update --nightly --allow-root # List all registered abilities $ wp ability list # Inspect a specific ability $ wp ability get your-plugin/your-ability # Run it $ wp ability run your-plugin/your-ability --input='{"order_id":42}' --user=admin
WhatsApp chat with Alain Schlesser
Chat with Alain Schlesser
3 / How to use

Backwards compat guard

One line. Protects you on WP 6.8 and older.

if ( function_exists( 'wp_register_ability' ) ) { // register your abilities }
3 / How to use

What core ships today

Three read-only abilities. The foundation, not the ceiling.

AbilityWhat it returnsCategory
core/get-site-info Site name, URL, description, language, timezone, date/time formats site
core/get-user-info Current user's basic profile: login, display name, email, roles user
core/get-environment-info WordPress version, PHP version, active theme, is multisite site
The goal was to lay the foundation, not ship everything at once. Core keeps the ability count small and deliberate; plugins and WooCommerce fill the gaps.
Coming in 7.1+: core/get-active-theme · core/list-plugins · core/get-site-health · core/get-settings · core/update-settings
Available via PHP and WP-CLI, not MCP, by default. None of these three ship with meta.mcp.public set to true. That's deliberate: registered abilities aren't exposed to MCP by default, so agents can't touch anything until a site owner explicitly opts it in, even read-only core abilities. Flip it with a wp_register_ability_args filter if you want Claude to see them.
Section 4

Abilities API
& the MCP Adapter

"Show me all processing orders
from the last 7 days."

That's it. That's the interface.

4 / MCP Adapter

Install the MCP Adapter

Not on WordPress.org yet, grab it from GitHub

GitHub:
github.com/WordPress/mcp-adapter → Releases → download the plugin zip → Plugins → Add New → Upload → Activate
Composer:
composer require wordpress/mcp-adapter
// No feature flag. No settings screen.
// Activation alone creates a default
// server that exposes every
// public ability automatically.

HTTP: /wp-json/mcp/mcp-adapter-default-server
STDIO: wp mcp-adapter serve
4 / MCP Adapter

Connect Claude Code

Prerequisites

  • No Node.js, no proxy, no npx: Claude Code speaks HTTP to the adapter directly
  • A WordPress user with the right capability for the abilities you'll call (e.g. manage_woocommerce)
  • An Application Password for that account
Create it:
Users → Your Profile → Application Passwords
Name it (e.g. "Claude Code") → Add New Application Password → copy it now, it's shown once.
4 / MCP Adapter

Connect Claude Code

One command. Restart Claude Code. Done.

# Base64-encode "username:application-password" first $ echo -n "marco:xxxx xxxx xxxx xxxx xxxx xxxx" | base64 $ claude mcp add --transport http wordpress \ https://yourstore.com/wp-json/mcp/mcp-adapter-default-server \ --header "Authorization: Basic <base64-string>" # No proxy, no Node: Claude Code talks HTTP straight to the adapter
4 / MCP Adapter

What WooCommerce exposes

Seven canonical abilities out of the box

DomainAbilities
Orders woocommerce/orders-query find orders
woocommerce/order-add-note add order note
woocommerce/order-update-status update order status
Products woocommerce/products-query find products
woocommerce/product-create create product
woocommerce/product-update update product
woocommerce/product-delete delete/trash/restore product
4 / MCP Adapter

Live demo

Natural language. Real data.

  1. "List all processing orders from the last 7 days"
  2. "Which products have stock below 5?"
  3. "What's my total revenue this week, broken down by day?"
  4. "Update product #42 stock to 0"
Section 5

Creating your
own abilities

WooCommerce's built-in abilities are just the start.
Your plugin can join that conversation.

5 / Creating abilities

Step 1: Register a category

Hook: wp_abilities_api_categories_init

add_action( 'wp_abilities_api_categories_init', function() { wp_register_ability_category( 'woo-dpd-portugal', [ 'label' => __( 'DPD Portugal', 'woo-dpd-portugal' ), 'description' => __( 'DPD Portugal shipping abilities.', 'woo-dpd-portugal' ), ] ); } );
5 / Creating abilities

Step 2: Register the ability

Hook: wp_abilities_api_init

add_action( 'wp_abilities_api_init', function() { wp_register_ability( 'woo-dpd-portugal/create-label', [ 'label' => __( 'Create Shipping Label', 'woo-dpd-portugal' ), 'description' => __( 'Creates a DPD shipping label for a given order.', 'woo-dpd-portugal' ), 'category' => 'woo-dpd-portugal', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'order_id' => [ 'type' => 'integer', 'description' => 'The WooCommerce order ID.' ], 'volumes' => [ 'type' => 'integer', 'description' => 'Number of volumes. Defaults to 1.' ], ], 'required' => [ 'order_id' ], ], 'output_schema' => [ 'type' => 'object', 'properties' => [ 'tracking_number' => [ 'type' => 'string', 'description' => 'Carrier tracking number.' ], 'label_url' => [ 'type' => 'string', 'description' => 'URL to download the PDF label.' ], ], 'required' => [ 'tracking_number', 'label_url' ], ], 'execute_callback' => 'dpd_create_label', 'permission_callback' => fn() => current_user_can( 'manage_woocommerce' ), 'meta' => [ 'mcp' => [ 'public' => true, 'type' => 'tool', ], 'annotations' => [ 'readonly' => false, 'destructive' => false, 'idempotent' => false, ], ], ] ); } );
5 / Creating abilities

Step 3: The legacy filter

One filter. Your entire namespace. Visible to Claude Code.

add_filter( 'woocommerce_mcp_include_ability', function( $include, $ability_id ) { if ( str_starts_with( $ability_id, 'woo-dpd-portugal/' ) ) { return true; } return $include; }, 10, 2 );
Deprecated as of WooCommerce 10.9: this filter only scopes the old WooCommerce-specific MCP bridge. WooCommerce now exposes abilities through the standard WordPress MCP Adapter instead, set meta.mcp.public on the ability itself and it's discovered automatically, no filter needed.
Section 6

Live demo

Real plugin. Real courier API.
Mostly real store.

6 / Live demo

One prompt.

Claude orchestrates everything.

💬
[ live demo ]
How cool was that?
Well... it depends.
6 / Live demo

Same abilities. Smarter caller.

Fine for a one-off demo. Wasteful for the exact same job every day.

🎬 Live demo (interactive)

Claude orchestrates one call at a time, doing the counting, category checks, and next-step decisions itself in between
  • woocommerce/orders-queryonce
  • woocommerce/products-querylooped per line item, uncached, to resolve category for the volume rule
  • woo-dpd-portugal/create-shipping-label× order
  • woocommerce/order-add-note× order
  • woocommerce/order-update-status× order
  • webdados-toolbox/send-sms× order
  • woo-dpd-portugal/request-collectonce
  • woo-dpd-portugal/end-of-day-reportonce

⚙️ Daily batch (one call)

One prompt calls one ability, not sixty
  • my-custom-abilities/process-daily-ordersinput: a date
Internally, in PHP, no LLM in the loop, it calls the exact same abilities:
  • orders-queryproducts-query looped per product, cached
  • create-shipping-labelorder-add-noteorder-update-statussend-sms looped server-side
  • request-collectend-of-day-report once, to close the day
Someone still has to write that PHP: the loop, the sequencing, the error handling. That's the developer's job, and it's exactly what turns AI into something that saves you money on daily token usage instead of costing you more.
Section 7

What this changes
for you

You've been writing hooks for years.
This isn't a replacement*. It's an upgrade.
* but it can be

7 / What changes

Register once. Let everything in.

PHP · REST · WP-CLI · MCP · Command Palette · whatever comes next

  • You don't rewire anything. One registration. Every surface.
  • Your plugin becomes discoverable. Composable. Agent-ready.
  • The list of consumers will only grow. Your abilities grow with it, for free.
  • When building abilities for deterministic workflows or analytical data: do the hard work inside the ability. Don't leave calculation or business logic to the AI agent, it's slower, less reliable, and costs tokens.
Note: This is exactly why "register once" matters in practice. When WooCommerce deprecated its own MCP bridge in favor of the shared WordPress MCP Adapter, none of the abilities anyone had registered needed to change, only the transport in front of them did. Earlier versions of this setup (WC 10.3–10.8) used a WooCommerce-specific MCP beta with its own endpoint and feature flag. That's gone now; the Adapter shown in Section 4 is the way in.
Naked Cat Plugins Wapuu

Questions or suggestions?

Thanks for attending.

Slides & resources:
QR code github.com/webdados/
The-WordPress-Abilities-API-talk