J.CV

Using Abilities To Build AI in WordPress (2/2)
SERIES:Creating Abilities API·PART 2 OF 2·

In the first post, I walked through how the Abilities API came to be: the startup years, the Feature API, and the decision to rebuild a true capabilities layer for WordPress from first principles. This post is about what you actually do with it (related to AI).

Practically, there are three ways to use Abilities for Artificial Intelligence in WordPress:

  1. Single-shot actions, where a sparkle button trigger maps cleanly to one ability.
  2. Agentic loops, where an LLM sits in a loop and decides which abilities to call.
  3. Deterministic workflows, where you define the graph and let AI live inside some of the nodes.

Along the way, Abilities ties into the WP/PHP AI Client, the MCP adapter, and – soon – a Workflows API and a visual workflow builder. MCP, WebMCP, UTCP, A2A, Rest API headless setups, all of it: Abilities is the layer those systems can pull from.

Before diving into the patterns, a very quick orientation on what the Abilities API actually gives you.


A quick orientation: what the Abilities API exposes

On the PHP side, the Abilities API gives you:

  • wp_register_ability( $name, $args ) to declare an ability.
  • wp_get_ability( $name ) to retrieve the WP_Ability object.
  • wp_get_abilities() to get all registered abilities.
  • wp_has_ability( $name ) to feature-detect whether something is present.

You register abilities on the wp_abilities_api_init hook:

<?php

add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' );

function my_plugin_register_abilities(): void {
    wp_register_ability(
        'my-plugin/translate-content',
        array(
            'label'             => __( 'Translate content', 'my-plugin' ),
            'description'       => __( 'Translate arbitrary text into a target language.', 'my-plugin' ),
            'category'          => 'content',
            'input_schema'      => array(
                'type'       => 'object',
                'properties' => array(
                    'text'            => array(
                        'type'        => 'string',
                        'description' => 'The text to translate.',
                    ),
                    'target_language' => array(
                        'type'        => 'string',
                        'description' => 'BCP-47 code for the target language, for example `es` or `el`.',
                    ),
                ),
                'required'   => array( 'text', 'target_language' ),
            ),
            'output_schema'     => array(
                'type'       => 'object',
                'properties' => array(
                    'translated_text' => array(
                        'type'        => 'string',
                        'description' => 'The translated text.',
                    ),
                ),
                'required'   => array( 'translated_text' ),
            ),
            'execute_callback'   => 'my_plugin_translate_content_ability',
            'permission_callback'=> 'my_plugin_can_use_translation',
            'meta'               => array(
                'show_in_rest' => true,
            ),
        )
    );
}
Code language: PHP (php)

That one call:

  • Registers a named ability (my-plugin/translate-content) with input/output schemas and callbacks.
  • Makes it discoverable via wp_get_ability() / wp_get_abilities().
  • Automatically exposes it on REST under wp-json/wp-abilities/v1 if meta.show_in_rest is enabled.

The WP_Ability object you get back has methods to inspect its configuration (label, description, schemas, meta) and an execute( $input ) method that validates the input against the schema and runs your callback.

On the JavaScript side, there’s a client package (@wordpress/abilities) that provides functions like executeAbility() to run abilities from the editor. That client currently lives in the Abilities feature plugin and GitHub repo, with the intent to ship it as a Gutenberg package over time (ie; you’ll see this in core with WP Version 7.0).

On the AI side, you pair all of this with either the generic PHP AI Client (wordpress/php-ai-client) or the WordPress AI Client (wordpress/wp-ai-client). Both give you a fluent prompt() API and a consistent way to talk to different model providers.

With that in your head, we can look at the three patterns.


1. Single-shot actions: sparkles backed by abilities

FIG 01.0 // SINGLE-SHOT INTERACTION FLOW UI LAYER Sparkle Btn CLICK JS CLIENT executeAbility( ‘translate’, { text: … } ) POST /run WP_ABILITY Schema Validation Input OK Callback Execution PROMPT AI CLIENT Adapter Layer OpenAI / Anthropic RESULT

Download SVG

The simplest pattern is the one you’ve already been building: sparkle buttons, aka triggers. (As an aside, we really can’t be having everything with sparkles layered on top of it, so let’s figure out an elegant way to do this.)

You have a button somewhere in the UI – maybe on a block toolbar, maybe next to a textarea. When the user clicks it, you take the current value, send it to a model, get something back, and do something – Inject content, modify an interface, etc.

