之前做 DWP 的 LINE 聊天機器人查 WooCommerce 訂單,會員可以直接在 LINE 裡問訂單狀態、查活動,背後是 OpenAI 的 Function Call 在跑。整個架子是自己刻的——每一個會員問句能對應到的後端動作,要自己定義一份 JSON Schema 告訴模型「這個工具吃什麼參數、回什麼結構」,再寫一個 dispatcher 收到 model 的 tool_calls 之後手動 routing 到對應的 WP 函式,回傳值也要自己塞回對話 context。
換一家模型供應商就要重做一次 schema 格式(OpenAI、Anthropic、Gemini 的欄位名稱都不一樣),漏處理一個錯誤路徑 LINE 那邊就吐出一堆奇怪的字。一份「查訂單狀態」的能力,被綁死在這個 LINE Bot 專案裡,搬不到別處用。
直到 WordPress 6.9 把 WordPress Abilities API 正式收進 core,加上 Automattic 維護的 MCP Adapter,這套東西就有了官方標準。同一個 ability 一次註冊,就同時能從 PHP、REST API、跟 MCP(給 Claude Code、Claude Desktop、Cursor 這些 AI client 直接呼叫)三個路徑使用。這篇就是用我們做的 cdx-mcp 外掛當實例,把整條路走過一次。
WordPress Abilities API 是什麼
Abilities API 是 WordPress 6.9 開始進 core 的官方標準,讓你把一段「可被外部呼叫的功能」用統一格式註冊起來,附上輸入/輸出 schema、權限檢查跟標籤——之後 PHP、REST、AI agent 都能用同一份定義去呼叫它。
對應到之前 LINE Bot 自刻 Function Call 的痛點,差別在:
| 手刻 Function Call | Abilities API |
|---|---|
| 每個 model 廠商一份 schema | 一份 input/output schema 全平台共用 |
| 自己寫 dispatcher routing | 用 wp_register_ability( $name, $args ) 註冊 |
| 自己驗證權限、自己回錯誤 | permission_callback + 回 WP_Error 是標準約定 |
| 只能在那個專案內用 | 註冊完同時走 PHP/REST/MCP |
註冊一個 ability 需要的關鍵欄位:
label跟description:description 是寫給 AI 看的,講清楚做什麼、吃什麼、回什麼category:先用wp_register_ability_category()註冊好,ability 才能歸到那個 category 底下input_schema/output_schema:JSON Schema 格式execute_callback:實際做事的 PHP functionpermission_callback:Required,不是 optional(官方 PHP API 文件曾經寫成 optional,後來修正)meta.annotations:標示這個 ability 的特性(唯讀、是否會破壞資料、是否冪等)
meta.annotations 這幾個旗標被 MCP Adapter 自動映射成 readOnlyHint、destructiveHint、idempotentHint,AI 會看這幾個值來決定要不要自動呼叫。特別注意 destructive 預設值是 true,純查詢的 ability 一定要手動設成 false,不然 Claude 會以為這動作有破壞性而拒絕自動執行。這是文件埋得比較深的雷。
MCP Adapter 把 ability 轉成 MCP tool
Abilities API 解決了「功能怎麼註冊」,MCP Adapter 解決的是「怎麼讓 AI agent 找得到並呼叫它」。
Model Context Protocol(MCP)是 Anthropic 推出的開放協定,讓 AI client(Claude Code、Claude Desktop、Cursor、VS Code 等)能透過統一介面去呼叫外部工具。MCP Adapter 就是 WordPress 這端的橋——把已註冊的 ability 包裝成 MCP server 暴露出去,AI client 用 tools/list 跟 tools/call 兩個方法就能讀到 schema 並執行。
裝起來有兩種選擇:
- 用 default server:裝完 mcp-adapter 外掛就會自動建一個叫
mcp-adapter-default-server的 server,要把 ability 加進去得在註冊時加一個meta.mcp.public = true旗標 - 自己建 custom server:在自己的外掛裡明確指定要暴露哪些 ability,乾淨可控,且不需要那個 public 旗標
我走第二條直接用 Composer 安裝,就不用讓使用者還要另外去裝 adapter 外掛了。
實戰:cdx-mcp 外掛把 WooCommerce 訂單查詢變 MCP tool
我做了一個外掛叫 cdx-mcp,功能很單純:給一個 WooCommerce 訂單 ID,回傳這筆訂單的完整結構化資料(狀態、金額、客戶、付款方式、品項),給 Claude Code 直接拿來查訂單。整個外掛大概 200 行 PHP,下面拆開講重點。
Step 1:composer 引入兩個官方 package
外掛根目錄的 composer.json:
{
"name": "codotx/cdx-mcp",
"type": "wordpress-plugin",
"require": {
"php": ">=8.1",
"wordpress/abilities-api": "*",
"wordpress/mcp-adapter": "*"
},
"minimum-stability": "dev",
"prefer-stable": true
}
跑 composer install 之後會把 wordpress/abilities-api(v0.4)、wordpress/mcp-adapter(v0.5)跟 wordpress/php-mcp-schema 一起拉下來。WordPress 6.9 之後 Abilities API 已經進 core,bundled 那份只會在舊版 WP 環境補位用,相同 class 已經存在時 bootstrap 會自動跳過載入,不會打架。
Step 2:主外掛檔載入 bootstrap
<?php
/**
* Plugin Name: CDX MCP
* Requires at least: 6.8
* Requires PHP: 8.1
*/
defined( 'ABSPATH' ) || exit;
define( 'CDX_MCP_DIR', plugin_dir_path( __FILE__ ) );
if ( file_exists( CDX_MCP_DIR . 'vendor/autoload.php' ) ) {
require_once CDX_MCP_DIR . 'vendor/autoload.php';
}
// Abilities API bootstrap(WP 6.9+ core 已有就跳過).
if ( ! function_exists( 'wp_register_ability' ) ) {
require_once CDX_MCP_DIR . 'vendor/wordpress/abilities-api/abilities-api.php';
}
// MCP Adapter bootstrap.
if ( ! class_exists( \WP\MCP\Core\McpAdapter::class ) ) {
require_once CDX_MCP_DIR . 'vendor/wordpress/mcp-adapter/mcp-adapter.php';
}
兩個 if ( ! ... ) 判斷的用意是讓外掛在 WP 6.9(core 已自帶 Abilities API)跟 6.8(要靠 bundled)都能跑。
Step 3:註冊 ability category
ability 必須先有 category 才能歸類,category 要在 wp_abilities_api_categories_init action 註冊:
add_action( 'wp_abilities_api_categories_init', function () {
wp_register_ability_category( 'order-management', array(
'label' => __( 'Order Management', 'cdx-mcp' ),
'description' => __( 'Abilities for querying WooCommerce orders.', 'cdx-mcp' ),
) );
} );
category slug 規則:只能小寫英數加連字號,不能用底線或斜線。
Step 4:註冊 ability,定義 input/output schema
這是整個系統的核心,AI 能不能用對全靠這份 schema 跟 description:
add_action( 'wp_abilities_api_init', function () {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
wp_register_ability( 'cdx-mcp/get-order-status', array(
'label' => __( 'Get WooCommerce Order Status', 'cdx-mcp' ),
'description' => __( 'Returns the WooCommerce order details for a given order ID, including order status, totals, customer info, payment method, shipping info and line items.', 'cdx-mcp' ),
'category' => 'order-management',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'order_id' => array(
'type' => 'integer',
'minimum' => 1,
'description' => 'The WooCommerce order ID to look up.',
),
),
'required' => array( 'order_id' ),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'order_id' => array( 'type' => 'integer' ),
'status' => array( 'type' => 'string' ),
'total' => array( 'type' => 'string' ),
'customer' => array(
'type' => 'object',
'properties' => array(
'email' => array( 'type' => 'string' ),
'phone' => array( 'type' => 'string' ),
),
),
'items' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'product_id' => array( 'type' => 'integer' ),
'name' => array( 'type' => 'string' ),
'quantity' => array( 'type' => 'integer' ),
),
),
),
// 省略其他欄位,實際外掛還含 currency、payment_method、date_paid 等
),
),
'execute_callback' => 'cdx_mcp_get_order_status',
'permission_callback' => function ( $input ) {
return current_user_can( 'manage_woocommerce' );
},
'meta' => array(
'annotations' => array(
'readonly' => true, // 只讀
'destructive' => false, // 預設是 true,純查詢務必設 false
'idempotent' => true, // 重複呼叫結果一樣
),
'show_in_rest' => true,
),
) );
} );
幾個重點:
description寫給 AI 看,不是寫給開發者看,要直接寫清楚「做什麼、吃什麼、回什麼」permission_callback卡manage_woocommerce,等於只有能管 WC 的人才能查meta.annotations.destructive設成false是讓 Claude 知道這是純查詢可以放心執行的關鍵show_in_rest = true順便把這個 ability 暴露到 Abilities API 的 REST 端點,多一條呼叫路徑
Step 5:execute callback 抓 WooCommerce 訂單
function cdx_mcp_get_order_status( $input ) {
$order_id = (int) ( $input['order_id'] ?? 0 );
$order = wc_get_order( $order_id );
if ( ! $order ) {
return new WP_Error(
'order_not_found',
sprintf( __( 'Order %d does not exist.', 'cdx-mcp' ), $order_id )
);
}
$items = array();
foreach ( $order->get_items() as $item ) {
$items[] = array(
'product_id' => (int) $item->get_product_id(),
'name' => (string) $item->get_name(),
'quantity' => (int) $item->get_quantity(),
);
}
return array(
'order_id' => $order->get_id(),
'status' => (string) $order->get_status(),
'total' => (string) $order->get_total(),
'customer' => array(
'email' => (string) $order->get_billing_email(),
'phone' => (string) $order->get_billing_phone(),
),
'items' => $items,
);
}
成功回符合 output_schema 的陣列、失敗回 WP_Error,這是 Abilities API 的標準錯誤約定,MCP Adapter 會自動把 WP_Error 轉成 AI 看得懂的 MCP error response,不用自己包 try/catch、也不用自己組錯誤訊息。
Step 6:建立 custom MCP server
最後一步是把這個 ability 暴露成 MCP tool。在 mcp_adapter_init action 裡呼叫 create_server():
add_action( 'plugins_loaded', function () {
if ( class_exists( \WP\MCP\Core\McpAdapter::class ) ) {
\WP\MCP\Core\McpAdapter::instance();
}
}, 20 );
add_action( 'mcp_adapter_init', function ( $adapter ) {
$adapter->create_server(
'cdx-mcp-server', // server 唯一 ID
'cdx-mcp', // REST namespace
'mcp', // REST route
'CDX MCP Server', // 顯示名稱
'Exposes WooCommerce order lookup as MCP tools.', // 描述
'v0.1.0', // 版本
array( \WP\MCP\Transport\HttpTransport::class ), // 傳輸:HTTP
\WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, // 錯誤處理
\WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, // 觀測
array( 'cdx-mcp/get-order-status' ), // 要暴露的 ability
array(), // resources
array() // prompts
);
} );
倒數第三個參數那個陣列就是「我這個 server 要暴露哪幾個 ability」。之後要加新功能,只要再註冊一個 ability、把名字加進這個陣列就完成。REST endpoint 自動掛到 /wp-json/cdx-mcp/mcp。
外掛啟用後在 WP-CLI 確認:
$ wp mcp-adapter list
ID Name Version Tools Resources Prompts
cdx-mcp-server CDX MCP Server v0.1.0 1 0 0
mcp-adapter-default-server MCP Adapter Default Server v1.0.0 8 0 0
Tools 顯示 1,代表 cdx-mcp/get-order-status 已經被掛上去當 MCP tool。
連 Claude Code:本機 stdio 與生產站 HTTP
ability 跟 MCP server 都備好了,剩下讓 Claude Code 能找到它。兩種 transport 都試一遍。
本機開發:STDIO transport
mcp-adapter 自帶一個 WP-CLI 指令 wp mcp-adapter serve,會把 server 從 stdin/stdout 跑起來。Claude Code 在專案根目錄讀 .mcp.json 設定:
{
"mcpServers": {
"cdx-mcp-local": {
"type": "stdio",
"command": "wp",
"args": [
"--path=/Users/username/Sites/mysite",
"mcp-adapter",
"serve",
"--server=cdx-mcp-server",
"--user=mysite"
]
}
}
}
--user=mystie 帶身分(同時觸發 permission_callback 裡的 current_user_can 檢查)。
用 raw JSON-RPC 手動測一下 server 有沒有正常起來:
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
' | wp mcp-adapter serve --server=cdx-mcp-server --user=mysite
{"jsonrpc":"2.0","id":1,"result":{"serverInfo":{"name":"CDX MCP Server","version":"v0.1.0"}, ... }}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"cdx-mcp-get-order-status", "annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true}, ... }]}}
annotations 那三個 hint 正確映射出來,代表前面在 meta.annotations 設的 readonly / destructive / idempotent 都通了。
正式站 HTTP transport + Application Password
正式站走 HTTP,MCP server 已經自動掛在 https://mystie.com/wp-json/cdx-mcp/mcp,認證用 WordPress 內建的 Application Password:
# 在生產站建一組專用的 application password 給 Claude Code
wp user application-password create 2 'Claude Code CDX MCP' --porcelain
# 回傳 → xxxxxxxxxxxxxxxxxxxxxxxx(24 字元密碼,這裡用占位符代替,實際請保密)
⚠️ 這個密碼等同於 admin 帳號的完整權限,產出後請直接寫進密碼管理器或 .mcp.json,不要貼進文章、commit、聊天訊息或螢幕截圖。下面範例中的 Base64 字串都是示意,請用你自己生成的密碼編碼。
把帳號跟密碼編成 Basic Auth header,寫進 .mcp.json(這個檔案要記得加進 .gitignore):
{
"mcpServers": {
"cdx-mcp-prod": {
"type": "http",
"url": "https://oberonlai.blog/wp-json/cdx-mcp/mcp",
"headers": {
"Authorization": "Basic <Base64(帳號:應用程式密碼)>",
"Accept": "application/json, text/event-stream"
}
}
}
}
產 Base64 的方式:printf 'Developer:你的應用程式密碼' | base64。
curl 直接打 initialize 確認端點通:
$ curl -s -u "Developer:<你的應用程式密碼>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-X POST https://mysite.com/wp-json/cdx-mcp/mcp \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}'
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","serverInfo":{"name":"CDX MCP Server","version":"v0.1.0"}, ... }}
重啟 Claude Code 進專案目錄,第一次會問是否信任 .mcp.json 裡的 MCP server,接受後 /mcp 指令會看到兩台都 connected。

