跳到正文
原文
WordPress 开发者博客· Jonathan Bossenger·· 2026-07-31编辑评分54

使用 Abilities API、AI Client 与 MCP Adapter 构建 Photo to Post 插件

Build your first AI-Powered WordPress plugin

编辑导读

WordPress 开发者博客通过 Photo to Post 插件,逐步讲解如何组合 Abilities API、AI Client 和 MCP Adapter。

WordPress 最近发布的两个主要版本都为希望在 WordPress 环境中探索 AI 的开发者带来了重要的新功能:

  • Abilities API——一种标准化的功能单元注册方式,使任何组件(REST、区块编辑器、其他开发者或 AI 智能体)都能发现并运行它。
  • WordPress AI Client——一个与提供商无关的 PHP 库,用于与任何 AI 模型交互,因此你的代码无需关心当前可用的 AI 提供商。
  • MCP Adapter——将 Abilities 暴露为 MCP 工具,使 AI 智能体(桌面端或基于云端)可以使用它们。

到目前为止,你可能已经阅读过关于如何单独使用其中每项技术的多篇教程。我们甚至在 WordPress 开发者博客上逐一介绍过它们:

  • 介绍 WordPress Abilities API
  • 从 Abilities 到 AI 智能体:介绍 WordPress MCP Adapter
  • 如何使用 WordPress AI Client 构建图像生成插件

事实是,尽管单独探索每项技术都很有趣,但只有将它们组合起来,魔力才会真正发生。

如果你一直在关注 Core AI 项目的落地,并想知道这些组成部分如何在一个真实的插件中协同运作,那么这篇文章正适合你。我们将一步步共同构建一个插件,重点介绍如何实现这三个 AI 构建模块。

如果这是你第一次接触其中任何概念,我们建议你先回头阅读上面链接的三篇文章,然后再回到这里。

目录

  1. 我们要构建什么
  2. 开始之前
  3. 将你的站点连接到 AI 提供商
  4. 构建插件功能
  5. 第 1 步:注册 Abilities
  6. 第 2 步:接入 Abilities
  7. 第 3 步:通过 REST 检查你的工作
  8. 第 4 步:描述图像(你的首次 AI 调用)
  9. 第 5 步:生成文章文案
  10. 第 6 步:组合 Abilities(精彩部分)
  11. Step 7: Update the Admin Interface
    1. 一个实用调整:超时
  12. 第 8 步:交给 AI 智能体(MCP)
  13. 总结

本教程的内容也曾在 WordCamp Europe 2026 的一场工作坊上进行展示。如果你更习惯通过视频学习,可以在 WordPress.tv 上观看录像。

我们要构建什么

这个插件名为 Photo to Post,理念很简单。你向它提供一张图片的 URL,插件会:

  1. 使用 AI 视觉模型查看图片并生成一段文字描述。
  2. 根据该图片描述创建一篇草稿文章——包括标题和正文。
  3. 更新这篇草稿文章,将原始图片设置为文章特色图片。

不过,有意思的并不是它做什么,而是它如何构建。在底层,Photo to Post 由三个可组合的 WordPress Abilities 组成,它们会调用 WordPress AI Client:

Ability作用AI 能力
describe-image图片 URL → 文字描述视觉(图片输入)
generate-post-from-description描述 → 文章标题 + 正文文本生成
create-post-from-photo协调上述两项能力,然后创建文章这项能力不使用 AI,但会协调其他能力

第三项能力是能力组合的一个示例——一项能力通过 WP_Ability::execute() 调用其他能力。与在 WordPress 中构建功能的传统方法相比,这是 Abilities API 更令人兴奋的特性之一。

开始之前

为了运行本教程的代码,你的本地计算机需要具备以下条件:

  • 一个运行 WordPress 7.0 或更高版本且使用 PHP 8.1 或更高版本的本地 WordPress 环境
  • 已安装最新版本的 Composer 和 Node.js
  • 支持视觉模型的 AI 提供商 API 凭据(稍后会详细介绍)