The difference with Abilities is that the “do the thing” part becomes a named ability instead of ad-hoc code buried inside a block, a REST endpoint, or an admin page.

Here’s an example of an ability to translate a string. It can easily be connected to a trigger, be it a button, command bar, action, or backend job. It pulls in text and then responds with a translated string leveraging a LLM in the callback.

<?php

add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' );

/**
 * Register abilities for this plugin.
 */
function my_plugin_register_abilities(): void {

    wp_register_ability(
        'my-plugin/translate-content',
        array(
            'label'       => __( 'Translate content', 'my-plugin' ),
            'description' => __( 'Translate arbitrary text into a target language.', 'my-plugin' ),
            'category'    => 'content',

            'input_schema' => array(
                'type'       => 'object',
                'properties' => array(
                    'text' => array(
                        'type'        => 'string',
                        'description' => 'The text to translate.',
                    ),
                    'target_language' => array(
                        'type'        => 'string',
                        'description' => 'BCP-47 code for the target language, for example `es` or `el`.',
                    ),
                ),
                'required' => array( 'text', 'target_language' ),
            ),

            'output_schema' => array(
                'type'       => 'object',
                'properties' => array(
                    'translated_text' => array(
                        'type'        => 'string',
                        'description' => 'The translated text.',
                    ),
                ),
                'required' => array( 'translated_text' ),
            ),

            // These are implemented later in the post.
            'execute_callback'    => 'my_plugin_translate_content_ability',
            'permission_callback' => 'my_plugin_can_use_translation',

            'meta' => array(
                'show_in_rest' => true,
            ),
        )
    );
}Code language: PHP (php)

Turning “translate this” into a first-class ability

Using the registration example above, the translation ability itself is just a thin wrapper over the AI client:

<?php

use WordPress\AI_Client\AI_Client;

/**
 * Execute callback for the `my-plugin/translate-content` ability.
 *
 * @param array $input Validated input from the ability schema.
 * @return array
 */
function my_plugin_translate_content_ability( array $input ): array {
    $text            = (string) ( $input['text'] ?? '' );
    $target_language = (string) ( $input['target_language'] ?? 'en' );

    // Guard rails: bail if there is nothing to translate.
    if ( '' === trim( $text ) ) {
        return array( 'translated_text' => '' );
    }

    // Ask the configured AI provider to translate the text.
    $translated = AI_Client::prompt(
        sprintf(
            'Translate the following text into %s. Return only the translated text, no commentary.',
            $target_language
        )
    )
        ->using_provider( 'openai' )     // or gemini / anthropic, etc.
        ->generate_text();               // returns a plain string.   

    return array(
        'translated_text' => $translated,
    );
}

/**
 * Simple permission check for the ability.
 */
function my_plugin_can_use_translation(): bool {
    return current_user_can( 'edit_posts' );
}
Code language: HTML / XML (xml)

You’ve now:

  • Given the translation feature a stable name (my-plugin/translate-content).
  • Defined exactly what it expects and returns (schema).
  • Wired it to an AI model using the WordPress AI Client.
  • Wrapped it in capability checks.

Anywhere in PHP, you can now execute the same logic via:

<?php

$ability = wp_get_ability( 'my-plugin/translate-content' );

if ( $ability ) {
    $result = $ability->execute(
        array(
            'text'            => $original,
            'target_language' => 'el',
        )
    );

    $translated = $result['translated_text'] ?? '';
}
Code language: HTML / XML (xml)

And because you set meta.show_in_rest to true, the same ability is automatically exposed at:

  • GET /wp-json/wp-abilities/v1/abilities to list abilities.
  • GET /wp-json/wp-abilities/v1/my-plugin%2Ftranslate-content to inspect it.
  • POST /wp-json/wp-abilities/v1/my-plugin%2Ftranslate-content/run to execute it.

From JavaScript – inside a block, for example – you can call it without writing your own REST wiring, using the Abilities JS client:

import { executeAbility } from '@wordpress/abilities';

async function translateField( text, targetLanguage ) {
    const result = await executeAbility( 'my-plugin/translate-content', {
        text,
        target_language: targetLanguage,
    } );

    return result.translated_text;
}
Code language: JavaScript (javascript)

The UX is still “click sparkle, get translation.” The difference is that the sparkle is now calling a formal ability, not a private helper function.

The payoff shows up later, when you start composing this same ability into agents, workflows, or external automations without touching its internal logic again.


2. Agentic loops: an LLM in a loop over abilities

