Skip to content

誕生日紐付け担当 MCP(Cursor) ​

誕生日データ・作品名マスタ・作品名と機種の紐付けを、wp-admin を開かずに検索し、人間の確認を取ったうえで追加・更新・スロカレ取込を行うための MCP です。WordPress の REST API birthday/v1 を、誕生日紐付け担当ロールのユーザーのアプリケーションパスワードで呼びます。編集用・取込用・運用確認用・X告知用・管理者のアカウントは渡しません。

関連: Issue #3892 / 設計 API-008 誕生日 REST / ADM-035 権限管理画面

できること・できないこと ​

操作誕生日紐付け担当
作品名・機種・誕生日データ・紐付けの検索、紐付け漏れ(未紐付けの作品名・近日の誕生日)の確認できる
作品名と機種の紐付けの追加・付け替え・削除(有効な作品名・機種のみ)できる(要確認)
誕生日データの追加・更新・削除できる(要確認)
作品名の新規作成・改名・有効/無効の切り替えできる(要確認)
スロカレの作品ページ 1 件のプレビューと取込(作品名マスタに無い作品名は新規作成される)できる(要確認)
機種マスタの編集・作品名の統合・全件シードできない
無効な作品名・機種への紐付けできない(400)
上記以外の書き込み(記事・自分のプロフィールやアプリケーションパスワードの変更を含む。REST・XML-RPC)できない
wp-admin を開く(誕生日の管理画面・プロフィールを含む)できない

「要確認」の操作は、bot が対象と変更内容を人間に見せ、了承を得てから呼びます。MCP の書き込みツールの説明にも「人間の確認後に呼ぶこと」と書いてあります。REST 側では確認の有無を判定しないため、この運用を守ってください。書き込みはすべて監査ログ([bot-rest-audit])に残ります。

誕生日紐付け担当ロール(birthday_linker)が持つのは WordPress の read と「誕生日」グループの 4 つの Capability(read_birthday_data / link_birthday_kishu / add_birthday_data / update_birthday_data)だけです。wp-admin を開くとサイトトップへリダイレクトされます。

WordPress は read だけのユーザーにも自分のプロフィール編集とアプリケーションパスワードの発行を許すため、birthday_linker を持ち、他には REST 用 bot ロール(ops_status_reader / x_announcer)しか持たないユーザーには BotWriteGuardHooks で次の制限をかけています(詳細は API-008 の「権限・nonce・レート制限」)。

  • REST は GET / HEAD / OPTIONS と birthday/v1 の決められた書き込み以外を 403(/wp/v2/users/me の更新、アプリケーションパスワードの発行・削除を含む)
  • 自分自身に対する edit_user とアプリケーションパスワードの発行・編集・削除の権限を外す(管理者による取り消しはできる)
  • XML-RPC にはログインできない

ログインパスワードには注意してください。 上の制限はアプリケーションパスワードでのアクセスと wp-admin が対象です。誕生日紐付け担当ユーザーのログインパスワードは長いランダムな値にし、どこにも保存せず使わないでください。bot にはアプリケーションパスワードだけを渡します。

運用手順(WordPress 側) ​

1. 誕生日紐付け担当ユーザーを作る ​

  1. 管理画面 → ユーザー → 新規追加
  2. ユーザー名は用途が分かる名前にする(例: birthday-bot)。メールアドレスは管理者が受け取れるものにする
  3. 権限グループで「誕生日紐付け担当」を選んで追加する
  4. パスワードは長いランダムな値を生成したまま、控えずに追加する

bot ごと・用途ごとにアカウントを分けてください。 他の bot のアカウントに誕生日の Capability を足して兼用しないこと。誕生日紐付け担当ロールの Capability は ADM-035 権限管理画面 で確認・変更できます(例: 書き込みを止めて検索だけにするなら read_birthday_data 以外を外す)。

2. アプリケーションパスワードを発行する ​

誕生日紐付け担当ユーザーは wp-admin を開けないため、管理者がユーザーの編集画面の「アプリケーションパスワード」で発行します(名前の例: cursor-birthday-tools)。表示されたパスワードはこの画面を閉じると二度と表示されません。

アプリケーションパスワードは HTTPS のサイトでのみ使えます(ローカルの HTTP では wp-config.php に define( 'WP_ENVIRONMENT_TYPE', 'local' ); が必要)。

3. 失効させる・漏れた疑いがあるとき ​

bot をやめるときは、管理者が同じ画面で該当のアプリケーションパスワードを「取り消す」。漏れた疑いがあるときは、アプリケーションパスワードをすべて取り消してログインパスワードもリセットするか、ユーザーを削除して作り直します。漏れたパスワードでは誕生日データ・作品名・紐付けを変更できるため、監査ログ([bot-rest-audit] の birthday/v1/...)で操作を確認してください。