为了让操作更简单(也让本教程更短),你将基于这里提供的入门插件进行开发:https://github.com/wptrainingteam/wp-ai-workshop-demo/releases。该插件已经包含功能集的基础框架,因此你只需构建 AI 功能即可。

下载最新版本的插件(1.1.1),在你的本地 WordPress 开发环境中安装并启用它。

启用后,插件会在工具下新增一个名为WP AI Workshop Demo的管理页面,用于显示管理“照片转文章”流程的表单。

WP AI Workshop Demo settings page

它使用 WordPress DataForm 包呈现表单,但目前未提供任何功能。

由于我们将构建本教程的功能,因此现在安装 composer 和 npm 依赖会很有帮助。在插件目录中运行:

composer install
npm install

打开主插件文件 wp-ai-workshop-demo.php,你会看到几处标记为 TODO 的注释,例如第 29 行的这一处:

// TODO: Register the ability category and the three Photo to Post ability hooks.

这表示插件中构建功能的不同区域。每当本教程要求你在文件中添加代码时,请查找要替换的特定 TODO。

将你的网站连接到 AI 提供商

要让你 WordPress 网站通过 AI Client 使用生成式 AI,需要将其连接到 AI 提供商。在此案例中,读取照片、生成照片描述以及生成文章内容的流程都使用 AI Client。

从 WordPress 7.0 开始,可以通过新的“连接器”页面完成此操作,该页面位于“设置”菜单中。借助 Connectors API,“连接器”页面为 WordPress 网站管理员提供了一个统一界面,用于存储外部 API 密钥。

WordPress Connectors page

此前,任何为 WordPress 添加外部连接的插件,都需要为网站管理员提供一个存储 API 密钥的位置,以启用连接。比较常见的示例包括:

  • 用于反垃圾邮件的 Akismet
  • 用于为 WordPress 添加 Mailchimp 邮件支持的 Mailchimp for WordPress
  • 用于获取 Google Analytics 信息的 Google Site Kit
  • WooCommerce 支付网关扩展(例如 Stripe、PayPal、Authorize.net)

插件开发者通常需要自行管理 API 密钥的存储,一般是在单独的“设置”页面中。因此,如果你有 4 个分别存储 API 密钥的插件,就意味着有 4 个不同的地方需要管理。

Connectors API 和“连接器”页面将这些信息集中到一个地方。开发者可以为其插件提供的服务注册一个连接器,该连接器会显示在“连接器”页面上,用户可以在那里输入并存储 API 密钥。

在 AI 提供商的场景中,已有插件支持三家主要的前沿提供商:OpenAI、Anthropic 和 Google。你可以在“连接器”页面中安装所选提供商的插件

安装后,你可以输入并保存 API 密钥,以启用 AI Client 功能。

Anthropic AI Provider AI API key

如果你希望使用 OpenAI、Anthropic 或 Google 之外的 AI 模型,也有适用于其他多个提供商的插件,包括 Ollama、OpenRouter 和 Mistral。请确保你的提供商使用支持视觉的模型,以支持你将在本教程中构建的功能。

构建插件功能

连接 AI 提供商后,你现在可以开始构建所有插件功能。

步骤 1:注册 abilities

Ability 只是一段已注册的描述,用于说明你的插件能够做什么:一个标签、一段描述、一个输入模式、一个输出模式、一次权限检查,以及一个执行实际工作的回调函数。你将在 includes/abilities.php 文件中完成所有 Ability 的注册。

首先,你需要使用 wp_register_ability_category 函数注册一个分类,用于对你的 abilities 进行分组。在 wp_ai_workshop_demo_register_ability_categories 函数中注册该分类:

wp_register_ability_category(
	'wp-ai-workshop-demo',
	array(
		'label'       => __( 'WP AI Workshop Demo', 'wp-ai-workshop-demo' ),
		'description' => __( 'Abilities for the WP AI Workshop Demo.', 'wp-ai-workshop-demo' ),
	)
);

接下来,你可以使用 wp_register_ability 函数注册第一个 ability describe-image。请注意,这里的大部分内容都只是对 ability 的描述——输入和输出模式会准确告诉任何使用该 ability 的程序应该传入什么,以及应该返回什么。将以下内容添加到 wp_ai_workshop_demo_register_describe_image_ability 函数中:

wp_register_ability(
	'wp-ai-workshop-demo/describe-image',
	array(
		'label'               => __( 'Describe an image via AI', 'wp-ai-workshop-demo' ),
		'description'         => __( 'Given an image URL, use AI vision to produce a detailed text description of the image.', 'wp-ai-workshop-demo' ),
		'category'            => 'wp-ai-workshop-demo',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'image_url' => array(
					'type'        => 'string',
					'description' => 'The URL of the image to describe.',
				),
			),
			'required'   => array( 'image_url' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'description' => array(
					'type'        => 'string',
					'description' => 'A detailed description of the image.',
				),
			),
			'required'   => array( 'description' ),
		),
		'execute_callback'    => 'wp_ai_workshop_demo_describe_image',
		'permission_callback' => function () {
			return current_user_can( 'edit_posts' );
		},
		'meta'                => array(
			'show_in_rest' => true,
		),
	)
);

generate-post-from-description 和 create-post-from-photo abilities 完全遵循相同的模式——只有模式和回调函数有所不同。

首先是 wp_ai_workshop_demo_register_generate_post_from_description_ability 函数:

wp_register_ability(
	'wp-ai-workshop-demo/generate-post-from-description',
	array(
		'label'               => __( 'Generate post copy from a description', 'wp-ai-workshop-demo' ),
		'description'         => __( 'Given a description (and optional tone/angle), generate a WordPress post title and body content.', 'wp-ai-workshop-demo' ),
		'category'            => 'wp-ai-workshop-demo',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'description' => array(
					'type'        => 'string',
					'description' => 'The source description to base the post on.',
				),
				'prompt'      => array(
					'type'        => 'string',
					'description' => 'Optional tone, angle, or extra guidance for the post.',
				),
			),
			'required'   => array( 'description' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'title'   => array(
					'type'        => 'string',
					'description' => 'The generated post title.',
				),
				'content' => array(
					'type'        => 'string',
					'description' => 'The generated post content in Block Editor markup.',
				),
			),
			'required'   => array( 'title', 'content' ),
		),
		'execute_callback'    => 'wp_ai_workshop_demo_generate_post_from_description',
		'permission_callback' => function () {
			return current_user_can( 'edit_posts' );
		},
		'meta'                => array(
			'show_in_rest' => true,
		),
	)
);

然后是 wp_ai_workshop_demo_register_create_post_from_photo_ability 函数:

wp_register_ability(
	'wp-ai-workshop-demo/create-post-from-photo',
	array(
		'label'               => __( 'Create a post from a photo via AI', 'wp-ai-workshop-demo' ),
		'description'         => __( 'Given an image URL, describe the image, write a post about it, and create a draft post using the image as the featured image.', 'wp-ai-workshop-demo' ),
		'category'            => 'wp-ai-workshop-demo',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'image_url' => array(
					'type'        => 'string',
					'description' => 'The URL of the image to turn into a post.',
				),
				'prompt'    => array(
					'type'        => 'string',
					'description' => 'Optional tone, angle, or extra guidance for the post.',
				),
			),
			'required'   => array( 'image_url' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'message' => array(
					'type'        => 'string',
					'description' => 'A status message describing the result.',
				),
				'post_id' => array(
					'type'        => 'integer',
					'description' => 'The ID of the newly created post.',
				),
			),
			'required'   => array( 'message' ),
		),
		'execute_callback'    => 'wp_ai_workshop_demo_create_post_from_photo',
		'permission_callback' => function () {
			return current_user_can( 'edit_posts' );
		},
		'meta'                => array(
			'show_in_rest' => true,
		),
	)
);

这里需要牢牢记住的关键是 Ability 的结构:标签、描述、模式、权限、回调函数。一旦理解了,你就能轻松注册 abilities。

那么,为什么要如此正式地描述所有内容?label、description、input_schema,和output_schema 让 REST API、其他开发者和 AI 智能体都能理解你的 ability,而无需你编写任何额外代码。你只需描述一次,所有希望使用它的东西都会立即明白它叫什么、做什么、接受哪些输入,以及会返回哪些输出。