The second pattern is the standard modern agentic loop: you give a model a toolbox of operations, and it decides which tool to call, with which arguments, and in what order.

FIG 02.0 // AGENTIC LOOP (OODA) LLM CORE State Engine Context Window 1. DISCOVER Fetch Tools wp_get_abilities() 2. PLAN Select Tool JSON Schema 3. EXECUTE Run Ability $ability->run() 4. FEEDBACK Result → Ctx History Update

Download SVG

Major LLM providers – OpenAI, Google, Anthropic – use a similar structure: you pass an array of tool definitions (function declarations), and the model returns function calls that you must execute and feed back.

Abilities map directly onto this structure. Each ability already has a name, a description, an input schema, and an execution callback. The WordPress AI Client exposes function calling through the FunctionDeclaration, FunctionCall, and FunctionResponse DTOs, along with the using_function_declarations() method on the Prompt Builder. The result: abilities become your tools, without any bespoke glue.

To make this concrete, imagine a content-editing agent that can translate text, summarize it, extract entities, apply formatting, and even send the result to an email platform like Mailchimp. Each of those operations becomes an ability:

  • content/translate
  • content/summarize
  • content/extract-entities
  • content/apply-formatting
  • mc4wp/create-draft-campaign (example ability – not part of any existing plugin)

Each ability has an input schema, an output schema, and an execute_callback. You don’t hard-wire them into a single flow; you just declare them. An agent orchestrator can then discover and use them on the fly.

2.1 Converting abilities into function declarations

To expose abilities to an agent, convert them into FunctionDeclaration objects. The WordPress AI Client uses these to describe callable functions to the model.

<?php

use WordPress\AiClient\Tools\DTO\FunctionDeclaration;

/**
 * Build function declarations from abilities.
 *
 * @param WP_Ability[] $abilities Array of ability objects.
 * @return FunctionDeclaration[] Array of function declarations.
 */
function my_plugin_build_declarations_from_abilities( array $abilities ): array {
    $declarations = array();

    foreach ( $abilities as $ability ) {
        $declarations[] = new FunctionDeclaration(
            my_plugin_tool_name_from_ability_name( $ability->get_name() ),
            $ability->get_description(),
            $ability->get_input_schema()
        );
    }

    return $declarations;
}

/**
 * Safe reversible mapping for tool names.
 *
 * Providers impose constraints on tool names (e.g., Anthropic requires
 * ^[a-zA-Z0-9_-]{1,64}$), so we map ability names to safe tool names.
 */
function my_plugin_tool_name_from_ability_name( string $ability_name ): string {
    return strtr( $ability_name, array( '/' => '__' ) );
}

function my_plugin_ability_name_from_tool_name( string $tool_name ): string {
    return strtr( $tool_name, array( '__' => '/' ) );
}Code language: PHP (php)

Abilities are not “AI tools” by themselves. They are WordPress-native capability definitions. The agent layer works by projecting abilities into the model’s expected tool format – taking the ability name, description, and input schema, and turning those into function declarations. Internally, everything still executes through wp_get_ability()->execute().

Note: In the near future (thanks to Jason!), the WP AI Client will include an automatic converter to transform WordPress Abilities into function declarations. For now, we instantiate the DTOs directly from the underlying PHP AI Client, while the WP AI Client wrapper proxies the actual API calls.

2.2 A single agent step using function calling

One pass through the agent loop works like this:

  1. Build a FunctionDeclaration array from the abilities.
  2. Send the conversation + declarations to the model through the WordPress AI Client.
  3. If the model returns function calls, map each back to an ability and execute it.
  4. Feed the results back as FunctionResponse objects.
  5. Loop until no more function calls appear.
<?php

use WordPress\AI_Client\AI_Client;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\FunctionResponse;

/**
 * Run a single agent step using function calling.
 *
 * @param Message[]    $messages   The conversation history.
 * @param WP_Ability[] $abilities  The available abilities.
 * @return array{messages: Message[], final: Message|null}
 */
