どうもカッツプロダクション代表のカッツです!

今回はWordPressとAIエージェントをつないでメンテナンス楽にしたいと思って、WordPress公式のMCPプラグイン(MCP Adapter)を試してみた記録です。先に結論を言っちゃうと、つながるまでに丸一日、地雷を踏みまくりました、、、そのハマりポイントを全部まとめておきます。同じことをやろうとしてる人の時間が少しでも浮けば!

※2026年9月時点、ステージング環境での検証です。社名・ドメイン・パスワード等は伏せています。

WordPress公式のMCPプラグイン「MCP Adapter」とは

前回の記事ではPHPでMCPサーバーを手作りしましたが、実はWordPressにも公式のMCPプラグインがあるんですよね。それがMCP Adapter。

ざっくり言うと、WordPress 6.9で入ったAbilities API(「このサイトでできること」を登録しておく仕組み)に登録された機能を、MCPのツールとしてAIに公開してくれるプラグインです。以前Automatticが出していたプラグインの後継で、WordPress公式のGitHubで開発されています。

検証時点のバージョンは0.6系、この記事を書いている2026年10月時点の最新版も0.7.0と、まだ1.0にもなっていない若いプラグインです。ここ、後で効いてきます、、、

npxの公式クライアントからREST API経由でつなぐ

手元のClaude Codeからつなぐ構成はこんな感じ。サイト側にMCP Adapterプラグインを入れて、手元では公式のクライアント(npxで動くやつ)からREST API経由で接続します。認証はWordPressのアプリケーションパスワードです。

.mcp.json(値はダミーです){
  "mcpServers": {
    "wp-agent-mcp-test": {
      "command": "npx",
      "args": ["-y", "@automattic/mcp-wordpress-remote"],
      "env": {
        "WP_API_URL": "https://stg.example.com/wp-json/mcp/mcp-adapter-default-server",
        "WP_API_USERNAME": "admin-user",
        "WP_API_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
      }
    }
  }
}

設定はこれだけ。プラグイン入れて、これ書いて、はい完了!、、、のはずだったんですよ。

ハマり1 Basic認証のURL埋め込みが拒否される

ステージング環境にはサーバー側でBasic認証(IDとパスワードの壁)をかけていたので、とりあえずhttps://user:pass@stg.example.com/...みたいにURLに埋め込んでみたところ、即エラー。

Node.jsのfetchは、URLに認証情報が入っているとリクエスト自体を作れない仕様なんですよね。じゃあヘッダーで送ればいいじゃん、と思ったら、今度は別の壁が。

同じリクエストで、サーバーのBasic認証とアプリケーションパスワードの両方は通せない
どちらも同じAuthorizationヘッダーを使っていて、1回のリクエストに入れられるのは1つだけだからです。WordPress本体でも既知の問題として扱われていて、Basic認証がかかっているサイトでは、アプリケーションパスワードの発行画面に警告が出る作りになっています。

ただ、これは「同じURLに両方かけた場合」の話で、Basic認証をかける場所を絞れば両立はできるんですよね。たとえばサーバーのBasic認証をwp-adminだけにして、REST API(/wp-json/)は対象から外す形です。WordPress本体にも、「管理画面だけBasic認証で守っているサイトでも、アプリケーションパスワードを使えるようにする」ための仕組み(フィルター)が用意されています。

今回はステージング全体にBasic認証をかけていたので、検証のためにいったん外しました。丸見えは避けたいので、本来は/wp-json/だけ認証の対象から外すか、IP制限に切り替えるのが現実的だと思います(このあたりは未検証です)。

ハマり2 Authorizationヘッダーがそもそも届かない

Basic認証を外しても、まだ認証が通らない。しかも正しいパスワードでも間違ったパスワードでも同じエラーが返ってくる。これ、パスワードを見る以前の段階で弾かれてるってことなんですよね。

原因は、サーバーの設定によってはAuthorizationヘッダーがPHP(WordPress)まで渡されないこと。.htaccessで渡してあげる必要があるんですが、ここで3回コケました(笑)

コケ1 ルールを入れたら「Basic認証が使われている」と警告が出た

