WordPress MCP Adapterで記事のAI要約を試してみた
目次
WordPressのMCP Adapterで、AIに記事の要約から下書き保存まで任せられるか試してみた。
実験用のWordPressをM4 Mac miniに立て、架空の記事とAIモデルのQwenを使った。
2026年10月13日の実験で、WordPressは7.1.3、MCP Adapterは0.7.0。
WordPressからQwenを呼ぶ構成
MCPは、AIを使うアプリケーションが外部の機能やデータを利用するための共通の通信方式だ。
今回は「この記事を取得する」「要約を下書きとして保存する」といった処理を、WordPressのAbilityとして登録した。
Abilityには名前や説明、入力の形式に加え、実行する関数と権限を確かめる関数を指定する。
登録や実行にはAbilities APIを使い、MCP Adapter経由で外部のMCPクライアントから呼び出した。
クライアントはMCPで通信するアプリケーションで、今回はPythonで作った。
AIモデルへの文章生成リクエストは、Abilityの中に自分で実装した。
WordPress公式にはWordPress AI Clientを組み合わせる例もあるが、今回はWordPressのHTTP APIを使った。
自然文の指示はPythonクライアントからQwenに送り、Qwenが使う機能名と引数を返してくる。
それを受けてPythonがMCP経由で機能を実行する。この一連の処理がツール呼び出しだ。
これとは別に、WordPress内のAbilityからQwenに記事本文を送って要約している。 モデルは同じだが、APIへのリクエストと送るメッセージは分けた。
| ソフトウェア | この実験での処理 |
|---|---|
| MCP Adapter | 外部クライアントからのMCPリクエストを受ける |
| Abilities API | 登録した機能の入力や権限を確認し、処理を実行する |
| 実験プラグイン | 記事を取得し、Qwenで要約し、結果を下書きに保存する |
| Pythonクライアント | AIが選んだツール呼び出しをMCPへ送り、結果をAIへ返す |
flowchart TD
A[自然文で作業を指示] --> B[Qwenが使う機能を選ぶ]
B --> C[PythonのMCPクライアント]
C --> D[WordPress MCP Adapter]
D --> E[実験用Ability]
E -->|記事本文を送る| F[Qwen APIで要約]
F -->|要約を返す| E
E -->|保存用Abilityの実行時| G[WordPressの下書き]
実験用WordPressをDockerで立てる
Docker Composeで1つの設定ファイルから、WordPressとデータベースのMariaDBを別々のコンテナとして起動した。
記事の作成やプラグインの有効化には、WordPressをコマンドラインから操作するWP-CLIを使った。
| 項目 | この環境で使ったもの |
|---|---|
| ホスト | M4 Mac mini、メモリ16GB、macOS 26.5.2 |
| WordPress | 7.1.3 |
| PHP | 8.3.35 |
| MariaDB | 11.4.13 |
| MCP Adapter | 0.7.0 |
| WP-CLI | 2.12.0 |
| AIモデル | Qwen-Ambassador/Qwen3.8-Flash-Next |
| AI API | ModelScope |
| WordPressのURL | http://localhost:8889/ |
Adapterは0.7.0のリリースにあるプラグインZIPを使った。 Dockerイメージは内容を識別するハッシュ値(ダイジェスト)で固定し、Composeファイルにも記録している。
WordPressの待ち受けは 127.0.0.1:8889 だけなので、このMacからしか接続できない。
DBはDockerの内部ネットワークで動かし、要約する元記事には、図書館の修理相談会について書いた架空の記事を用意した。
再現用のCompose設定、プラグイン、Pythonクライアント、実行ログはLiltingChannelLaboの実験ディレクトリで公開している。
最初はDockerが停止していて、Docker CLIから接続できなかった。 Dockerを起動してから、実験ディレクトリで準備とWordPressの起動を試した。
export MODELSCOPE_API_KEY='利用可能なModelScope APIキー'
python3 lab.py prepare
python3 lab.py start
Composeの初回実行では、Docker APIがHTTP 500(サーバー内部エラー)を返した。
APIが応答するのを確認して同じコマンドをやり直すと起動できたが、原因は分かっていない。
prepare はAdapterを取得してローカル設定ファイルを作り、start はWordPressの初期設定を行うようにした。
start ではプラグインの有効化と実験用ユーザー、架空の記事の登録まで済ませている。今回作った記事の投稿IDは4。
Python側は標準ライブラリだけで動く。
スクリプトの既定のモデルは、アンバサダー用アカウント向けの Qwen-Ambassador/Qwen3.8-Flash-Next にしてある。
別のモデルを使う場合は、準備時と自然文からの実行時に、それぞれ --model で自分のアカウントで使えるモデルIDを指定する。
自然文から操作を選ぶモデルは、ツール呼び出しに対応している必要がある。
python3 lab.py prepare --model '利用可能なモデルID'
python3 lab.py agent --model '利用可能なモデルID'
1行目は上の prepare コマンドの代わりで、その後に start を実行する。2行目は、後の自然文テストで使う agent コマンドの代わりになる。
自作プラグインのAbility
記事の取得と要約は、投稿メタデータに _labo_fixture=1 を設定した実験用の記事だけを受け付けるようにした。
| Ability名 | 入力 | 返すもの |
|---|---|---|
labo/get-post | 投稿ID | 記事のタイトルと本文 |
labo/summarize-post | 投稿ID | Qwenが生成した要約と、その要約を指すID |
labo/save-summary-draft | 要約ID | 保存した下書きの投稿IDと状態 |
先にAbilityのカテゴリを登録してから、記事取得のAbilityを登録した。
labo_mcp_get_post() が記事を取得する関数で、labo_mcp_can_read() が対象記事と閲覧権限を確かめる関数。
input_schema は受け付ける値の形式で、ここでは正の整数の投稿IDを指定している。
wp_register_ability('labo/get-post', [
'label' => 'get-post',
'description' => 'Read a sandbox fixture post.',
'category' => 'labo',
'input_schema' => [
'type' => 'object',
'properties' => [
'post_id' => ['type' => 'integer', 'minimum' => 1],
],
'required' => ['post_id'],
'additionalProperties' => false,
],
'execute_callback' => 'labo_mcp_get_post',
'permission_callback' => 'labo_mcp_can_read',
'meta' => ['mcp' => ['public' => true]],
]);
上のコードは wp_abilities_api_init というフックに登録し、WordPressがAbilityを初期化するタイミングで実行した。
公式のAbility登録ガイドに従い、meta.mcp.public を true にした。
Adapterに標準で用意されているMCPサーバーから、このAbilityを呼び出せるようにしている。
この設定でも認証と権限は確認される。今回の要約処理でも、記事を編集する edit_posts 権限を確かめている。
Qwenへのリクエストは labo/summarize-post を実行する関数に書いた。
WordPressの wp_remote_post() でModelScopeのAPIに記事本文を送り、生成された文章を受け取っている。
要約結果には一時的なIDを付け、15分間保存している。
下書きを作るときは、指定されたIDの要約を取り出し、生成したユーザーが同じかを確かめている。
AIは呼び直さず、すでに生成した文章を投稿に使っている。
PythonからMCP経由で要約する
Pythonクライアントは、このURLに接続した。
http://localhost:8889/wp-json/mcp/mcp-adapter-default-server
認証には、WordPressへの接続用に発行するApplication Password(アプリケーションパスワード)を使った。
実験用ユーザーは labo_agent で、閲覧用の read と記事編集用の edit_posts の権限を付けている。
PythonとWordPressがQwenを呼ぶときは、同じModelScopeのAPIキーを使っている。
WordPressへの接続用パスワードとは別で、MCPリクエストには含めていない。
今回はMCPの仕様バージョンとして 2025-11-25 を指定した。
0.7.0のデフォルトサーバーの説明に従い、initialize で接続を初期化している。
返された Mcp-Session-Id は、以後のリクエストが同じセッションのものだと示すIDになる。
notifications/initialized はクライアントの準備完了通知で、接続から実行までの順序はこうなった。
initialize
→ Mcp-Session-Idを受け取り、以後のリクエストで使う
→ notifications/initialized
→ tools/list
→ tools/call
tools/list でツール一覧を取得すると、Abilityを探したり実行したりするメタツールが返ってきた。
自作したAbilityは、このメタツールを通じて呼び出している。
| MCPツール名 | 用途 |
|---|---|
mcp-adapter-discover-abilities | 利用できるAbilityを探す |
mcp-adapter-get-ability-info | 選んだAbilityの入力形式などを調べる |
mcp-adapter-execute-ability | Ability名と引数を渡して実行する |
個々のAbilityを直接ツールとして登録するカスタムサーバーも作れるが、今回はデフォルトサーバーを使った。
tools/call の params.name にはAdapterの実行用ツール名を入れる。
その引数である arguments の中に、WordPress内のAbility名を ability_name、入力を parameters として指定する。下の例では投稿IDを指定している。
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "mcp-adapter-execute-ability",
"arguments": {
"ability_name": "labo/get-post",
"parameters": { "post_id": 4 }
}
}
}
まずはAIに操作を選ばせず、Pythonから labo/get-post を指定して実行すると記事本文が返ってきた。
続けて投稿IDを指定して labo/summarize-post を実行した。
WordPressが記事本文をQwenのAPIに送って要約すると、要約文と要約IDが返ってきた。この環境でのAPI呼び出し時間は5.51秒。
自然文の指示から下書き保存まで
次に、Pythonクライアントを介してQwen自身に操作を選ばせた。
MCPのツール一覧をQwenへ送り、Pythonでツールを実行した結果もQwenへ返す。
lab.py agent で送る指示は、スクリプト内に書いてある。
投稿ID 4の架空のイベント記事を読み、WordPress側のAI要約機能で要約し、その結果を新しい下書きとして1件保存してください。元記事は変更しないでください。最後に保存した投稿IDと状態を日本語で報告してください。
実行したコマンド。
python3 lab.py agent
この実行では、7回の tools/call が次の順に送られた。
| 順番 | MCPツール | 実行した処理と結果 |
|---|---|---|
| 1 | mcp-adapter-discover-abilities | 実験用のAbilityを確認 |
| 2〜4 | mcp-adapter-get-ability-info | 記事取得・要約・保存の各Abilityの引数を確認 |
| 5 | mcp-adapter-execute-ability | labo/get-post で投稿ID 4の本文を取得 |
| 6 | mcp-adapter-execute-ability | labo/summarize-post でWordPress側からQwenを呼び、要約を取得 |
| 7 | mcp-adapter-execute-ability | labo/save-summary-draft で投稿ID 5を draft として保存 |
2〜4の呼び出しはAIから一度に返ってきたが、Python側では順番に処理した。
WordPressの保存内容も確認すると、生成された投稿は下書きになっていた。
元記事が変わっていないかは、本文から計算したSHA-256というハッシュ値で確かめた。実験前に用意した架空の記事と同じ値だった。
実験プラグインには、登録したAbilityと保存された下書きを表示する管理画面も追加した。