function my_plugin_run_agent_step( array $messages, array $abilities ): array {
    // 1. Convert abilities into function declarations.
    $declarations = my_plugin_build_declarations_from_abilities( $abilities );

    // 2. Send conversation + declarations to the model.
    $result = AI_Client::prompt( $messages )
        ->using_function_declarations( ...$declarations )
        ->generate_text_result();

    // 3. Extract function calls from the result.
    $function_calls = my_plugin_extract_function_calls( $result );

    if ( empty( $function_calls ) ) {
        // The model returned a normal text response.
        $final_message = my_plugin_result_to_message( $result );
        $messages[]    = $final_message;

        return array(
            'messages' => $messages,
            'final'    => $final_message,
        );
    }

    // 4. Execute each requested function via Abilities.
    $responses = array();

    foreach ( $function_calls as $call ) {
        $tool_name   = $call->getName();
        $args        = $call->getArgs();
        $ability_key = my_plugin_ability_name_from_tool_name( $tool_name );

        $ability = wp_get_ability( $ability_key );

        if ( ! $ability ) {
            // Unknown ability—return an error response.
            $responses[] = new FunctionResponse(
                $tool_name,
                array(
                    'error'   => 'Unknown ability',
                    'ability' => $ability_key,
                )
            );
            continue;
        }

        // Execute the ability (input is validated automatically).
        $output      = $ability->execute( $args );
        $responses[] = new FunctionResponse( $tool_name, $output );
    }

    // 5. Build the next prompt with function responses.
    $builder = AI_Client::prompt( $messages )
        ->using_function_declarations( ...$declarations );

    foreach ( $responses as $response ) {
        $builder = $builder->with_function_response( $response );
    }

    // Make another call to get the model's next response.
    $next_result      = $builder->generate_text_result();
    $next_calls       = my_plugin_extract_function_calls( $next_result );

    if ( empty( $next_calls ) ) {
        // Model produced a final text response.
        $final_message = my_plugin_result_to_message( $next_result );
        $messages[]    = $final_message;

        return array(
            'messages' => $messages,
            'final'    => $final_message,
        );
    }

    // More function calls—return without final to continue the loop.
    return array(
        'messages' => $messages,
        'final'    => null,
    );
}

/**
 * Extract function calls from a GenerativeAiResult.
 *
 * @param \WordPress\AiClient\Results\DTO\GenerativeAiResult $result
 * @return \WordPress\AiClient\Tools\DTO\FunctionCall[]
 */
function my_plugin_extract_function_calls( $result ): array {
    $function_calls = array();

    foreach ( $result->getCandidates() as $candidate ) {
        foreach ( $candidate->getMessage()->getParts() as $part ) {
            $call = $part->getFunctionCall();
            if ( $call !== null ) {
                $function_calls[] = $call;
            }
        }
    }

    return $function_calls;
}

/**
 * Convert a GenerativeAiResult to a Message for conversation history.
 *
 * @param \WordPress\AiClient\Results\DTO\GenerativeAiResult $result
 * @return Message
 */
function my_plugin_result_to_message( $result ): Message {
    $candidate = $result->getCandidates()[0] ?? null;

    if ( $candidate ) {
        return $candidate->getMessage();
    }

    // Fallback: create a simple text message.
    return new Message(
        MessageRoleEnum::model(),
        array( new MessagePart( $result->toText() ) )
    );
}Code language: PHP (php)

2.3 Looping until completion

You can now wrap this in a loop and continue until the model produces a final answer (i.e., a step with no function calls) or you hit a step limit.

<?php

/**
 * Run the agent loop until the model produces a final answer.
 *
 * @param Message[]    $messages   Initial conversation.
 * @param WP_Ability[] $abilities  Available abilities.
 * @param int          $max_steps  Maximum iterations to prevent runaway loops.
 * @return array{messages: Message[], final: Message|null}
 */
function my_plugin_run_agent_until_done( array $messages, array $abilities, int $max_steps = 8 ): array {
    for ( $step = 0; $step < $max_steps; $step++ ) {
        $state    = my_plugin_run_agent_step( $messages, $abilities );
        $messages = $state['messages'];

        if ( $state['final'] !== null ) {
            return $state;
        }
    }

    // Step limit reached—return an error message.
    $timeout_message = new Message(
        MessageRoleEnum::model(),
        array(
            new MessagePart(
                'I was not able to complete this task within the configured step limit.'
            ),
        )
    );
    $messages[] = $timeout_message;

    return array(
        'messages' => $messages,
        'final'    => $timeout_message,
    );
}Code language: PHP (php)

This pattern gives you a full modern agent over WordPress: abilities are transformed into function declarations, the model chooses which one to use, and execution continues until the task is done.

Because everything routes through the Abilities API, these same primitives work for sparkles, workflows, MCP clients, and external orchestrators.

2.4 A note on the API surface

