This project builds an ability that takes an already-published post and generates two short, distinct pieces of repurposed copy: a social caption and a newsletter blurb, using the AI Client SDK. It closes out the course by combining nearly everything the earlier projects touched on, one input, multiple tailored outputs, all reviewable before use.
Course 5 for wp_ai_client_prompt(). A published post to test against, this ability
explicitly checks for publish status before generating anything.
Step 1: Register the ability
Input is a post ID. Output is a social caption and a newsletter blurb, kept as two separate fields since they serve different channels with different constraints.
// File: repurposing-tool/repurposing-tool.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'repurposing-tool/generate',
array(
'label' => __( 'Generate social and newsletter copy', 'repurposing-tool' ),
'description' => __( 'Generates a short social media caption and a newsletter blurb from a published post, for repurposing existing content across channels.', 'repurposing-tool' ),
'category' => 'content',
'input_schema' => array(
'type' => 'object',
'properties' => array( 'post_id' => array( 'type' => 'integer' ) ),
'required' => array( 'post_id' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'social_caption' => array( 'type' => 'string' ),
'newsletter_blurb' => array( 'type' => 'string' ),
),
),
'permission_callback' => function ( $input ) {
return current_user_can( 'edit_post', $input['post_id'] );
},
'execute_callback' => 'repurposing_tool_generate',
'meta' => array(
'annotations' => array( 'readonly' => false, 'destructive' => false ),
'mcp' => array( 'public' => true ),
),
)
);
} );
readonly is false here, unlike the SEO Content Optimizer, because this ability
does write generated copy to post meta as its real output, not just as an incidental
log.
Step 2: Require publish status, then generate both pieces
// File: repurposing-tool/repurposing-tool.php
function repurposing_tool_generate( $input ) {
$post = get_post( $input['post_id'] );
if ( ! $post ) {
return new WP_Error( 'not_found', 'Post not found.' );
}
if ( 'publish' !== $post->post_status ) {
return new WP_Error( 'not_published', 'This post must be published before it can be repurposed. Repurposing content that isn\'t public yet doesn\'t make sense.' );
}
$body = wp_strip_all_tags( $post->post_content );
$url = get_permalink( $post->ID );
$social_prompt = "Write a short, engaging social media caption (under 280 characters) for this post, include a hook, no hashtags unless genuinely relevant, don't include the URL, that gets added separately.\n\nTitle: {$post->post_title}\nContent: {$body}";
$social_caption = trim( wp_ai_client_prompt( $social_prompt )->generate_text() );
$newsletter_prompt = "Write a short newsletter blurb (2-3 sentences) introducing this post to email subscribers, conversational tone, end with something like 'read the full post' rather than repeating the URL.\n\nTitle: {$post->post_title}\nContent: {$body}";
$newsletter_blurb = trim( wp_ai_client_prompt( $newsletter_prompt )->generate_text() );
if ( empty( $social_caption ) && empty( $newsletter_blurb ) ) {
return new WP_Error( 'empty_response', 'The AI provider returned no content for either format.' );
}
update_post_meta( $post->ID, '_repurposed_social_caption', sanitize_text_field( $social_caption ) );
update_post_meta( $post->ID, '_repurposed_newsletter_blurb', sanitize_textarea_field( $newsletter_blurb ) );
return array(
'social_caption' => $social_caption,
'newsletter_blurb' => $newsletter_blurb,
);
}
Two separate wp_ai_client_prompt() calls, not one call asked to produce both
formats at once, because a caption and a blurb have different length limits, tones,
and calls to action, one prompt trying to do both tends to produce a compromise that
serves neither format well.
Step 3: How this closes the loop on the whole course
Look back at Lesson 1’s skeleton: an ability as the agent-facing surface, an internal
wp_ai_client_prompt() call as the actual engine, output that lands somewhere
reviewable rather than acting unattended. This project is that same shape one more
time, and like every project before it, the generated copy sits in post meta waiting
for a human (or a separate, explicitly-triggered publishing integration) to actually
post it to social media or send the newsletter. Nothing in this ability posts
anywhere on its own, it stops at generating and storing the copy, same instinct as the
draft-only blog generator in Lesson 3 and the recommendation-only moderation tool in
Lesson 7.
Test it
Run the ability against a real published post:
wp-env run cli wp eval '
$result = wp_get_ability( "repurposing-tool/generate" )->execute( array( "post_id" => 42 ) );
print_r( $result );
'
wp-env run cli wp post meta get 42 _repurposed_social_caption
Confirm social_caption is under 280 characters and reads like something you’d
actually post, and that newsletter_blurb is a distinct 2 to 3 sentences rather than a
copy of the caption with a period changed.
The explicit post_status !== 'publish' check exists because repurposing content
that isn’t live yet can leak a caption or blurb referencing a post the public can’t
actually reach, confusing at best and a spoiler at worst if the post is scheduled but
not yet public. Don’t remove or loosen this check to “test faster”, use a real
published test post instead.
Recap
This ability generates a social caption and a newsletter blurb with two separate,
purpose-specific wp_ai_client_prompt() calls, only after confirming the source post
is actually published, and stores both in post meta for a scheduler or newsletter tool
to pick up later. Like every project in this course, it stops at generating reviewable
content rather than posting or sending anything itself, the same dual-direction,
human-reviewed pattern this course opened with in Lesson 1.
Resources & further reading
- PHP AI Client SDK repository, GitHub
- get_permalink() function reference, developer.wordpress.org
- update_post_meta() function reference, developer.wordpress.org