Appearance
誕生日紐付け担当 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. 誕生日紐付け担当ユーザーを作る
- 管理画面 → ユーザー → 新規追加
- ユーザー名は用途が分かる名前にする(例:
birthday-bot)。メールアドレスは管理者が受け取れるものにする - 権限グループで「誕生日紐付け担当」を選んで追加する
- パスワードは長いランダムな値を生成したまま、控えずに追加する
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 側)
.cursor/mcp.json.exampleを.cursor/mcp.jsonにコピーする(.cursor/mcp.jsonは gitignore。パスワードをコミットしない)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, offset | GET /birthday/v1/titles | 不要 |
birthday_list_kishu | 任意 search, limit, offset | GET /birthday/v1/kishu | 不要 |
birthday_list_birthdays | 任意 chara, title, divi, month, day, limit, offset | GET /birthday/v1/birthdays | 不要 |
birthday_list_links | 任意 title, kishu, limit, offset | GET /birthday/v1/links | 不要 |
birthday_list_unlinked_titles | 任意 limit, offset | GET /birthday/v1/unlinked-titles | 不要 |
birthday_upcoming | 任意 days(1〜60、既定 14) | GET /birthday/v1/upcoming | 不要 |
birthday_sulocale_preview | url | POST /birthday/v1/sulocale/preview | 不要 |
birthday_sulocale_import | url, expected_preview_hash | POST /birthday/v1/sulocale/import | 必要 |
birthday_create_link | title_id, kishu_id | POST /birthday/v1/links | 必要 |
birthday_update_link | old_title_id, old_kishu_id, title_id, kishu_id | PATCH /birthday/v1/links | 必要 |
birthday_delete_link | title_id, kishu_id | DELETE /birthday/v1/links | 必要 |
birthday_create_birthday | month, day, divi, chara, 任意 actor, title_id | POST /birthday/v1/birthdays | 必要 |
birthday_update_birthday | id と変更する項目 | PATCH /birthday/v1/birthdays/{id} | 必要 |
birthday_delete_birthday | id | DELETE /birthday/v1/birthdays/{id} | 必要 |
birthday_create_title | name | POST /birthday/v1/titles | 必要 |
birthday_rename_title | id, name | PATCH /birthday/v1/titles/{id} | 必要 |
birthday_set_title_active | id, is_active | POST /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です
紐付け漏れを埋める流れ
birthday_upcoming(またはbirthday_list_unlinked_titles)でlinked: falseの作品名を探すbirthday_list_kishuに作品名の一部をsearchで渡し、紐付け先の機種の候補を探す- 作品名・機種の候補を人間に見せ、どれを紐付けるか確認する
- 了承を得たら
birthday_create_linkを呼ぶ。code: duplicate(409)なら既に紐付いている code: title_inactive(400)なら作品名が無効。人間の確認後にbirthday_set_title_activeで有効にしてから 4 をやり直す。code: kishu_inactive(400)なら機種が無効で、bot からは有効にできないので管理者に伝える
スロカレから取り込む流れ
birthday_sulocale_previewに作品ページの URL を渡すnew_count・entries(status: newの行)と、新規作成される作品名new_titlesを人間に見せる- 了承を得たら同じ URL と、そのプレビューの
preview_hashをexpected_preview_hashに渡してbirthday_sulocale_importを呼ぶ。created_titlesが実際に作られた作品名 code: sulocale_preview_mismatch(409)なら、プレビュー後にページの内容または登録状況が変わっている(何も登録されていない)。1 からやり直し、新しい結果を人間に見せ直すcode: sulocale_run_in_progress(409)なら他の取込が実行中(何も登録されていない)。時間をおいて 3 を再実行する(その間に登録状況が変われば 4 になる)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 409 | code で分ける。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)で読める