ヘッダーを渡すルールを入れたら、管理画面のアプリケーションパスワード欄に「アプリケーションパスワードと互換性がありません」という警告が出ました。WordPressは「PHPにBasic認証の情報が届いているか」だけで判断していて、ルールを消すと警告も消えます。ヘッダーが届くようになった結果、ブラウザが覚えていた前のBasic認証の情報までWordPressに見えてしまった、というのが有力です(シークレットウィンドウでの確認はしていないので、断定はできません)。ちなみにルールは、ヘッダーが空のときに発火しないよう.*ではなく.+(1文字以上)にしています。

コケ2 WordPressに上書きされて消える

# BEGIN WordPress〜# END WordPressの間はWordPressが自動で書き換える領域なので、そこに書いたルールは設定変更のたびに消えます。

コケ3 WordPressブロックの後ろに書くと届かない

じゃあ# END WordPressの後ろに、と移動したら今度は届かない。/wp-json/へのアクセスは内部的にindex.phpへ書き換えられるので、後ろに置いたルールまで処理がたどり着かないんですよね。最終的にWordPressブロックより前に置いて解決しました。

.htaccess(最終形:# BEGIN WordPress より前に置く)RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.+)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]
 
# BEGIN WordPress
(以下WordPressの自動生成部分)

コケ4 サーバーのWordPressセキュリティ設定

エックスサーバーのデフォルト設定、この辺OFFにするのはかなり抵抗

まだ通らない。そこでサーバー(エックスサーバー)側のWordPressセキュリティ設定も疑いました。この中の「国外アクセス制限」に、REST APIやXML-RPC APIへの接続を制限する項目があるんですよね。確認したらREST API関連の制限がONになっていたので、解除してみました。ちなみにこの設定は、自分の環境ではメインドメイン側でしか変更できず、建て付けによっては本番にも適用されてしまい、かなり危なっかしいです(実際の影響範囲は確認できていません。.htaccessで細かく制御できる可能性もありますが、こちらも未検証です)。

このあたり、どの対処が最後の決め手だったのかは正直切り分けきれていません。.htaccessの位置とサーバー設定の変更を並行してやっていたので、「全部やったら通った」が正確なところです、、、ちなみにエックスサーバーのマニュアルでは、これらの制限は通常はONのまま運用することを強く推奨されています。AIのために外すかどうかは要検討ですね。

やっとつながった!でも使えるのは読み取り3つだけ

ここまでやってようやく接続成功。tools/listを叩くと、出てきたツールはこの3つでした。

ツールできること
mcp-adapter-discover-abilities登録されているabilityの一覧を取得
mcp-adapter-get-ability-infoabilityの詳細(入出力の形)を取得
mcp-adapter-execute-abilityabilityを実行

つまり「ツールが直接並ぶ」んじゃなくて、一覧を見る → 詳細を見る → 実行するの3段階で使う設計なんですね。で、肝心のability一覧を見てみると、デフォルトで入っていたのはサイト情報・ユーザー情報・環境情報を読む読み取り専用の3つだけ。

てっきり投稿の編集とかもデフォルトでできると思ってたんですが、書き込み系は自分でabilityを登録しないと何もできないんですよね。

「前は操作系のツールも揃ってた気がする」を調べ直してみました
確認できた範囲では、こんな感じです。
・MCP Adapter自体は、abilityをMCPにつなぐ「橋渡し役」です。更新履歴を見ても、投稿の操作などを標準で持っていたという記載も、後から削除したという記載も見つかりませんでした。
・WordPress本体が標準で持つabilityは、6.9で入った読み取り専用の3つ(サイト情報・ユーザー情報・環境情報)だけです。投稿の作成・更新などは、本体に入れる案として議論されている段階です。
・一方、前身のAutomattic製プラグイン(wordpress-mcp)は、設定でオンにすれば投稿の検索・追加などの操作系ツールが使えました。こちらは2026年1月にアーカイブされ、MCP Adapterへの移行が案内されています。
なので「前は揃ってたのに消えた」というより、前身のプラグインには入っていたけど、後継のMCP Adapterは作りが違う、というのが実態に近そうです。ただ、旧バージョンを実際に入れて比べたわけではないので、そこは未検証です。

abilityを自作したら、さらに3連続で地雷

じゃあ投稿の取得・作成・更新・削除のabilityを自分で登録しよう、とプラグインを書いたら、これがまた一向に認識されない。原因は4つ重なってました。

原因症状
公開フラグ(meta.mcp.public)が無かったデフォルトは非公開なので、AIからは見えない
フック名が違った(正しくはwp_abilities_api_init)登録処理がそもそも走らない
categoryが無かったエラーも出さずに登録が弾かれる

2つ目のフック名は、Claude Codeが最初にabilities_api_init(wp_なし)で書いてきたのが原因です(笑)。WordPressのデバッグログに「アビリティはwp_abilities_api_initアクション上に登録されなければなりません」と出てくれて、やっと気づけました。

一番キツかったのが3つ目のcategory。必須項目なのに、無くてもエラーが一切出ずに静かに登録されない。READMEのサンプルコードをそのまま貼ったら動いたので、差分を比べてようやく判明しました。最終的に動いた形はこんな感じです。

wp-content/plugins/my-agent-abilities.php(実際に作ったもの。7つのうち読み取り系と書き込み系を1つずつ抜粋)

<?php
/**
 * Plugin Name: My Agent Abilities
 * Description: 投稿の取得・作成・更新・削除・画像アップロードなどをAbilityとして登録します(MCP Adapter向け)。
 * Version: 1.0.0
 */
 
if (!defined('ABSPATH')) {
    exit;
}
 
// フック名は wp_ 付き(abilities_api_init だと登録されない)
add_action('wp_abilities_api_init', function () {
 
    if (!function_exists('wp_register_ability')) {
        return;
    }
 
    /**
     * 読み取り系:投稿一覧を取得する
     */
    wp_register_ability('my-agent/get-posts', [
        'label' => '投稿一覧を取得する',
        'description' => '検索・ステータス絞り込みに対応した投稿一覧を返します。',
        'category' => 'site', // 必須。無いと、エラーも出ずに登録が弾かれる
        'input_schema' => [
            'type' => 'object',
            'properties' => [
                'search' => ['type' => 'string', 'description' => 'キーワード検索'],
                'status' => ['type' => 'string', 'description' => 'publish, draft, pending, private など'],
                'per_page' => ['type' => 'integer', 'description' => '取得件数 (デフォルト10)'],
            ],
        ],
        'output_schema' => ['type' => 'array'],
        'execute_callback' => function ($input) {
            $args = [
                'posts_per_page' => $input['per_page'] ?? 10,
                'post_status' => $input['status'] ?? 'publish',
            ];
            if (!empty($input['search'])) {
                $args['s'] = $input['search'];
            }
            $posts = get_posts($args);
            $result = [];
            foreach ($posts as $post) {
                $result[] = [
                    'id' => $post->ID,
                    'title' => $post->post_title,
                    'status' => $post->post_status,
                    'link' => get_permalink($post),
                    'date' => $post->post_date,
                ];
            }
            return $result;
        },
        'permission_callback' => function () {
            return current_user_can('edit_posts');
        },
        'meta' => ['mcp' => ['public' => true]], // 無いと、MCP経由でAIから見えない
    ]);
 
    /**
     * 書き込み系:既存の投稿を更新する
     */
    wp_register_ability('my-agent/update-post', [
        'label' => '投稿を更新する',
        'description' => '投稿IDを指定して、タイトル・本文・ステータスなどを部分更新します。',
        'category' => 'site', // 必須。無いと、エラーも出ずに登録が弾かれる
        'input_schema' => [
            'type' => 'object',
            'properties' => [
                'id' => ['type' => 'integer', 'description' => '投稿ID'],
                'title' => ['type' => 'string', 'description' => 'タイトル'],
                'content' => ['type' => 'string', 'description' => '本文 (HTML可)'],
                'status' => ['type' => 'string', 'description' => 'publish, draft, pending, private のいずれか'],
                'categories' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'カテゴリID配列'],
                'tags' => ['type' => 'array', 'items' => ['type' => 'integer'], 'description' => 'タグID配列'],
                'featured_media' => ['type' => 'integer', 'description' => 'アイキャッチ画像のメディアID'],
            ],
            'required' => ['id'],
        ],
        'output_schema' => ['type' => 'object'],
        'execute_callback' => function ($input) {
            $id = (int) $input['id'];
            if (!get_post($id)) {
                return new WP_Error('not_found', '投稿が見つかりません。');
            }
            $postArgs = ['ID' => $id];
            if (isset($input['title'])) {
                $postArgs['post_title'] = $input['title'];
            }
            if (isset($input['content'])) {
                $postArgs['post_content'] = $input['content'];
            }
            if (isset($input['status'])) {
                $postArgs['post_status'] = $input['status'];
            }
            if (isset($input['categories'])) {
                $postArgs['post_category'] = array_map('intval', $input['categories']);
            }
            $result = wp_update_post($postArgs, true);
            if (is_wp_error($result)) {
                return $result;
            }
            if (isset($input['tags'])) {
                wp_set_post_tags($id, array_map('intval', $input['tags']));
            }
            if (isset($input['featured_media'])) {
                set_post_thumbnail($id, (int) $input['featured_media']);
            }
            return [
                'id' => $id,
                'link' => get_permalink($id),
                'status' => get_post_status($id),
            ];
        },
        'permission_callback' => function ($input) {
            return current_user_can('edit_post', (int) ($input['id'] ?? 0));
        },
        'meta' => ['mcp' => ['public' => true]], // 無いと、MCP経由でAIから見えない
    ]);
 
    // このほかに get-post / create-post / delete-post / upload-media / get-users を同じ形で登録
});