設定(Cursor 側) ​

  1. .cursor/mcp.json.example を .cursor/mcp.json にコピーする(.cursor/mcp.json は gitignore。パスワードをコミットしない)
  2. birthday-tools の env を書き換える
変数必須説明
WP_SITE_URLはいWordPress のサイト URL。https:// 必須(http:// は localhost / 127.0.0.1 / [::1] / *.local / *.test のみ)。リダイレクトは追わないので、リダイレクト先の URL(www の有無も含む)を書く
BIRTHDAY_WP_USERNAMEはい誕生日紐付け担当ユーザーのユーザー名
BIRTHDAY_WP_APP_PASSWORDはい誕生日紐付け担当ユーザーのアプリケーションパスワード(スペース込みで可)

他の MCP(OPS_STATUS_WP_*・X_ANNOUNCE_WP_* 等)とは別の変数名です。資格情報を混ぜないでください。

json
{
  "mcpServers": {
    "birthday-tools": {
      "command": "node",
      "args": ["scripts/mcp-birthday-tools.mjs"],
      "env": {
        "WP_SITE_URL": "https://example.com",
        "BIRTHDAY_WP_USERNAME": "birthday-bot",
        "BIRTHDAY_WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
      }
    }
  }
}

設定変更後は Cursor を再読み込みし、MCP birthday-tools が有効になっていることを確認します。リポジトリで npm install 済み(@modelcontextprotocol/sdk)であることが前提です。

利用可能な Tool ​

MCP Tool name主な引数REST確認
birthday_list_titles任意 search, active, limit, offsetGET /birthday/v1/titles不要
birthday_list_kishu任意 search, limit, offsetGET /birthday/v1/kishu不要
birthday_list_birthdays任意 chara, title, divi, month, day, limit, offsetGET /birthday/v1/birthdays不要
birthday_list_links任意 title, kishu, limit, offsetGET /birthday/v1/links不要
birthday_list_unlinked_titles任意 limit, offsetGET /birthday/v1/unlinked-titles不要
birthday_upcoming任意 days(1〜60、既定 14)GET /birthday/v1/upcoming不要
birthday_sulocale_previewurlPOST /birthday/v1/sulocale/preview不要
birthday_sulocale_importurl, expected_preview_hashPOST /birthday/v1/sulocale/import必要
birthday_create_linktitle_id, kishu_idPOST /birthday/v1/links必要
birthday_update_linkold_title_id, old_kishu_id, title_id, kishu_idPATCH /birthday/v1/links必要
birthday_delete_linktitle_id, kishu_idDELETE /birthday/v1/links必要
birthday_create_birthdaymonth, day, divi, chara, 任意 actor, title_idPOST /birthday/v1/birthdays必要
birthday_update_birthdayid と変更する項目PATCH /birthday/v1/birthdays/{id}必要
birthday_delete_birthdayidDELETE /birthday/v1/birthdays/{id}必要
birthday_create_titlenamePOST /birthday/v1/titles必要
birthday_rename_titleid, namePATCH /birthday/v1/titles/{id}必要
birthday_set_title_activeid, is_activePOST /birthday/v1/titles/{id}/active必要

戻り値は REST のレスポンス JSON そのままです。HTTP エラー・通信失敗・設定不足のときは isError: true で {"error":"...","status":409,"code":"duplicate","data":{...}} のように返します。code は REST のエラー応答の code で、次の操作は message(error)の文言ではなく code で分けてください(文言は変わることがあります)。code の一覧と入出力・エラー条件の正本は API-008 の「エラー応答」です。

  • 権限チェック・ルートが無いなど WordPress 側で返したエラーでは、WordPress の code(rest_forbidden / rest_no_route / birthday_write_forbidden / incorrect_password 等)が入ります。rest_forbidden は権限不足(403)とレート制限(429)で共通のため、status で分けてください
  • 通信失敗・設定不足・入力不正・リダイレクト・JSON 以外の応答など、REST の応答に code が無いときは code: null です

紐付け漏れを埋める流れ ​

  1. birthday_upcoming(または birthday_list_unlinked_titles)で linked: false の作品名を探す
  2. birthday_list_kishu に作品名の一部を search で渡し、紐付け先の機種の候補を探す
  3. 作品名・機種の候補を人間に見せ、どれを紐付けるか確認する
  4. 了承を得たら birthday_create_link を呼ぶ。code: duplicate(409)なら既に紐付いている
  5. code: title_inactive(400)なら作品名が無効。人間の確認後に birthday_set_title_active で有効にしてから 4 をやり直す。code: kishu_inactive(400)なら機種が無効で、bot からは有効にできないので管理者に伝える

