Course 1’s ability lesson established that description is what an AI model actually
reads to decide whether and how to call an ability. This lesson is that idea taken all
the way to its practical conclusion: writing descriptions and input schemas well enough
that a model picks the right ability reliably, even when your site has several abilities
that could plausibly apply to the same request. This is prompt engineering aimed at a
model’s tool-selection step rather than at its text generation, and it fails in its own
specific ways.
Course 1’s Abilities API overview, particularly the lesson explaining
wp_register_ability()’s fields. If you haven’t registered a real ability yet, Course
2 covers that hands-on, this lesson assumes you already understand the shape and is
about writing better content for it, not a different mechanism.
Step 1: What a model reads before deciding to call an ability
When an AI client is deciding which ability to invoke for a user’s request, it isn’t
reading your PHP, it’s reading the ability’s label, its description, and the
description on every property inside input_schema. All of that text is the model’s
only window into what an ability does and what it needs, which means every one of those
fields is prompt content, written for a model to make a decision from, not documentation
written for a human skimming a list.
Step 2: The vague-description failure mode
A description like this is common, and quietly unreliable:
// File: my-plugin.php (weak version)
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'my-plugin/posts',
array(
'label' => __( 'Posts', 'my-plugin' ),
'description' => __( 'Does things with posts.', 'my-plugin' ),
'input_schema' => array(
'type' => 'object',
'properties' => array(
'params' => array( 'type' => 'string' ),
),
),
// permission_callback / execute_callback omitted for brevity
)
);
} );
Two problems here compound each other. “Does things with posts” gives a model no signal
for when to reach for this ability versus any other one that also touches posts, and a
single generic params string property gives it no signal for what to actually pass in.
A model faced with this will either avoid the ability entirely, or call it with
guessed, malformed input.
Step 3: The overlapping-abilities failure mode, and a real fix
The sharper version of the same problem shows up once a site has two abilities that both plausibly apply to the same kind of request, one that fetches a single known post, and one that searches across many. Registered with similarly generic descriptions, a model frequently can’t tell them apart and either always picks one, ignoring the other entirely, or picks the wrong one for a given request. Here’s both, rewritten to be genuinely distinct:
// File: my-plugin.php (disambiguated version)
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'my-plugin/get-post-by-id',
array(
'label' => __( 'Get a single post by ID', 'my-plugin' ),
'description' => __(
'Fetches one specific post when you already know its numeric ID. ' .
'Use this only when an exact post ID is known. If you only have a ' .
'title, a topic, or a partial description, use ' .
'my-plugin/search-posts instead.',
'my-plugin'
),
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array(
'type' => 'integer',
'description' => 'The numeric WordPress post ID to fetch.',
),
),
'required' => array( 'post_id' ),
),
// ...permission_callback, execute_callback
)
);
wp_register_ability(
'my-plugin/search-posts',
array(
'label' => __( 'Search posts by keyword', 'my-plugin' ),
'description' => __(
'Searches published posts by keyword or topic when the exact post ' .
'is not already known by ID. Returns a list of matching posts with ' .
'their IDs and titles. Use my-plugin/get-post-by-id afterward if a ' .
'single specific post from the results needs to be fetched in full.',
'my-plugin'
),
'input_schema' => array(
'type' => 'object',
'properties' => array(
'query' => array(
'type' => 'string',
'description' => 'Keyword or phrase to search for in post titles and content.',
),
),
'required' => array( 'query' ),
),
// ...permission_callback, execute_callback
)
);
} );
Each description now names the other ability explicitly and states exactly when to
prefer one over the other, and each input_schema property has its own description
rather than a bare type. Both changes matter, a model choosing between tools weighs the
top-level description most heavily, but a property with no description still forces it
to guess what value belongs there.
Explicitly telling a model “use the other ability instead when X” inside a description is one of the most effective ways to resolve overlap between two abilities that genuinely both need to exist. It costs nothing at runtime and removes an entire class of misrouted calls.
Step 4: Scoping input schemas as tightly as the task allows
Beyond disambiguating descriptions, a schema with specific, narrowly typed properties
gives a model far less room to pass something malformed than one generic string or
object property does. An integer typed post_id with no other option can’t be handed
a title string by mistake, whereas a params string property invites exactly that kind
of confusion, as seen in Step 2.
Test it: verify a model picks the right ability
Register both abilities from Step 3, connect an MCP client from Course 2 or Course 3, and try two distinct requests in conversation:
"Get me post number 482."
"Find posts about staging site setup."
The first should trigger a my-plugin/get-post-by-id call with post_id: 482, the
second a my-plugin/search-posts call with query: "staging site setup". Check your
site’s logs or a temporary error_log() call inside each execute_callback to confirm
which ability actually ran for each request.
Padding a description with more words doesn’t fix vagueness, “Does things with posts, including fetching, searching, updating, creating, and deleting them, in a flexible and powerful way” is still functionally as unhelpful as “does things with posts.” A model needs specific, decision-relevant information: when to use this ability, what it needs, and what distinguishes it from the alternative, not more adjectives.
Recap
The description field and every property description inside input_schema are prompt
content a model reads to decide whether and how to call an ability, not internal
documentation. Vague descriptions get abilities skipped or misused, and overlapping
abilities with similar descriptions get confused for each other. Naming the alternative
ability explicitly inside each description, and scoping input_schema properties as
narrowly and specifically as the task allows, are the two most reliable fixes for both
problems.
Resources & further reading
- wp_register_ability() function reference, developer.wordpress.org
- Abilities API reference, developer.wordpress.org
- WordPress AI Foundations: Understanding Abilities, this track, Course 1