Claude Desktop から WordPress を操作するための MCP サーバ(Claude がツールを呼ぶための常駐プロセス)を、1Password CLI(op)経由で認証するように組んでみました。ローカルに平文のパスワードを置かずに済むので、見た目はキレイな構成です。
ところが使い始めると、10 分ごとに Windows Hello の生体認証プロンプトが飛んでくる。ブログを書いている最中に顔認証を求められるのは、地味に集中が切れます。
原因は一つではなく、サービスアカウント方式への切替と、その過程で踏む 4 つの罠を順番に潰す必要がありました。最終的には launch_mcp.cmd というラッパーバッチを噛ませて、HKCU\Environment(Windows のユーザー環境変数が格納されるレジストリキー)からサービスアカウントトークンを直読みする構成で落ち着いています。この記事は、同じ構成を組む人向けの地雷マップです。
この記事の想定読者
- Claude Desktop で自作の MCP サーバを常駐運用している人
- 1Password CLI(
op)でシークレットを管理したい/しているが、claude_desktop_config.jsonに平文を置きたくない人 - Windows 11 で上記構成を動かしていて、生体認証プロンプトが 10 分おきに出る問題を踏み抜いた人
「そもそも MCP サーバって何?」という段階の方は、まず Claude Desktop 公式ドキュメントの MCP セクションを先に眺めると後半が読みやすくなります。
TL;DR(先に結論)
op runのデスクトップアプリ連携は、10 分の非アクティブで再認可が必要になる仕様- Claude Desktop が stdio 型 MCP サーバを再起動するたびに
op runが再評価され、Windows Hello が出ているように見えた(挙動からの推測) - 回避策は 1Password のサービスアカウントを使うこと
- ただし、サービスアカウント化には以下の罠がある
- サービスアカウントは Personal Vault を読めない(専用 Vault が必要)
- Claude Desktop の
envブロックは親プロセスの環境変数を継承しない op runのマスキングがログ中の部分文字列に反応して壊すことがある- バッチファイルは CRLF で保存しておいた方が安全
- 最終構成は
cmd.exe→ ラッパー.cmd(CRLF で保存)→HKCU\Environment直読み →op run --no-masking - 動く
claude_desktop_config.jsonとlaunch_mcp.cmdの全文は本記事末尾の「最終構成」に掲載
以下、順番に罠を潰していきます。
環境
- Windows 11(OMEN16)
- Claude Desktop
- 1Password 8 デスクトップアプリ + 1Password CLI(
op) - WordPress MCP サーバ(自作、Python + uv で起動)
- WordPress サイト: 本ブログ(kwrkb.com)
- 認証方式: WordPress のアプリケーションパスワード(WordPress がユーザーごとに発行できる API 専用パスワード)
起きていたこと
最初の claude_desktop_config.json はこんな形でした。
"wordpress": {
"command": "C:\\Users\\<USER>\\AppData\\Local\\Microsoft\\WinGet\\Links\\op.exe",
"args": [
"run",
"--",
"C:\\...\\uv.exe",
"run",
"--directory",
"C:\\Users\\<USER>\\Code\\WordPressMCP",
"python",
"wordpress_mcp.py"
],
"env": {
"WP_URL": "https://kwrkb.com/",
"WP_USERNAME": "op://Personal/<item-id>/username",
"WP_APP_PASSWORD": "op://Personal/<item-id>/password"
}
}
op run で op:// の秘密参照を解決してから MCP サーバを起動する、教科書どおりの構成です。MCP 側は環境変数 WP_USERNAME / WP_APP_PASSWORD を読むだけにしてあります。
これで動かすと、10 分ごとに Windows Hello が出続けました。使用中は出ないものの、少し席を立って戻ってくると必ず出ます。
10 分という数字の根拠
まず仕様を確認しました。1Password の公式ドキュメントによると、デスクトップアプリ連携で CLI を認可した場合、10 分間のセッションが確立され、コマンド実行のたびに自動的にリフレッシュされる仕様です(参照: 1Password app integration security)。
「10 分経ったら強制ログアウト」ではなく「10 分間非アクティブだと失効」する挙動で、少なくとも公式ドキュメント上、このタイムアウトをユーザー側で延長する設定は見当たりません。1Password コミュニティでも要望は上がっていますが、現時点で環境変数や config で変更する手段は確認できませんでした。
op run 自体は起動時に 1 回だけ秘密参照を解決するので、MCP サーバが生きているあいだは再認証の要求は本来発生しないはず。なのに 10 分周期で出るということは、手元の挙動からは、Claude Desktop が必要に応じて stdio 型(標準入出力で通信するタイプ)MCP サーバを再起動しており、そのたびに op run が再評価されているように見えました。あくまで外から観測した挙動ベースの推測で、Claude Desktop 側の内部仕様としての確証はありません。
ここで方針は決まりました。アプリ連携をやめて、サービスアカウントトークンに切り替える。これなら生体認証プロンプト自体が発生しません。
ここからが本題の罠地獄です。
罠 1: サービスアカウントは Personal Vault にアクセスできない
1Password Web でサービスアカウントを作成し、トークンを発行。Windows のユーザー環境変数 OP_SERVICE_ACCOUNT_TOKEN にセット。Claude Desktop を再起動。
まだ Windows Hello が出ます。
切り分けのために PowerShell で op whoami を叩いたら、ちゃんと User Type: SERVICE_ACCOUNT と返ります。トークン自体は生きている。続いて op vault list を叩くと、出てきたのは Dev Vault だけ。WordPress のアイテムが入っている Personal Vault はリストにすら出てこない。
1Password の公式仕様で、サービスアカウントには組み込みの Personal / Private / Employee Vault、および default Shared Vault へのアクセス権を付与できないことが明記されています(参照: service-account コマンドリファレンス / Get started with 1Password Service Accounts)。
Vault の設定で変えられる類のものではなく、ハードな仕様制約です。
対応:
- 1Password アプリで新規 Vault(筆者の場合は
Dev)を作成 - WordPress 認証情報のアイテムを
PersonalからDevへ移動 - サービスアカウントに
DevVault の Read 権限を付与
補足として注意点が 2 つ。
- サービスアカウントは作成後にアクセス可能な Vault を追加・変更できません。既存のサービスアカウントで新 Vault を読みたい場合は、新しいサービスアカウントを作り直して旧トークンを revoke します(参照: Manage service accounts)
- Vault 間でアイテムを移動するとアイテム ID が振り直されます。移動後に
op item list --vault Devで新 ID を確認してからop://参照パスを書き換えます
罠 2(最大の罠): Claude Desktop の env ブロックは親環境を継承しない
Vault を移し、参照パスを op://Dev/<item-id>/... に書き換え、Claude Desktop を再起動。
まだ Windows Hello が出ます。
原因はここでした。Claude Desktop は MCP サーバを起動する際、親プロセスの環境変数を継承せず、env ブロックで指定されたものだけをサブプロセスに渡します(完全置換)。
この記事の中で、ここが一番時間を溶かした場所であり、かつ公式ドキュメントに書かれていない独自の発見ポイントです。
手元での検証手順はこうです。
- Windows のユーザー環境変数に
CLAUDE_MCP_DEBUG=helloをsetxでセットしてログオンし直す - MCP サーバ側の起動直後に
print("CLAUDE_MCP_DEBUG=", os.environ.get("CLAUDE_MCP_DEBUG"))を仕込む - Claude Desktop 経由で MCP サーバを起動 → ログには
Noneが出る claude_desktop_config.jsonのenvブロックに"CLAUDE_MCP_DEBUG": "from-config"を追加 →from-configが出る
つまり OS 側のユーザー環境変数はサブプロセスに届いていません。どれだけ OP_SERVICE_ACCOUNT_TOKEN を OS 側に仕込んでも、op run から見たら存在しない扱いになります。トークンが見えない op は自動的にデスクトップアプリ連携にフォールバックし、結果的に Windows Hello が出続ける、という動きでした。
最初は env ブロックに直接書く対応を試しました。
"env": {
"OP_SERVICE_ACCOUNT_TOKEN": "ops_eyJh...(実トークン)",
...
}
これで動きます。動くのですが、設定ファイルに平文でトークンが載る。claude_desktop_config.json は %APPDATA%\Claude\ 配下で、OneDrive 同期や dotfiles リポジトリへのうっかりコミット、スクショ共有などで漏洩するリスクが拭えません。
そこで、ラッパーバッチで HKCU\Environment から直接レジストリを読む構成に切り替えました。完全置換される env ブロックの外側で、バッチ自身がトークンを取得してから op run を起動する流れです。
罠 3: op run のマスキングが部分文字列に反応する
ラッパーを書く前に、もう一つ別の罠を踏んでいました。
op run はデフォルトで、解決した秘密情報が標準出力・標準エラーに出ないようマスキングをかけます。セキュリティとしては正しい挙動ですが、これが部分文字列マッチで動くため、想定外のマスクが発生します。
たとえば WordPress のユーザー名が kwrkb の場合、MCP サーバのデバッグログで
INFO: connecting to https://kwrkb.com/wp-json
と出すと、実際のログには
INFO: connecting to https://<concealed by 1Password>.com/wp-json
と出ます。ユーザー名に一致する kwrkb の部分が食われ、ドメイン名まで巻き添えで <concealed> に置換されるわけです。エラーメッセージやスタックトレースも同様に壊れるので、デバッグ中に何が起きているかが極端に見えづらくなります。
今回のように、ユーザー名がドメイン名など通常ログに含まれる文字列と重なる場合は、--no-masking フラグを付けないとデバッグがかなり難しくなります。
op run --no-masking -- <command>
ただしマスキングを切る場合、MCP サーバ側では認証情報を絶対にログに出さない前提で運用します。特に起動時の os.environ ダンプや、例外時に環境変数をそのまま吐く実装は避けます。クリティカルな秘密はそもそもログに流さない設計にしておけば、--no-masking を付けても実害はありません。
罠 4: バッチファイルの改行コードは CRLF に固定する
ラッパーバッチを書いて、いざ動かすと、なぜか op がサービスアカウントモードに入らず Hello が出る。set で設定したはずの OP_SERVICE_ACCOUNT_TOKEN が空。
原因は、バッチファイルを LF で保存していたことでした。VS Code や他のエディタで作業していると、.gitattributes(リポジトリごとに改行コードを指定するファイル)や Git の core.autocrlf の設定次第で LF のまま保存されがちです。Windows の cmd.exe は CRLF 前提の挙動が残っているため、バッチファイルは CRLF で保存しておくのが安全です。手元では LF 保存のままだと for /f(コマンド出力を変数に束縛するバッチ構文)や set が期待通りに動かず、結果的にトークン未設定のまま op run が呼ばれて Hello フォールバックに戻っていました。
対応:
- バッチファイル(
.cmd/.bat)は CRLF で保存 - リポジトリに置くなら
.gitattributesで明示しておく
*.cmd text eol=crlf
*.bat text eol=crlf
最終構成
ここまでの罠をすべて回避した最終形です。
ポイントは、claude_desktop_config.json に OP_SERVICE_ACCOUNT_TOKEN を直接書かないこと。env に書けば動きますが、Claude の設定ファイルはスクショ・同期・バックアップ・git 管理に混ざりやすいため、今回は避けました。代わりに、ラッパーバッチ側で HKCU\Environment からトークンを取り出してから op run を呼びます。
C:\Users\<USER>\AppData\Roaming\Claude\claude_desktop_config.json
"wordpress": {
"command": "C:\\Windows\\System32\\cmd.exe",
"args": [
"/c",
"C:\\Users\\<USER>\\Code\\WordPressMCP\\launch_mcp.cmd"
],
"env": {
"WP_URL": "https://kwrkb.com/",
"WP_USERNAME": "op://Dev/<item-id>/username",
"WP_APP_PASSWORD": "op://Dev/<item-id>/password"
}
}
C:\Users\<USER>\Code\WordPressMCP\launch_mcp.cmd(CRLF で保存)
@echo off
rem Claude Desktop は env を完全置換するため、HKCU\Environment から OP_SERVICE_ACCOUNT_TOKEN を直接読み出す
for /f "tokens=2,*" %%a in ('reg query "HKCU\Environment" /v OP_SERVICE_ACCOUNT_TOKEN 2^>nul ^| findstr /C:"REG_SZ"') do set "OP_SERVICE_ACCOUNT_TOKEN=%%b"
"C:\Users\<USER>\AppData\Local\Microsoft\WinGet\Links\op.exe" run --no-masking -- "C:\Users\<USER>\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uv.exe" run --directory "C:\Users\<USER>\Code\WordPressMCP" python wordpress_mcp.py
Windows のユーザー環境変数
OP_SERVICE_ACCOUNT_TOKEN=ops_... を GUI の環境変数設定(システムのプロパティ → 環境変数)から登録しておきます(HKCU\Environment に格納されます)。setx OP_SERVICE_ACCOUNT_TOKEN ... でも登録できますが、コマンド履歴にトークンが残ってしまうため、今回は GUI 経由で登録しました。
パスは環境ごとに違うので置き換える
op.exe や uv.exe のパスは WinGet のバージョンやインストール方法で変わります。自分の環境では PowerShell で where.exe op / where.exe uv を叩いて実パスを確認し、上記スニペットの該当箇所に置き換えてください。
動作の流れ
- Claude Desktop が
cmd /c launch_mcp.cmdを起動(envでWP_*のop://参照文字列を渡す) - バッチが
HKCU\Environmentからサービスアカウントトークンをレジストリ直読み op run --no-maskingがサービスアカウントモードで認証 →WP_USERNAME/WP_APP_PASSWORDのop://参照を Dev Vault から解決- 解決済みの実値で
wordpress_mcp.py起動 → WordPress と通信
起動後、Claude Desktop で wp_site_info を叩くと、Windows Hello プロンプトが一切出ずにサイト情報が返ってきます。10 分以上放置してから別のツールを呼んでも、やはり無言で通ります。想定どおりの挙動になりました。
ハマりどころと回避策(まとめ表)
| 罠 | 症状 | 回避策 |
|---|---|---|
env の op:// を Claude Desktop が解釈しない | デスクトップ起動の env では参照が文字列のまま渡る | op run -- でラップして子プロセス側で展開 |
op run のマスキングが部分文字列に反応 | WP_USERNAME=kwrkb が kwrkb.com ドメインまで <concealed> 化 | op run --no-masking(+ ログに秘密を出さない設計) |
| Claude Desktop の env は完全置換 | OS のユーザー環境変数(setx で設定した SA トークン)が子に継承されない | ラッパーバッチでレジストリ HKCU\Environment から直読み |
| バッチの改行が LF だと動かないことがある | for /f や set が期待通りに動かず結果的にトークン未設定 → Hello フォールバック | CRLF に固定(エディタのデフォルト LF に注意) |
セキュリティ設計の整理
この記事は「すでに 1Password を使っている前提」で書いています。別のシークレット管理ツール(Windows 資格情報マネージャ、VS Code Secret Storage、外部 Vault など)をお使いの方は別構成が組めるはずですが、ここでは op 前提の最小変更での落とし所を示しています。
この構成のセキュリティ境界は以下のとおりです。
claude_desktop_config.jsonにはトークン本体が載らない。レビュー・スクショ・git 事故に強い- トークンは
HKCU\Environmentに保存されており、同一 Windows ユーザーのプロセスからは読める - OneDrive の既定同期対象はファイル単位で、
HKCU\Environmentはレジストリに格納されるため、少なくともclaude_desktop_config.jsonのようにファイルとして同期や dotfiles に混ざるリスクは避けられる(バックアップソフトや環境移行ツールまで含めると経路は残り得るため、断定はしない)
一方で注意点として、この構成は「同一 Windows ユーザー権限で動くプロセスを信頼する」前提です。マルウェアや不審な常駐アプリが同一ユーザー権限で動いている場合、HKCU\Environment に置いたトークンは防御境界になりません。ここは DPAPI(Windows がユーザー単位で暗号化・復号する仕組み)で暗号化した外部ファイルに置いても、user scope である以上は同じ制約が残ります。運用コストと防御力のバランスで、今回はレジストリ直読みに落ち着きました。
加えて運用レベルで以下を押さえておくと安心です。
- サービスアカウントのアクセス権は必要最小限の Vault のみ(Dev Vault の Read Only など)に絞る
- WordPress のアプリケーションパスワードは WordPress 側で個別 revoke 可能(万一トークンが漏れても App Password 失効で被害を止められる)
- サービスアカウントトークン自体も定期ローテーションを運用に組み込む
--no-maskingを使う以上、MCP サーバ側で 認証情報をログに出さない。os.environダンプや例外時の環境変数出力は封じておく
デスクトップアプリ連携と違い、サービスアカウントトークンは失効まで 1Password 側に握られ続けます。ローテーションは忘れずに。
まとめ
Claude Desktop + 1Password CLI + 常駐 MCP サーバという組み合わせは、見た目よりも踏む地雷が多い構成でした。順番通りに並べると、
op runのアプリ連携は 10 分の非アクティブで再認可が必要になり、公式ドキュメント上は延長設定が見当たらない → サービスアカウント方式へ- サービスアカウントは Personal / Private / Employee / default Shared Vault にアクセスできない → 専用 Vault へアイテム移動
- サービスアカウントは作成後に Vault アクセス変更不可 → 差し替えは新規作成 + 旧 revoke
- Claude Desktop の
envブロックは完全置換 → 親プロセスの環境変数は継承されない - ユーザー環境変数を届けるにはラッパーバッチ経由でレジストリ直読み
op runのマスキングは部分文字列マッチなので、ユーザー名がドメインと衝突する等の場合は--no-maskingが現実的- バッチは CRLF で保存
1 番目の仕様を読んだ時点で「じゃあサービスアカウント」までは辿り着きやすいのですが、4〜7 番目で粘ります。特に「env 完全置換」と「バッチの CRLF」は、気づくまでに時間を溶かしました。
同じ構成を組んでいて生体認証が止まらない方、あるいは「トークンを平文で置きたくないがどう逃がすか」で悩んでいる方の参考になれば幸いです。

コメント