第 2 步:连接 abilities

与 WordPress 中的大多数功能一样,Ability 的注册通过专用的操作钩子完成:wp_abilities_api_categories_init 和 wp_abilities_api_init。

在主插件文件 wp-ai-workshop-demo.php 中,将 includes/abilities.php 文件中的函数连接到相应的钩子:

add_action( 'wp_abilities_api_categories_init', 'wp_ai_workshop_demo_register_ability_categories' );
add_action( 'wp_abilities_api_init', 'wp_ai_workshop_demo_register_describe_image_ability' );
add_action( 'wp_abilities_api_init', 'wp_ai_workshop_demo_register_generate_post_from_description_ability' );
add_action( 'wp_abilities_api_init', 'wp_ai_workshop_demo_register_create_post_from_photo_ability' );

第 3 步:通过 REST 检查结果

Abilities API 最让我喜欢的一点是——当你使用 'show_in_rest' => true 注册一个 ability 后,它就会向已认证的用户公开在 REST API 中,无需任何额外代码。请确保本地站点的永久链接设置不是“普通”,否则 REST 端点将无法解析。

要测试这一点,请为你的管理员用户创建一个应用程序密码,然后在终端中使用类似 curl 的内容进行测试:

curl -u 'USERNAME:APPLICATION_PASSWORD' https://yoursite.local/wp-json/wp-abilities/v1/abilities

你应当看到一个 JSON 响应,其中包含三个核心 WordPress abilities,以及你自己的三个 wp-ai-workshop-demo/* abilities。

把这个节点作为检查点会让人非常安心。你还没有为这些 Abilities 编写任何内部功能,但已经拥有三个可被发现且带有模式描述的端点。

第 4 步:描述图像(你的第一次 AI 调用)

现在进入有趣的部分。你需要实现所有 Ability 执行回调函数的功能,也就是在其中连接并使用 AI 提供商。

从 wp_ai_workshop_demo_describe_image 开始,它会执行 wp-ai-workshop-demo/describe-image Ability。这发生在 includes/vision.php 文件中,你将在这里首次调用 WordPress AI Client。

$image_url = $arguments['image_url'];

// Vision-capable providers (e.g. Anthropic) require the image to be sent
// inline (base64), not as a remote URL, so fetch it and build a data URI.
// A data URI is also accepted by URL-friendly providers such as OpenAI,
// which keeps this ability portable across providers.
$data_uri = wp_ai_workshop_demo_image_url_to_data_uri( $image_url );
if ( is_wp_error( $data_uri ) ) {
	return $data_uri;
}

$prompt  = 'Describe this image in detail for someone who cannot see it. ';
$prompt .= 'Focus on the main subject, the setting, the mood, notable colours, ';
$prompt .= 'and any details that would help someone write an engaging blog post about it.';

$description = wp_ai_client_prompt()
	->with_text( $prompt )
	->with_file( $data_uri )
	->generate_text();

if ( is_wp_error( $description ) ) {
	return $description;
}

return array(
	'description' => trim( $description ),
);

看看这次 AI Client 调用有多易读。wp_ai_client_prompt() 为你提供了一个流式构建器:添加提示词文本、附加文件、请求返回文本。由于 AI Client 与提供商无关,因此无论用户配置的是 OpenAI、Anthropic 还是 Google,这段代码都可以直接使用。

你还需要添加 wp_ai_workshop_demo_image_url_to_data_uri() 辅助函数。它使用 wp_remote_get() 获取远程图像,并将其编码为 base64 数据 URI,以便发送给 AI 模型。

提示:通常,开发者喜欢将这类辅助函数放在插件的单独文件中。就本教程而言,你可以直接将所有辅助函数添加到相关文件的底部。

/**
 * Fetch a remote image and return it as a base64-encoded data URI.
 *
 * @param string $image_url The URL of the image to fetch.
 * @return string|WP_Error The data URI on success, or WP_Error on failure.
 */