通常の投稿一覧にも、labo_agent が作った下書きが1件だけ表示された。

自然文からの実行は1回だけで、時間と文字数はそのときの測定値。
| 計測した範囲 | 今回の実測 |
|---|---|
| 自然文の指示からAIの完了応答まで | 25.603秒 |
| そのうちWordPress内の要約API呼び出し | 4.39秒 |
| 保存された要約の長さ | 305文字 |
先にPythonから直接呼んだときは、別の実行として計測した。
| 計測した範囲 | 直接呼んだときの実測 |
|---|---|
| WordPress内の要約API呼び出し | 5.51秒 |
保存した要約と元記事の条件を比べると、「一人につき一台」が要約に書かれていなかった。
| 元記事の条件 | 下書きの要約 |
|---|---|
| 2026年10月18日、受付は午後1時〜3時30分、相談終了は午後4時 | 記載あり |
| 定員12組 | 記載あり |
| 部品代は実費 | 記載あり |
| 当日午前10時に市内で暴風警報が出ていたら中止 | 記載あり |
| 持ち込める機器は一人につき一台 | 記載なし |
権限不足とAPIエラーのテスト
Adapterはサーバーへの接続時と個々のAbilityの実行時に、それぞれ権限を確認している。 この2段階の確認は公式の権限確認ガイドに説明がある。
実験用ユーザーには、投稿を公開する publish_posts 権限は付けていない。
実験プラグインの保存処理も、投稿状態を下書きの draft に固定した。
失敗時の確認は、次のコマンドでまとめて実行した。
python3 lab.py negative
| 試した条件 | MCPのHTTPステータス | この環境でのエラー |
|---|---|---|
| 認証なしで接続 | 401 | 接続時の認証で拒否 |
| 購読者(subscriber)のユーザーから要約を実行 | 200 | isError: true、Permission denied |
| 投稿IDに数値ではなく文字列を渡す | 200 | isError: true、Permission denied |
| 存在しないAIモデルIDで要約を実行 | 200 | isError: true。WordPressから呼んだAI APIがHTTP 400を返した |
| 存在しない要約IDで下書き保存 | 200 | isError: true。先に有効な要約を生成するよう求めるエラー |
MCPのHTTPステータスが200だった4件も、ツールの結果はエラーになっていた。
HTTPの通信結果とツールの実行結果は別で、後者の失敗は isError: true で通知される。
AI APIのHTTP 400は、一時的に無効なモデルIDを指定して実際のリクエストで発生させた。確認後は元のモデル指定に戻している。
プラグインが返すエラーメッセージには、下書きを作成しなかったことも書いた。
失敗のテストを一通り実行した前後で、下書きは1件のままだった。
実装中に起きたエラー
実装中には、プラグインの編集直後のHTTPアクセスで構文エラーが返ることもあった。
ホストとコンテナの両方で php -l による構文チェックが成功し、ファイルのハッシュも同じだった。
変更を加えずにアクセスし直すと成功したが、この構文エラーの原因は分かっていない。
lab.py prepare がAPIキーを読み取り専用のファイルに保存したところ、再実行時に上書きできなくなった。
再現用のコードでは、更新時だけ所有者の書き込みを許可し、書き終えたら読み取り専用へ戻すように修正した。