スロカレから取り込む流れ ​

  1. birthday_sulocale_preview に作品ページの URL を渡す
  2. new_count・entries(status: new の行)と、新規作成される作品名 new_titles を人間に見せる
  3. 了承を得たら同じ URL と、そのプレビューの preview_hash を expected_preview_hash に渡して birthday_sulocale_import を呼ぶ。created_titles が実際に作られた作品名
  4. code: sulocale_preview_mismatch(409)なら、プレビュー後にページの内容または登録状況が変わっている(何も登録されていない)。1 からやり直し、新しい結果を人間に見せ直す
  5. code: sulocale_run_in_progress(409)なら他の取込が実行中(何も登録されていない)。時間をおいて 3 を再実行する(その間に登録状況が変われば 4 になる)
  6. code が sulocale_page_invalid / sulocale_too_many_rows(422)なら URL を人間に確認する。sulocale_fetch_failed(502)なら時間をおいて再実行する

スロカレへの取得(プレビューと取込の合算)は 1 ユーザーあたり 1 時間 10 回までです。プレビュー不一致で 409 になった取込も 1 回として数えます(不一致 1 回のあとプレビューと取込をやり直すと、合計 4 回分)。プレビューを何度も呼ばず、結果を使い回してください。管理画面の全件シードの 1 時間クールダウンは、この URL 取込にはかかりません。

その他のレート制限は 1 ユーザーあたり GET 1 分 120 回、書き込み 1 分 30 回です。超えると 429 になります。

REST の例(curl) ​

bash
SITE=https://example.com
AUTH='birthday-bot:xxxx xxxx xxxx xxxx xxxx xxxx'

# 今日から 14 日間の誕生日と紐付け有無
curl -sS -u "$AUTH" "$SITE/wp-json/birthday/v1/upcoming"

# 機種の検索
curl -sS -u "$AUTH" "$SITE/wp-json/birthday/v1/kishu?search=%E3%82%A8%E3%83%B4%E3%82%A1"

# 紐付けの追加(人間の確認後)
curl -sS -u "$AUTH" -H 'Content-Type: application/json' \
  -d '{"title_id":12,"kishu_id":345}' \
  "$SITE/wp-json/birthday/v1/links"

# スロカレのプレビュー(書き込まない)
curl -sS -u "$AUTH" -H 'Content-Type: application/json' \
  -d '{"url":"https://sulocale.sulopachinews.com/archives/35936"}' \
  "$SITE/wp-json/birthday/v1/sulocale/preview"

# スロカレの取込(人間の確認後。preview_hash はプレビューの応答の値)
curl -sS -u "$AUTH" -H 'Content-Type: application/json' \
  -d '{"url":"https://sulocale.sulopachinews.com/archives/35936","expected_preview_hash":"<preview_hash>"}' \
  "$SITE/wp-json/birthday/v1/sulocale/import"

トラブルシューティング ​

症状確認
WP_SITE_URL / BIRTHDAY_WP_USERNAME / ... を設定.cursor/mcp.json の birthday-tools.env
HTTP 401(incorrect_password / invalid_username)ユーザー名・アプリケーションパスワードの誤り、取り消し済み、HTTP サイトでの利用
HTTP 403(rest_forbidden)認証ヘッダーが PHP に届いていない、または権限グループが「誕生日紐付け担当」でない・ADM-035 で該当の Capability が外されている
HTTP 403(birthday_write_forbidden)birthday/v1 の決められた書き込み以外を呼んだ(WordPress コアの REST 等)
HTTP 400(title_inactive / kishu_inactive)無効な作品名・機種には紐付けできない。作品名なら人間の確認後に birthday_set_title_active で有効にする
HTTP 409code で分ける。duplicate=同じデータが既にある、sulocale_run_in_progress=取込が実行中、sulocale_preview_mismatch=プレビューからやり直す
HTTP 422 / 502(スロカレ)作品ページでない・件数が多すぎる(422)、取得失敗(502)。URL と時間をおいての再実行を確認する
HTTP 429(rest_forbidden)レート制限を超えた。スロカレは 1 時間、それ以外は 1 分待つ
HTTP 503(保存できませんでした)DB 書き込みの一時的な失敗。時間をおいて再実行する
リダイレクトされましたWP_SITE_URL を location の URL(https / www の有無)に合わせる
HTTP 404(rest_no_route)テーマが本機能を含むバージョンか。パーマリンク設定が「基本」だと /wp-json/ が使えない場合がある
JSON 以外の応答WAF・メンテナンス画面・PHP エラー。body の先頭を確認

実装メモ ​

  • 本体: scripts/mcp-birthday-tools.mjs(テスト: npm run test:mcp-birthday。npm test にも含まれる)
  • limit の上限 200・days の上限 60 は BirthdayRestController::MAX_LIMIT / MAX_UPCOMING_DAYS と同期する
  • 権限判定とレート制限は REST 側(WordPressBirthdayPermissionChecker)。MCP 側では判定しない
  • 誕生日紐付けの件数だけなら運用確認bot の ops_status_birthday(API-006-6)で読める