function wp_ai_workshop_demo_image_url_to_data_uri( $image_url ) {
	$response = wp_remote_get(
		$image_url,
		array(
			'timeout'    => 30,
			'user-agent' => 'WP-AI-Workshop-Demo/1.0; ' . home_url( '/' ),
		)
	);
	if ( is_wp_error( $response ) ) {
		return $response;
	}

	$code = (int) wp_remote_retrieve_response_code( $response );
	if ( 200 !== $code ) {
		return new WP_Error(
			'image_fetch_failed',
			sprintf( 'Could not fetch the image (HTTP %d).', $code )
		);
	}

	$body = wp_remote_retrieve_body( $response );
	if ( '' === $body ) {
		return new WP_Error( 'image_fetch_empty', 'The fetched image was empty.' );
	}

	$mime = wp_remote_retrieve_header( $response, 'content-type' );
	if ( ! is_string( $mime ) || 0 !== strpos( $mime, 'image/' ) ) {
		$filetype = wp_check_filetype( $image_url );
		$mime     = ! empty( $filetype['type'] ) ? $filetype['type'] : 'image/jpeg';
	}

	return 'data:' . $mime . ';base64,' . base64_encode( $body );
}

这里有两点需要注意:

  • wp_ai_client_prompt()->generate_text() 返回失败时的 WP_Error——它不会抛出异常。AI Client 的 WordPress 包装器会为你捕获异常并返回 WP_Error。因此始终检查 is_wp_error()。
  • Anthropic 等视觉提供商希望以 base64 内联形式发送图像,而不是远程 URL。这就是为什么你要获取图像并构建 data URI。在某些提供商上发送 URL 可以运行,在另一些提供商会失败——data URI 的通用性最强。

步骤 5:生成帖子文案

下一个要更新的 Ability execute 回调 wp_ai_workshop_demo_generate_post_from_description 位于 includes/content.php 内。该函数会发起第二次 AI 调用,将描述转换为帖子标题和正文。请注意,提示词要求 AI 返回特定的 JSON 对象,从而更容易返回标题和内容。

$description = $arguments['description'];
$guidance    = ! empty( $arguments['prompt'] ) ? trim( $arguments['prompt'] ) : '';

$prompt = 'Write an engaging WordPress blog post inspired by the following image description.';
if ( '' !== $guidance ) {
	$prompt .= ' Tone, angle, or extra guidance: ' . $guidance . '.';
}
$prompt .= ' Image description: ' . $description;
$prompt .= ' Respond with a single JSON object containing exactly two keys: ';
$prompt .= '"title" (a short, compelling post title as plain text) and ';
$prompt .= '"content" (the full post body using valid WordPress Block Editor markup). ';
$prompt .= 'Do not wrap the response in markdown code fences.';

$text = wp_ai_client_prompt( $prompt )->generate_text();

if ( is_wp_error( $text ) ) {
	return $text;
}

$data = wp_ai_workshop_demo_decode_json_response( $text );

if ( ! is_array( $data ) || empty( $data['title'] ) || empty( $data['content'] ) ) {
	return new WP_Error(
		'post_copy_parse_failed',
		'The AI response could not be parsed into a title and content.'
	);
}

return array(
	'title'   => trim( $data['title'] ),
	'content' => trim( $data['content'] ),
);

你还要添加一个 wp_ai_workshop_demo_decode_json_response() 辅助函数,特意去除模型可能在 JSON 周围包裹的任何 markdown 代码围栏,然后再解码。

/**
 * Decode a JSON object from an AI text response, tolerating markdown fences.
 *
 * @param string $text The raw text returned by the AI.
 * @return array|null Decoded associative array, or null if no JSON could be parsed.
 */
function wp_ai_workshop_demo_decode_json_response( $text ) {
	$text = trim( $text );

	$text = preg_replace( '/^\s*```(?:[a-z0-9_-]+)?\s*/i', '', $text );
	$text = preg_replace( '/\s*```\s*$/i', '', $text );
	$text = trim( $text );

	$data = json_decode( $text, true );
	if ( is_array( $data ) ) {
		return $data;
	}

	if ( preg_match( '/\{.*\}/s', $text, $matches ) ) {
		$data = json_decode( $matches[0], true );
		if ( is_array( $data ) ) {
			return $data;
		}
	}

	return null;
}