The WordPress AI Client provides the core building blocks:

  • FunctionDeclaration — describes a callable function to the model
  • FunctionCall — represents the model’s request to invoke a function
  • FunctionResponse — contains the result of executing a function
  • Prompt_Builder::using_function_declarations() — attaches declarations to a prompt
  • Prompt_Builder::with_function_response() — adds a function result to the conversation
  • MessagePart::getFunctionCall() — extracts a function call from a response part

For production use, you’ll also want to consider:

  • Rate limiting – cap the number of ability executions per request
  • Cost awareness – track token usage across the loop
  • Timeout handling – set maximum execution time for the entire agent run
  • Observability – log each step for debugging and auditing (I use Langfuse, personally, and at Automattic)
  • Sandboxing – restrict which abilities an agent can access based on context

3. Deterministic workflows: chaining abilities on rails

The third pattern is where workflows come in.

Agentic loops are great when you want flexibility: “here’s the toolbox, figure out how to use it.” Sometimes, though, you want rails:

  • Always do A, then B, then C.
  • Always pass the output of one step as the input to the next.
  • Always ask the user for this one piece of information in between.

That’s what workflows are for.

FIG 03.0 // DETERMINISTIC WORKFLOW PIPELINE TRIGGER Incoming Webhook URL STEP 1 content/fetch Scraper HTML STEP 2 ai/classify Tagger Context STEP 3 (AI) ai/summarize prompt() Summary END wp/save-post Draft

Download SVG

The Core AI and Gutenberg teams have been thinking about Workflows in almost the same terms as Blocks and Patterns: Abilities are the primitives, workflows are structured compositions of those primitives.

In the current code you can already treat abilities as workflow steps by hand. The upcoming Workflows API and workflow builder will formalize that pattern.

Rolling your own minimal workflow runner today

You don’t have to wait for the full Workflows API to start thinking in these terms. At a very basic level, a workflow is just “an array of steps, where each step points at an ability and describes how to map inputs and outputs.”

Something like:

<?php

$workflow = array(
    array(
        'ability' => 'content/classify',
        'map'     => function ( $previous_output, $initial_input ) {
            return array(
                'text' => $initial_input['text'] ?? '',
            );
        },
    ),
    array(
        'ability' => 'content/summarize',
        'map'     => function ( $previous_output, $initial_input ) {
            return array(
                'text'   => $previous_output['normalized'] ?? $initial_input['text'] ?? '',
                'length' => 'short',
            );
        },
    ),
    array(
        'ability' => 'content/save-draft',
        'map'     => function ( $previous_output, $initial_input ) {
            return array(
                'title'   => $initial_input['title'] ?? '',
                'content' => $previous_output['summary'] ?? '',
            );
        },
    ),
);
Code language: HTML / XML (xml)

You can run that with a very simple executor:

<?php

/**
 * Execute a deterministic workflow made of abilities.
 *
 * @param array $workflow      Array of workflow steps.
 * @param array $initial_input Initial input for the first step.
 * @return array               Final step output.
 */
function my_plugin_run_workflow( array $workflow, array $initial_input ): array {
    $previous_output = array();

    foreach ( $workflow as $step ) {
        $ability_name = $step['ability'];
        $map          = $step['map'];

        $ability = wp_get_ability( $ability_name );

        if ( ! $ability ) {
            throw new RuntimeException( sprintf( 'Unknown ability in workflow: %s', $ability_name ) );
        }

        $input   = $map( $previous_output, $initial_input );
        $output  = $ability->execute( $input );

        $previous_output = is_array( $output ) ? $output : array( 'value' => $output );
    }

    return $previous_output;
}
Code language: HTML / XML (xml)

This is deliberately naive. A real implementation would:

  • Validate that the workflow is well-formed.
  • Check that input/output schemas are compatible step to step.
  • Handle errors, retries, logging, and timeouts.
  • Potentially allow certain steps to be agentic abilities (see below).

But even in this simple form, you get:

  • A deterministic pipeline built out of abilities.
  • The ability to reuse the same abilities in single-shot features and agents.
  • A clear place to add a UI later (a workflow editor) without rethinking the runtime.

Mixing deterministic and agentic

One of the most interesting patterns is to embed an agent inside a deterministic workflow.

For example:

  1. Step 1: content/classify (static or simple model call).
  2. Step 2: content/agentic-rewrite (an ability whose execute_callback runs the agent loop from the previous section).
  3. Step 3: content/apply-moderation-rules (static).
  4. Step 4: content/save-draft (static).