全部直したら、ちゃんと動きました。試しに「投稿の”〇〇”って単語を”△△”に変更して」と頼んだら、該当の2記事を見つけて置換・更新・確認までやってくれました。動いたときは普通に感動しましたね。

結論としては今は公式プラグインより自作の方が良さそう

丸一日かけてつながりはしたものの、正直な感想は「今これを実案件で使うのはまだ早いかな」でした。理由はこのあたり。

  • まだ0.x系で、落とし穴が多い:フック名、category必須、公開フラグなど、間違えてもエラーが出ずに静かに無視されるものばかり(category必須は、0.6.0のリリースでドキュメントに明記されたそうです)
  • REST経由だとサーバーのセキュリティを緩める必要が出てくる:Basic認証を外す、サーバー側のREST API・XML-RPC APIの制限をOFFにする、など
  • やりたいことは標準のREST APIでもできる:投稿・固定ページの編集や画像アップロードは、WordPress標準のREST APIで普通にできるので、abilityという層を挟むメリットが薄い
  • abilityで縛るほど、AIの自由度が下がる:「他の固定ページのデザインを参考にLPを作って」みたいな複合的な作業だと、決められたabilityだけでは足りない

とはいえ、公式だけあって今後WordPressのMCPの標準になっていく可能性は高いですし、ネット上の情報もこれから増えていくはず。「今は」自作が扱いやすい、というくらいの温度感です。体感的にはわかってたんですが文章にして体系化すると面白ーい!!