你可能会奇怪,既然提示词明确要求不要这样做,为什么还要添加这个。向语言模型请求纯 JSON 时,情况是这样的:它通常会遵守要求,但并非始终如此。AI 模型并不是确定性的,也就是说它们不会遵循固定的规则集,而是识别模式并计算下一个输出的数学概率。因此,向 AI 提出完全相同的问题,每次都可能产生不同的响应。所以,在这里添加防御性检查并解析 AI 的数据,可以确保每次都获得正确的响应类型。

步骤 6:组合 abilities(精彩部分)

现在来看真正有趣的部分。includes/post.php 文件内包含 wp_ai_workshop_demo_create_post_from_photo,它是插件功能的核心。在这个编排器函数中,可以调用另外两个 abilities。

$image_url = $arguments['image_url'];
$prompt    = isset( $arguments['prompt'] ) ? $arguments['prompt'] : '';

// Step 1: Describe the image (ability composition).
$describe_ability = wp_get_ability( 'wp-ai-workshop-demo/describe-image' );
if ( ! $describe_ability instanceof WP_Ability ) {
	return array( 'message' => 'Post creation failed: the describe-image ability is not available.' );
}
$description_result = $describe_ability->execute( array( 'image_url' => $image_url ) );
if ( is_wp_error( $description_result ) ) {
	return array( 'message' => 'Post creation failed while describing the image: ' . $description_result->get_error_message() );
}

// Step 2: Generate the post title and content (ability composition).
$generate_ability = wp_get_ability( 'wp-ai-workshop-demo/generate-post-from-description' );
if ( ! $generate_ability instanceof WP_Ability ) {
	return array( 'message' => 'Post creation failed: the generate-post-from-description ability is not available.' );
}
$copy_result = $generate_ability->execute(
	array(
		'description' => $description_result['description'],
		'prompt'      => $prompt,
	)
);
if ( is_wp_error( $copy_result ) ) {
	return array( 'message' => 'Post creation failed while writing the post: ' . $copy_result->get_error_message() );
}

// Step 3: Create the draft post and set the featured image.
return wp_ai_workshop_demo_create_post(
	$copy_result['title'],
	$copy_result['content'],
	$image_url
);

你看到发生了什么吗?wp_get_ability() 按名称获取一个 ability,->execute() 运行它。你的编排器从不直接接触 AI Client;它只是组合两个已经知道如何完成各自工作的 abilities,然后交给一个 wp_ai_workshop_demo_create_post() 辅助函数,该函数调用 wp_insert_post() 创建帖子,并通过 media_sideload_image()helper 加载特色图片。现在正是添加这些辅助函数的好时机。

/**
 * Create a draft post and set its featured image from an image URL.
 *
 * @param string $title The Post title.
 * @param string $content The Post content.
 * @param string $image_url The external image url.
 */
function wp_ai_workshop_demo_create_post( $title, $content, $image_url ) {
	$post_id = wp_insert_post(
		array(
			'post_title'   => sanitize_text_field( $title ),
			'post_content' => $content,
			'post_status'  => 'draft',
			'post_type'    => 'post',
		)
	);

	if ( is_wp_error( $post_id ) || 0 === $post_id ) {
		return array( 'message' => 'Post creation failed.' );
	}

	$attachment_id = wp_ai_workshop_demo_set_featured_image_from_url( $post_id, $image_url );
	if ( is_wp_error( $attachment_id ) ) {
		return array(
			'message' => 'Post created, but the featured image could not be added: ' . $attachment_id->get_error_message(),
			'post_id' => $post_id,
		);
	}

	return array(
		'message' => 'Post created successfully with featured image.',
		'post_id' => $post_id,
	);
}

/**
 * Sideload an image from a URL into the media library and set it as the
 * featured image for a post.
 *
 * @param string $post_id ID of the Post.
 * @param string $image_url URL of the external image.
 */