From the workflow’s point of view, this is still A → B → C → D. The only difference is that step B is doing more work inside: it may be calling content/translate, content/summarize, content/apply-formatting, maybe even mailchimp/create-draft-campaign, depending on how you configure it.

That’s exactly how the Workflows API is being sketched at the conceptual level: workflows are chains of abilities and “controls” (UI steps) with a shape that looks a lot like an ability’s own signature – name, description, input and output.

The workflow builder/editor will put a visual interface on top of exactly this idea.


What’s next: Workflows API and a workflow builder on top of Abilities

There’s an open issue in Gutenberg that lays out how Abilities and Workflows fit together, along with a Workflow Editor that sits on top of both. The high-level shape looks like this:

  • Abilities extend the existing Commands work by adding structured descriptions and input/output schemas.
  • Workflows are sequences of abilities (and optional UI controls) that can be triggered from the command palette, from other parts of the UI, or from external systems.
  • A Workflow Editor lets users wire these steps together visually, using block editor components to represent abilities as the basic building block.

If you squint, it’s the same relationship as blocks and patterns:

  • A block is a unit you can place on the canvas.
  • A pattern is a reusable composition of blocks.

Similarly:

  • An ability is a unit you can execute.
  • A workflow is a reusable composition of abilities.

The Workflows API will make workflows first-class objects in WordPress, with names, descriptions, input and output schemas, and execution semantics very similar to an ability. Once that lands, it’s not hard to imagine:

  • Workflows being exposed through Abilities themselves (e.g., a workflow/run ability that lists and executes workflows).
  • MCP adapter exposing workflows as MCP tools or resources alongside individual abilities.
  • Agents being able to choose between calling a single ability directly or deferring to an entire workflow when the job matches something you’ve already codified.

And on the UI side, a workflow builder that lets you:

  • Drag abilities onto a canvas.
  • Connect outputs to inputs, with JSON schemas helping the editor know what’s compatible.
  • Insert form controls where user input is required.
  • Save the result as a reusable workflow that shows up in the command palette, admin menus, or custom plugin UIs.

In other words: Abilities give WordPress a language for “things the system can do.” Workflows will turn that into sentences.


Putting it together

If the first post was about why the Abilities API exists at all, this one is about what you can actually build with it.

In practice, the patterns are straightforward:

  • For single-shot AI actions, you register a small ability, wire its execute_callback to the WordPress AI Client, and call it from PHP, JavaScript, REST, MCP, or anything else that can hit the registry.
    • A translation sparkle button is a single-shot action.
  • For agentic loops, you treat Abilities as tools. You let a model plan which ability to call next, and you execute those calls through the same API, either locally or via MCP/WebMCP/UTCP/A2A.
    • A content-editor agent might translate, summarize, and extract entities in an arbitrary order.
  • For deterministic workflows, you chain abilities together on rails, and, over time, let the Workflows API and workflow builder formalize that into first-class workflows the entire WordPress UI can see and reuse.
    • A publishing workflow might classify → summarize → apply moderation rules → save, in a fixed rail sequence.

Everything else – MCP servers built with the MCP adapter, WebMCP frontends, FastAPI-style backends that talk to WordPress over HTTP – is just different shapes of the same story:

WordPress finally has a native, structured way to say “here is what this site can do.” Abilities are that layer. The PHP AI Client, the WordPress AI Client, MCP, WebMCP, UTCP, A2A, and the upcoming Workflows API are all just different consumers of it.

If you start by registering honest abilities for what your plugin or site can do, you’ve already done the hard part. The rest; sparkle buttons, agents, workflows, and external AI tools is just composition.

One response to “Using Abilities To Build AI in WordPress (2/2)”

  1. James W. LePage Avatar

    A quick note on the API surface in these examples: you’ll notice a mix of WordPress\AI_Client (snake_case) for the client and builder methods alongside WordPress\AiClient (camelCase) for DTOs like Message and FunctionDeclaration. This is currently by design, as the WP AI Client serves as a lightweight, WordPress-native wrapper that proxies logic while strictly reusing the robust data structures from the underlying PHP AI Client SDK. As the WordPress package matures, we expect to introduce native wrappers for these objects as well, eventually unifying the entire developer experience under a single, consistent WordPress-style namespace. This was published just a few days following WP AI Client API v0.1.0’s release!

Leave a Reply

Your email address will not be published. Required fields are marked *

j.notes

Discover more from J.CV

Subscribe now to keep reading and get access to the full archive.

Continue reading