Appearance
日別記事編集 MCP(Cursor)
日別記事の考察日・ホール・イベント・〇〇の日・ホール別考察・絵文字を、管理画面を開かずに Cursor エージェント(日別記事編集bot)から読み書きするための MCP です。WordPress の REST API daily-article/v1 を、権限を絞った日別記事編集bot ユーザーのアプリケーションパスワードで呼びます。管理者アカウントは渡しません。
関連: Issue #3872 / #3995(誕生日データ) / 設計 API-001-19 日別記事bot編集 REST / API-001-5 〇〇の日オプション / API-001-8 日付ホール別機種名 / ADM-035 権限管理画面
できること・できないこと
| 操作 | 日別記事編集bot | 管理者 |
|---|---|---|
| 考察日の範囲で日別記事を一覧・1 件の編集項目を取得 | できる | できる |
| 考察日・ホール・イベント・〇〇の日・ホール別考察・絵文字の更新 | できる | できる |
| イベント候補の取得、イベントマスタへの新規登録 | できる | できる |
| 下書き・承認待ちの日別記事をゴミ箱へ移す | できる | できる |
| 前後 N 日の誕生日・紐付いた機種・ホール別の台数(去年の結果)を読む | できる | できる |
| 声優名・キャラ名で誕生日を検索する | できる | できる |
| 誕生日マスタ・作品名と機種の紐付けの編集 | できない(誕生日紐付け担当の birthday/v1) | できる |
| 本文・タイトル・ステータス・公開日時の変更、公開済み記事の削除 | できない | できる |
| イベントの改名・削除、有料記事・機種マスタ・ほかの投稿タイプの編集 | できない | できる |
タイトルは考察日から自動で作り直されます(管理画面で保存したときと同じ)。保存後のキャッシュ無効化・考察テーブルの同期・絵文字テーブルの同期も管理画面保存と同じく走ります。
運用手順(WordPress 側)
bot が叩く WordPress(通常は本番)で、管理者が次の作業をします。
1. 日別記事編集bot ユーザーを作る
- 管理画面 → ユーザー → 新規追加
- ユーザー名は用途が分かる名前にする(例:
daily-article-bot)。メールアドレスは管理者が受け取れるものにする - 権限グループで「日別記事編集bot」(
daily_article_editor_bot)を選んで追加する
日別記事編集bot ロールの Capability は ADM-035 権限管理画面 の「日別記事」で確認・変更できます。ゴミ箱への移動は trash_daily_article_drafts(既定で日別記事編集bot に付与)で、外すとゴミ箱移動だけ 403 になります。
2. アプリケーションパスワードを発行する
- 管理画面 → ユーザー → 日別記事編集bot ユーザーの編集画面
- 「アプリケーションパスワード」で名前(例:
cursor-daily-article-tools)を入れて追加する - 表示されたパスワード(
xxxx xxxx xxxx xxxx xxxx xxxx)を控える。この画面を閉じると二度と表示されません
アプリケーションパスワードは HTTPS のサイトでのみ使えます(ローカルの HTTP では wp-config.php に define( 'WP_ENVIRONMENT_TYPE', 'local' ); が必要)。
3. 失効させる
bot をやめるときは、同じ画面で該当のアプリケーションパスワードを「取り消す」。
4. パスワードが漏れた疑いがあるとき
取り消しだけでは足りません。WordPress では本人のプロフィール編集(メールアドレス・ログインパスワードの変更)とアプリケーションパスワードの追加発行ができるため、漏れた資格情報で日別記事編集bot ユーザーが乗っ取られている可能性があります。
- 日別記事編集bot ユーザーのアプリケーションパスワードをすべて取り消す
- メールアドレスを確認したうえでログインパスワードをリセットする。確実にするなら、日別記事編集bot ユーザーを削除して作り直す(記事の作成者を別ユーザーに移す)
- サーバーの PHP エラーログで
[bot-rest-audit]の行を確認する。いつ・どのユーザーが・どの記事のどの項目を変えたか(変更前後の文字数)、どの記事をゴミ箱へ移したか、どのイベントを登録したかが残る - 心当たりのない変更があれば、該当記事のリビジョンや管理画面で内容を戻す。ゴミ箱へ移された記事はゴミ箱から復元する。不要なイベントはイベントマスタ管理画面で削除する
管理者権限への昇格はできないため、影響は上の表の「日別記事編集bot」の操作と、日別記事編集bot ユーザー自身のプロフィールに限られます。
設定(Cursor 側)
.cursor/mcp.json.exampleを.cursor/mcp.jsonにコピーする(.cursor/mcp.jsonは gitignore。パスワードをコミットしない)daily-article-toolsのenvを書き換える
| 変数 | 必須 | 説明 |
|---|---|---|
WP_SITE_URL | はい | WordPress のサイト URL(例: https://example.com)。https:// 必須(http:// は localhost / 127.0.0.1 / [::1] / *.local / *.test のみ)。リダイレクトは追わないので、リダイレクト先の URL(www の有無も含む)を書く |
DAILY_ARTICLE_WP_USERNAME | はい | 日別記事編集bot ユーザーのユーザー名 |
DAILY_ARTICLE_WP_APP_PASSWORD | はい | 日別記事編集bot ユーザーのアプリケーションパスワード(スペース込みで可) |
有料記事用の .env.local(WP_USERNAME / WP_APP_PASSWORD)、新台情報担当 MCP(NEW_MACHINE_WP_*)、外部データ取込 MCP(DATA_IMPORT_WP_*)とは別の変数名です。資格情報を混ぜないでください。
json
{
"mcpServers": {
"daily-article-tools": {
"command": "node",
"args": ["scripts/mcp-daily-article-tools.mjs"],
"env": {
"WP_SITE_URL": "https://example.com",
"DAILY_ARTICLE_WP_USERNAME": "daily-article-bot",
"DAILY_ARTICLE_WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
}
}
}
}設定変更後は Cursor を再読み込みし、MCP daily-article-tools が有効になっていることを確認します。リポジトリで npm install 済み(@modelcontextprotocol/sdk)であることが前提です。
利用可能な Tool
| MCP Tool name | 主な引数 | REST |
|---|---|---|
daily_article_list | date_from, date_to, 任意 status | GET /daily-article/v1/articles |
daily_article_get | id | GET /daily-article/v1/articles/{id} |
daily_article_update_fields | id, fields, 任意 expected_modified | POST /daily-article/v1/articles/{id}/fields |
daily_article_list_events | hall | GET /daily-article/v1/events |
daily_article_register_event | hall, name | POST /daily-article/v1/events |
daily_article_what_day_options | date または mmdd | GET /daily-article/v1/what-day-options |
daily_article_kishu_names | 任意 date | GET /daily-article/v1/kishu-names |
daily_article_trash_draft | id | POST /daily-article/v1/articles/{id}/trash |
daily_article_birthdays | date, 任意 days / halls / include_last_year | GET /daily-article/v1/birthdays |
daily_article_birthday_search | q, 任意 field / limit | GET /daily-article/v1/birthdays/search |
fieldsに入れられるキーはkousatsu_date/halls/daily_article_events/espasu_what_day_manual/island_pre/island_after/espasu_pre/espasu_after/bigapple_pre/bigapple_after/island_emoji/espasu_emoji/bigapple_emojiだけ。ほかのキーは MCP 側で送らずにエラーにするdaily_article_eventsは{ "island": [12, 34] }のようにホールごとのイベント ID の配列。記事のhallsに無いホールは指定できないespasu_what_day_manualはdaily_article_what_day_optionsが返した名前の配列daily_article_birthdaysは基準日の前後days(0〜7、省略時 3)日の誕生日を返す。hallsは省略時island/espasu。台数の基準日はホールごとにdate以前でデータがある最新日(7 日までさかのぼる)で、count_reference_datesに入る。基準日が無いホールはcountがnull、機種が無ければ0。include_last_year: trueで各機種に 1 年前の同日の台数・差枚(last_year)が付く。BB/RB は返さないdaily_article_birthday_searchはfieldがactor(声優名)/chara(キャラ名)/any(両方。省略時)。半角・全角スペースは無視する。台数が要るときは、返ったmonth/dayでdaily_article_birthdaysを呼ぶ
戻り値は REST のレスポンス JSON そのままです。HTTP エラー・通信失敗・設定不足のときは isError: true で {"error":"...","status":409,"data":{...}} のように返します。入出力とエラー条件の正本は API-001-19 です。
bot の作業の流れ
daily_article_listで対象の記事を探し、daily_article_getで現在の値とmodified_gmtを取る- イベントを入れるときは
daily_article_list_eventsで ID を探す。無ければdaily_article_register_eventで登録して ID を得る - 〇〇の日を入れるときは
daily_article_what_day_optionsの候補から選ぶ daily_article_update_fieldsに変える項目だけを入れ、expected_modifiedに手順 1 のmodified_gmtを渡す。続けて更新するときは、応答のmodified_gmtを次のexpected_modifiedに使う- 409(
記事が他の操作で更新されています)が返ったら、daily_article_getで取り直して内容を確かめてから再実行する。人が編集中の可能性があるので、上書きしてよいか迷うときは人間に報告する
HTTP 400 は入力不正(errors / unknown_keys を確認。何も更新されていない)、404 は記事が無い・日別記事でない、409 は上記の競合・公開済み記事の考察日の重複・ゴミ箱へ移せないステータス、429 はレート制限(項目更新 1 分 60 回、イベント登録・ゴミ箱移動 各 1 分 20 回、読み取り 1 分 120 回)です。読み取りの 120 回は記事の一覧・取得・イベント候補・誕生日(daily_article_birthdays / daily_article_birthday_search)の合計で数えるため、誕生日を日ごとに何度も呼ぶと記事の取得も 429 になります。429 は時間をおいて再実行します。503 は DB の読み取り失敗(0 件とは区別している)で、時間をおいて再実行します。
REST の例(curl)
MCP を使わずに動作確認するときの例です。
bash
SITE=https://example.com
AUTH='daily-article-bot:xxxx xxxx xxxx xxxx xxxx xxxx'
# 一覧
curl -sS -u "$AUTH" "$SITE/wp-json/daily-article/v1/articles?date_from=2026-10-01&date_to=2026-10-31&status=draft"
# 1 件取得
curl -sS -u "$AUTH" "$SITE/wp-json/daily-article/v1/articles/26916"
# 項目更新
curl -sS -u "$AUTH" -X POST "$SITE/wp-json/daily-article/v1/articles/26916/fields" \
-H 'Content-Type: application/json' \
-d '{"island_pre":"<p>考察</p>","daily_article_events":{"island":[12]},"expected_modified":"2026-10-03 01:02:03"}'
# イベント登録
curl -sS -u "$AUTH" -X POST "$SITE/wp-json/daily-article/v1/events" \
-H 'Content-Type: application/json' -d '{"hall":"island","name":"周年"}'
# 誕生日(前後 3 日・アイランド/エスパス)
curl -sS -u "$AUTH" "$SITE/wp-json/daily-article/v1/birthdays?date=2026-10-10&days=3&halls=island,espasu"
# 誕生日(去年の結果付き)
curl -sS -u "$AUTH" "$SITE/wp-json/daily-article/v1/birthdays?date=2026-10-10&days=3&include_last_year=1"
# 声優名・キャラ名で検索(q は URL エンコードする)
curl -sS -u "$AUTH" "$SITE/wp-json/daily-article/v1/birthdays/search?q=%E8%8A%B1%E6%BE%A4&field=actor"トラブルシューティング
| 症状 | 確認 |
|---|---|
WP_SITE_URL / DAILY_ARTICLE_WP_USERNAME / ... を設定 | .cursor/mcp.json の daily-article-tools.env |
HTTP 401(incorrect_password / invalid_username) | ユーザー名・アプリケーションパスワードの誤り、取り消し済み、HTTP サイトでの利用 |
HTTP 401(ログインが必要です。) | 認証ヘッダーが PHP に届いていない。下の「401 / 403 の切り分け」を参照 |
HTTP 403(rest_forbidden) | 権限グループが「日別記事編集bot」か、ADM-035 で edit_daily_articles(ゴミ箱移動は trash_daily_article_drafts)が外されていないか |
リダイレクトされました | WP_SITE_URL を location の URL(https / www の有無)に合わせる |
HTTP 404(rest_no_route) | テーマが本機能を含むバージョンか。パーマリンク設定が「基本」だと /wp-json/ が使えない場合がある |
JSON 以外の応答 | WAF・メンテナンス画面・PHP エラー。body の先頭を確認。考察 HTML を WAF が弾く場合はサーバー側の WAF 設定を確認する |
401 / 403 の切り分け
同じ資格情報で WordPress コアの /users/me を呼び、ログインできているかを確かめます。
bash
curl -sS -u "$AUTH" "$SITE/wp-json/wp/v2/users/me"- 401(
rest_not_logged_in): 認証ヘッダーが PHP に届いていない。HTTP サイトで使っていないか、Apache の CGI / FastCGI 構成なら.htaccessにSetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1等があるかを確認する - 200(日別記事編集bot ユーザーが返る): ログインはできている。権限グループと ADM-035 の Capability を確認する
実装メモ
- 本体:
scripts/mcp-daily-article-tools.mjs(テスト:npm run test:mcp-daily-article。npm testにも含まれる) fieldsの許可キーはDailyArticleFieldsUpdateServiceInterface::ALLOWED_KEYS、一覧の 31 日上限はDailyArticleBotRestController::MAX_LIST_RANGE_DAYSと同期する- 誕生日の
days上限・limit上限・fieldの値はDailyArticleBirthdayServiceInterface(MAX_DAYS/SEARCH_LIMIT_MAX/SEARCH_FIELDS)と同期する - 権限判定は REST 側(
WordPressDailyArticleBotPermissionChecker)。MCP 側では権限を判定しない