function wp_ai_workshop_demo_set_featured_image_from_url( $post_id, $image_url ) {
	// Verify that the image_url is a valid url, just to be safe.
	if ( ! wp_http_validate_url( $image_url ) ) {
		return new WP_Error( 'invalid_url', 'Image URL failed validation.' );
	}

	// The media_sideload_image() function is typically only used in the admin context
	// This ensures that it can be used outside the admin context, such as in a REST API request or MCP tool.
	if ( ! function_exists( 'media_sideload_image' ) ) {
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';
	}

	$attachment_id = media_sideload_image( $image_url, $post_id, null, 'id' );
	if ( is_wp_error( $attachment_id ) ) {
		return $attachment_id;
	}

	set_post_thumbnail( $post_id, $attachment_id );

	return $attachment_id;
}

这是 Abilities 最棒的事情之一。一旦你的功能以 abilities 的形式表达,你和其他扩展该功能的开发者就可以通过组合现有 abilities 来构建新功能——就像 LEGO 积木一样。这与让 hooks 和 filters 变得强大的思维模式相同,只是应用于新一代功能。

步骤 7:更新管理界面

至此,你已经完成了插件的大部分核心功能。现在只需将其提供给站点所有者即可。如前文所述,该插件附带一个设置页面,因此我们需要将其连接到刚注册的 Abilities。

第一步是在 includes/admin.php 中加载 Abilities 脚本模块:

wp_enqueue_script_module( '@wordpress/core-abilities' );

@wordpress/core-abilities 加载了查找和执行你在 PHP 中注册的 Abilities 所需的一切 JavaScript 内容。

接下来,你需要更新插件自身的脚本加载方式,将其作为脚本模块加载,并添加对 @wordpress/core-abilities 的依赖:

wp_enqueue_script_module(
	'wp-ai-workshop-demo-script',
	plugins_url( 'build/index.js', __DIR__ ),
	array( '@wordpress/core-abilities' ),
	$asset_file['version'],
);

现在打开文件 src/components/settings-page.jsx,在其中更新设置页面界面。

第一步是从 @wordpress/abilities 包中导入 ready promise。这是一项近期更新,可确保 Abilities REST API 端点在任何模块尝试与其交互之前完成加载。接下来,从 @wordpress/abilities 包中导入 getAbility 和 executeAbility 函数。这让你能够获取并执行已注册的 Abilities。同时,你可以创建一个常量,用来保存 create-post-from-photo Ability 的名称。

const { ready } = await import( /* webpackIgnore: true */ '@wordpress/core-abilities' );
await ready;
const { getAbility, executeAbility } = await import( /* webpackIgnore: true */ '@wordpress/abilities' );

const ABILITY = 'wp-ai-workshop-demo/create-post-from-photo';

提醒:请注意 /* webpackIgnore: true */ 注释。它指示 wp-scripts 在构建步骤中不要打包该导入,因为 WordPress 会在运行时提供它。Gutenberg 中目前有一个用于修复此问题的开放 PR,但它暂时位于 wp-scripts 中;目前正是这条注释让构建顺利通过。

然后,在 generateFromInput 函数内部,使用 Abilities JavaScript API 执行 create-post-from-photo ability。

       const ability = getAbility( ABILITY );
        if ( ! ability ) {
            updateNotice( __( 'Whoops, the create-post-from-photo Ability was not found.', 'wp-ai-workshop-demo' ), 'error' );
            return;
        }

        setIsBusy( true );
        updateNotice( __( 'Looking at your image and writing a post… this can take a moment.', 'wp-ai-workshop-demo' ), 'info' );

        try {
            const result = await executeAbility( ABILITY, {
                image_url: input.image_url,
                prompt: input.prompt,
            } );

            if ( result && result.post_id ) {
                const editUrl = `post.php?post=${ result.post_id }&action=edit`;
                updateNotice(
                    <span>
                        { ( result.message || __( 'Post created.', 'wp-ai-workshop-demo' ) ) + ' ' }
                        <a href={ editUrl }>{ __( 'Edit the draft post.', 'wp-ai-workshop-demo' ) }</a>
                    </span>,
                    'success'
                );
            } else {
                updateNotice( result && result.message ? result.message : __( 'Done.', 'wp-ai-workshop-demo' ), 'error' );
            }
        } catch ( err ) {
            updateNotice( __( 'Error during post generation. Check the console for details.', 'wp-ai-workshop-demo' ), 'error' );
            console.error( err );
        } finally {
            setIsBusy( false );
        }