で、結局どうしたかというと、SSH越しにWP-CLIを叩く自作MCPに落ち着いて、今は実案件で運用し始めてます。その話は次回!

検証した人向けの後片付けチェック
同じように検証した方は、終わったら以下を確認しておくのがおすすめです。
・検証で発行したアプリケーションパスワードの失効(チャットやファイルにコピペしていたら特に)
・デバッグ用に置いたPHPファイルの削除(ユーザー情報などが丸見えになりがち)
・WP_DEBUG_LOGを戻す(wp-content/debug.logがURLから読めてしまう場合があります)
・外したBasic認証やサーバーのアクセス制限を元に戻す

まとめ

  • WordPress公式のMCP Adapterは、Abilities APIに登録された機能をAIに公開するプラグイン(2026年10月時点で0.7.0)
  • REST経由でつなぐと、URLのBasic認証、Authorizationヘッダーの転送、.htaccessの設定、サーバーのアクセス制限と、認証まわりで地雷が多い
  • デフォルトのabilityは読み取り3つだけ。書き込みは自分で登録が必要
  • ability登録はwp_abilities_api_initフック・category必須・公開フラグの3点セットを忘れずに(無いと静かに失敗する)
  • 現時点では、自作MCPの方が挙動をコントロールしやすい(気がするだけ)

AIと一緒にデバッグしてても丸一日かかったので、AIなしだったら、、、と考えるとぞっとしますね(もう便利で昔にもどれないorz)

参考になれば幸いです!ツッコミどころあれば連絡くださいね。AIの困りごとあれば相談にのれますよ!