技術約11分で読めます

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
WordPress7.1.3
PHP8.3.35
MariaDB11.4.13
MCP Adapter0.7.0
WP-CLI2.12.0
AIモデルQwen-Ambassador/Qwen3.8-Flash-Next
AI APIModelScope
WordPressのURLhttp://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投稿IDQwenが生成した要約と、その要約を指す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-abilityAbility名と引数を渡して実行する

個々の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ツール実行した処理と結果
1mcp-adapter-discover-abilities実験用のAbilityを確認
2〜4mcp-adapter-get-ability-info記事取得・要約・保存の各Abilityの引数を確認
5mcp-adapter-execute-abilitylabo/get-post で投稿ID 4の本文を取得
6mcp-adapter-execute-abilitylabo/summarize-post でWordPress側からQwenを呼び、要約を取得
7mcp-adapter-execute-abilitylabo/save-summary-draft で投稿ID 5を draft として保存

2〜4の呼び出しはAIから一度に返ってきたが、Python側では順番に処理した。

WordPressの保存内容も確認すると、生成された投稿は下書きになっていた。
元記事が変わっていないかは、本文から計算したSHA-256というハッシュ値で確かめた。実験前に用意した架空の記事と同じ値だった。

実験プラグインには、登録したAbilityと保存された下書きを表示する管理画面も追加した。

ローカルのWordPress管理画面。3つの実験用Abilityと、Qwenで生成して下書き保存した要約が表示されている

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

ローカルWordPressの下書き一覧。AI要約の記事が1件あり、投稿者はlabo_agentになっている

自然文からの実行は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)のユーザーから要約を実行200isError: true、Permission denied
投稿IDに数値ではなく文字列を渡す200isError: true、Permission denied
存在しないAIモデルIDで要約を実行200isError: true。WordPressから呼んだAI APIがHTTP 400を返した
存在しない要約IDで下書き保存200isError: true。先に有効な要約を生成するよう求めるエラー

MCPのHTTPステータスが200だった4件も、ツールの結果はエラーになっていた。
HTTPの通信結果とツールの実行結果は別で、後者の失敗は isError: true で通知される。

AI APIのHTTP 400は、一時的に無効なモデルIDを指定して実際のリクエストで発生させた。確認後は元のモデル指定に戻している。
プラグインが返すエラーメッセージには、下書きを作成しなかったことも書いた。 失敗のテストを一通り実行した前後で、下書きは1件のままだった。

実装中に起きたエラー

実装中には、プラグインの編集直後のHTTPアクセスで構文エラーが返ることもあった。
ホストとコンテナの両方で php -l による構文チェックが成功し、ファイルのハッシュも同じだった。 変更を加えずにアクセスし直すと成功したが、この構文エラーの原因は分かっていない。

lab.py prepare がAPIキーを読み取り専用のファイルに保存したところ、再実行時に上書きできなくなった。 再現用のコードでは、更新時だけ所有者の書き込みを許可し、書き終えたら読み取り専用へ戻すように修正した。