直接在對話裡打「查訂單 56789 的狀態」,Claude 會自動:
- 用
tools/list看可用 tool,發現cdx-mcp-get-order-status描述貼題 - 發
tools/call帶name=cdx-mcp-get-order-status、arguments={"order_id": 56789} - 拿到 JSON 後翻成中文摘要

踩過的幾個小坑
整理一下實作過程中卡到的點:
- WP 版本要 6.9 以上 — Abilities API 是 6.9 才進 core
destructive預設 true — 不手動設false,AI 會以為這個 tool 會破壞資料而拒絕自動呼叫permission_callback是 Required 不是 optional — 官方文件曾經標 optional,後來修正- Abilities Registry 是 lazy init —
wp_abilities_api_init這個 action 只有在第一次有人呼叫WP_Abilities_Registry::get_instance()之後才會 fire,而且必須在init之後
從自刻 Function Call 到官方標準
回頭看 LINE Bot 那套手刻 Function Call,痛點其實在綁死特定專案,同樣是「查訂單狀態」這個能力,當時為了 LINE Bot 寫的 schema 跟 dispatcher,要搬到 Claude Code、Cursor、或下一個聊天機器人專案,全部得重做。
WordPress Abilities API + MCP Adapter 等於把這層工作整合進核心跟官方 package:
- ability 註冊一次,PHP/REST/MCP 三條路徑都能用
- AI client 透過 MCP 標準介面拉 schema,不必為每家模型供應商重做格式
- 權限、錯誤、annotations 都是約定好的協定,不用自己定義
- 換 AI client(Claude Code → Cursor → Continue)只要改
.mcp.json一行,server 端完全不動
之前在另一篇文章討論過 AI agent 管 WordPress 該選 MCP、REST API 還是 SSH + WP-CLI,就能透過 Abilities API 進 core、MCP Adapter 來設計自己的 MCP,對 WordPress 開發者來說「自己造 AI 工具」的門檻又更低了。
cdx-mcp 整個外掛大約 200 行 PHP,但每一行都在做真正的業務邏輯,不再是繞 schema 跟 dispatcher,WordPress 官方把 AI 整合這塊做順了,以後要整合就超方便了!
參考連結
- From Abilities to AI Agents: Introducing the WordPress MCP Adapter — WordPress 官方 dev blog,Jonathan Bossenger 寫的 MCP Adapter 完整介紹,這篇是入門必讀
- WordPress Abilities API GitHub repo — 官方原始碼,
includes/abilities-api/class-wp-ability.php有完整的欄位定義與驗證邏輯(permission_callback是 Required 也是在這裡硬驗證的) - WordPress MCP Adapter GitHub repo — 文中
create_server()的完整簽名、McpAnnotationMapper怎麼把readonly/destructive/idempotent對到 MCP hint 都在這裡 - Model Context Protocol 官方規格 — MCP 協定本身的 spec,要實作自己的 client 或 server 時的依據
- MCP Tool Annotations 規格 —
readOnlyHint/destructiveHint/idempotentHint/openWorldHint四個 hint 的定義,這就是為什麼純查詢要手動設destructive => false - WordPress Application Passwords 官方文件 — HTTP transport 用的 Basic Auth 認證機制
- Claude Code MCP 設定文件 —
.mcp.json格式、stdio/HTTP transport 選項與權限管理