这里需要关注的关键细节是:与 PHP 中一样,你可以通过名称获取 Ability,检查其是否有效,然后使用所需的输入执行它。无需额外代码,即可在 PHP 和 JavaScript 中执行相同的操作。

在终端中,进入插件目录并运行 npm run build,重新构建资源。

接着前往 Tools → WP AI Workshop Demo,粘贴图片 URL,然后点击 Generate Post。你刚才通过 curl 测试的同一个 ability现在正驱动着仪表盘 UI。一次编写,随处使用。

一个实用调整:超时

Photo to Post 会连续进行两次 AI 调用,然后侧载图片,因此单个请求很容易超过 WordPress 默认的 HTTP 超时时间。幸运的是,AI Client 暴露了一个用于增大超时值的过滤器:

首先,将回调函数添加到 includes/ai-client.php 中以更新超时时间

function wp_ai_workshop_demo_set_request_timeout() {
	return 120;
}

然后,回到主插件文件 wp-ai-workshop-demo.php,像处理 Ability hooks 一样,将该函数连接到过滤器。

add_filter( 'wp_ai_client_default_request_timeout', 'wp_ai_workshop_demo_set_request_timeout' );

这类事情往往要等到你第一次在最糟糕的时刻遇到真实请求超时时,才会意识到。

第 8 步:交给 AI 代理(MCP)

这里就有一点神奇了。我们已经有可以通过 REST 和在仪表盘运行的 abilities。只需做一个小改动,它们也可以由桌面应用中的 AI 代理驱动。

在 wp-ai-workshop-demo/create-post-from-photo Ability 注册内部,更新 meta 数组以添加对 MCP 的支持。

'meta' => array(
	'show_in_rest' => true,
	'mcp'          => array(
		'public' => true, // Expose this ability via MCP.
	),
),

现在,在你的站点上下载、安装并激活 MCP Adapter 插件。这会在 WordPress 站点上创建一个默认 MCP 服务器。

使用你之前创建的同一个 Application Password,你可以使用默认的 WordPress MCP 服务器详情来配置 AI 代理。下面是一个示例,说明如何在 Claude ~/.config/Claude/claude_desktop_config.json 文件中完成此操作:

{
  "mcpServers": {
    "wordpress-ai-demo": {
      "command": "npx",
      "args": [ "-y", "@automattic/mcp-wordpress-remote@latest" ],
      "env": {
        "WP_API_URL": "https://yoursite.local/wp-json/mcp/mcp-adapter-default-server",
        "WP_API_USERNAME": "your-username",
        "WP_API_PASSWORD": "your-application-password"
      }
    }
  }
}

(如果你是 VS Code 用户,MCP 配置块的名称是 servers,而不是 mcpServers。)

现在,让你的 MCP 客户端根据图片 URL “根据这张照片创建一篇文章”——然后看着它自行发现并调用你的 create-post-from-photo ability。同一个 ability,三个不同的入口:REST、仪表盘和 AI 代理。我们没有为其中任何一个单独编写代码。

总结

基于 WordPress Core AI 项目进行构建的一大好处是,你无需重新发明任何东西,就能为插件添加 AI 驱动的功能。

你注册 abilities 来描述插件可以执行的操作。你让 AI Client 处理繁杂的提供商特定细节。你将小型 abilities 组合成更大的功能。然后,REST、区块编辑器和 MCP 代理都能免费随之受益。

如果你用它构建了什么——或者遇到了非常棘手的问题——我希望在评论中听到你的分享。这正是将这些东西公开的最精彩之处。现在,去构建你的第一个 AI 驱动插件吧。🚀

感谢 @juanmaguitar 和 @greenshady 协助进行编辑审阅。

来源:WordPress 开发者博客 · https://developer.wordpress.org/news/2026/07/build-your-first-ai-powered-wordpress-plugin/

部分链接为推广/关联链接。