This project builds an ability that pulls real content statistics, post counts by category and by date, straight from WordPress, and hands those raw numbers to the AI Client SDK to produce a short, plain-language summary a non-technical stakeholder can read in ten seconds.
Course 5 for wp_ai_client_prompt(). Enough published content with varied categories
and dates that the resulting summary reflects something real rather than “1 post total.”
Step 1: Register the ability
Input is an optional date range. Output is both the raw stats (for anyone who wants the real numbers) and a plain-language summary.
// File: analytics-reporter/analytics-reporter.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'analytics-reporter/summarize-content',
array(
'label' => __( 'Summarize content stats', 'analytics-reporter' ),
'description' => __( 'Pulls post counts by status, category, and date range, and returns both the raw numbers and a plain-language summary of publishing activity.', 'analytics-reporter' ),
'category' => 'analytics',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'since_days' => array( 'type' => 'integer' ),
),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'stats' => array( 'type' => 'object' ),
'summary' => array( 'type' => 'string' ),
),
),
'permission_callback' => function () {
return current_user_can( 'edit_others_posts' );
},
'execute_callback' => 'analytics_reporter_summarize',
'meta' => array(
'annotations' => array( 'readonly' => true ),
'mcp' => array( 'public' => true ),
),
)
);
} );
Step 2: Gather real stats with wp_count_posts() and WP_Query
// File: analytics-reporter/analytics-reporter.php
function analytics_reporter_gather_stats( $since_days ) {
$counts = wp_count_posts( 'post' );
$since = date( 'Y-m-d', strtotime( "-{$since_days} days" ) );
$recent_query = new WP_Query( array(
'post_status' => 'publish',
'date_query' => array( array( 'after' => $since ) ),
'posts_per_page' => -1,
'fields' => 'ids',
) );
$categories = get_categories( array( 'hide_empty' => true ) );
$by_category = array();
foreach ( $categories as $category ) {
$by_category[ $category->name ] = $category->count;
}
arsort( $by_category );
return array(
'published_total' => (int) $counts->publish,
'draft_total' => (int) $counts->draft,
'published_recent' => $recent_query->found_posts,
'recent_window_days' => $since_days,
'top_categories' => array_slice( $by_category, 0, 5, true ),
);
}
wp_count_posts() returns an object with a property per post status, publish and
draft counts come straight from it, no manual WP_Query counting required for those.
The recent-activity and per-category numbers do need WP_Query and
get_categories() respectively, since wp_count_posts() alone doesn’t break counts
down by date or taxonomy.
Step 3: Hand the numbers to the AI Client SDK, and only the numbers
// File: analytics-reporter/analytics-reporter.php
function analytics_reporter_summarize( $input ) {
$since_days = ! empty( $input['since_days'] ) ? (int) $input['since_days'] : 30;
$stats = analytics_reporter_gather_stats( $since_days );
$categories_list = '';
foreach ( $stats['top_categories'] as $name => $count ) {
$categories_list .= "- {$name}: {$count} posts\n";
}
$prompt = <<<PROMPT
Write a short, plain-language summary (3-4 sentences) of this site's publishing
activity, for a non-technical stakeholder. Use only the numbers given below, do not
estimate or invent any figures not listed here.
Total published posts: {$stats['published_total']}
Draft posts waiting: {$stats['draft_total']}
Posts published in the last {$since_days} days: {$stats['published_recent']}
Top categories by post count:
{$categories_list}
PROMPT;
$summary = wp_ai_client_prompt( $prompt )->generate_text();
return array(
'stats' => $stats,
'summary' => $summary ?: 'Summary unavailable, raw stats are still included above.',
);
}
The instruction “use only the numbers given below, do not estimate or invent any
figures” matters more than it might look. Models are fluent enough to produce a
plausible-sounding sentence with a made-up number in it if the prompt leaves room for
that, explicitly constraining the model to the supplied figures, and always returning
the raw stats object alongside the prose summary, means anyone reading the output
can verify the narration against the real numbers directly.
Test it
Run the ability and compare the summary against the raw stats it’s built from:
wp-env run cli wp eval '
$result = wp_get_ability( "analytics-reporter/summarize-content" )->execute( array( "since_days" => 30 ) );
print_r( $result );
'
Confirm every number mentioned in summary actually appears in stats, if the
summary references a figure that isn’t in the raw data, that’s a sign the prompt needs
tightening, not a fact you should trust.
It’s tempting to ask the model something like “what percentage of posts are drafts,” and let it compute that in the response. Don’t, language models are not reliable calculators and can produce a plausible but wrong percentage. Compute any derived figures (percentages, averages, growth rates) in PHP before the prompt is built, and only ask the model to narrate numbers you’ve already calculated correctly.
Recap
This ability’s real content stats come entirely from wp_count_posts(),
WP_Query, and get_categories(), deterministic WordPress functions with no AI
involved. The wp_ai_client_prompt() call at the end has exactly one job: turning
those already-correct numbers into readable prose, with the raw stats object always
returned alongside the summary so the narration can be checked against the real data.
Resources & further reading
- wp_count_posts() function reference, developer.wordpress.org
- WP_Query reference, developer.wordpress.org
- PHP AI Client SDK repository, GitHub