[{"data":1,"prerenderedAt":2820},["ShallowReactive",2],{"content-/internal-cli-mcp-cloudflare-access":3,"related-/internal-cli-mcp-cloudflare-access":2770,"all-pages-for-dir":2818,"og-image-/internal-cli-mcp-cloudflare-access":2819},{"id":4,"title":5,"body":6,"category":2755,"description":2756,"extension":2757,"meta":2758,"navigation":824,"path":2759,"project_name":2755,"published":2760,"publishedAt":2761,"seo":2762,"stem":2763,"tags":2764,"todo":2755,"unpublished":2760,"updatedAt":2755,"__hash__":2769},"pages/2026-06/2026-06-12/internal-cli-mcp-cloudflare-access.md","APIキーを配らずに「社内の人とAIだけが使えるCLI/MCP」を作る ― Cloudflare Access × IdP認証",{"type":7,"value":8,"toc":2732},"minimark",[9,14,23,26,29,51,58,67,70,75,78,148,155,165,172,175,192,196,199,216,222,229,233,236,266,269,272,276,283,297,300,304,310,348,354,357,377,381,388,414,421,424,432,435,439,446,460,467,471,485,488,491,495,498,512,517,520,566,573,577,592,604,607,611,614,677,779,1015,1029,1033,1061,1068,1072,1079,1471,1483,1487,1493,1504,1510,1580,1583,1587,1594,2193,2199,2465,2469,2472,2498,2501,2626,2629,2633,2639,2661,2664,2667,2696,2699,2725,2728],[10,11,13],"h1",{"id":12},"apiキーを配らずに社内の人とaiだけが使えるclimcpを作る","APIキーを配らずに「社内の人とAIだけが使えるCLI/MCP」を作る",[15,16,17,18,22],"p",{},"会社を AI Ready にする、と言ったときに最初に必要なのは、",[19,20,21],"strong",{},"AIが人間と同じ文脈を読みに行ける場所","です。顧客情報、案件の進捗、社内ナレッジ。人間がいちいちコピペして渡さなくても、AIが自分で取りに行ける状態を作りたい。",[15,24,25],{},"そのための入口として AI に扱いやすいのは CLI と MCP です。ところが、ここで必ず認証の問題にぶつかります。",[15,27,28],{},"「APIキーを発行して配ればいいのでは?」と考えたくなりますが、これには3つの問題があります。",[30,31,32,39,45],"ul",{},[33,34,35,38],"li",{},[19,36,37],{},"AIに秘密情報を渡したくない。"," APIキーは漏れたら終わりの秘密情報です。AIの設定ファイルや会話ログに書く運用は避けたい",[33,40,41,44],{},[19,42,43],{},"発行・配布・保管が面倒。"," 人数分のキーを作り、安全に渡し、各自に保管させる手間が発生する",[33,46,47,50],{},[19,48,49],{},"失効管理が地獄。"," 退職者のキーを消し忘れたら、その人は退職後もデータにアクセスできてしまう。有効期限も個別管理になる",[15,52,53,54,57],{},"この記事では、APIキーを",[19,55,56],{},"一切配らずに","この問題を解く構成を説明します。考え方はひとつです。",[59,60,61],"blockquote",{},[15,62,63,64],{},"利用者ごとにキーを発行するのではなく、",[19,65,66],{},"API自体をIdP（Google Workspace等）のログインで守り、その認証をCLIとMCPで共有する。",[15,68,69],{},"後半では、この構成を私の環境（ひとり税理士事務所＋Cloudflare＋Google Workspace）に導入する手順を書きます。",[71,72,74],"h2",{"id":73},"全体像-守るのはapiひとつだけ","全体像 ― 守るのはAPIひとつだけ",[15,76,77],{},"登場人物は3つです。",[79,80,81,97],"table",{},[82,83,84],"thead",{},[85,86,87,91,94],"tr",{},[88,89,90],"th",{},"要素",[88,92,93],{},"役割",[88,95,96],{},"実体",[98,99,100,114,127],"tbody",{},[85,101,102,108,111],{},[103,104,105],"td",{},[19,106,107],{},"API",[103,109,110],{},"業務データの正本。唯一の保護対象",[103,112,113],{},"Cloudflare Workers + D1",[85,115,116,121,124],{},[103,117,118],{},[19,119,120],{},"CLI",[103,122,123],{},"ターミナルからAPIを呼ぶ入口",[103,125,126],{},"単一バイナリ（npm配布）",[85,128,129,134,137],{},[103,130,131],{},[19,132,133],{},"MCPサーバ",[103,135,136],{},"AIホスト（Claude等）からAPIを呼ぶ入口",[103,138,139,142,143,147],{},[19,140,141],{},"CLIと同じバイナリ","（",[144,145,146],"code",{},"\u003Ccli> mcp"," で起動）",[15,149,150,151,154],{},"ポイントは、CLIもMCPも",[19,152,153],{},"APIを呼ぶだけの「入口」にすぎない","という点です。データを持つのはAPIだけ。だから守るのもAPIだけでいい。入口を何個増やしても、セキュリティの設計は1か所で済みます。",[156,157,162],"pre",{"className":158,"code":160,"language":161},[159],"language-text","人間（ターミナル）──→ CLI ─┐\n                            ├─→ Cloudflare Access ──→ API（Workers + D1）\nAI（Claude等）────→ MCP ─┘      （IdPで本人確認）\n","text",[144,163,160],{"__ignoreMap":164},"",[15,166,167,168,171],{},"そしてAPIの手前に ",[19,169,170],{},"Cloudflare Access"," を置きます。これは「リクエストが届く前に、エッジで本人確認を済ませる門番」です。Google Workspace にログインしていない人のリクエストは、APIに到達する前に弾かれます。",[15,173,174],{},"門番にCloudflareを選ぶ理由は3つあります。",[30,176,177,180,189],{},[33,178,179],{},"Google Workspace 等のIdPとそのまま連携でき、「誰が社員か」の判断をIdPに任せられる",[33,181,182,185,186],{},[144,183,184],{},"cloudflared"," というローカルエージェントが認証トークンの取得とキャッシュを肩代わりするので、",[19,187,188],{},"CLIもMCPも秘密情報を1バイトも持たずに済む",[33,190,191],{},"APIの実行基盤（Workers）もデータベース（D1）もCloudflareで作れば、認証から実行まで同じプラットフォームで完結する",[71,193,195],{"id":194},"認証の流れ-トークンの面倒は-cloudflared-が見る","認証の流れ ― トークンの面倒は cloudflared が見る",[15,197,198],{},"利用者から見た認証は、こうなります。",[200,201,202,205,208,211],"ol",{},[33,203,204],{},"CLIがログイン操作を始め、ブラウザが開く",[33,206,207],{},"ブラウザに Google のログイン画面が出る（普段の Google ログインと同じ）",[33,209,210],{},"ログインに成功すると、Cloudflare Access が**短命のトークン（JWT）**を発行する",[33,212,213,215],{},[144,214,184],{}," がそのトークンをローカルにキャッシュし、以降のAPI呼び出しで使い回す。期限が切れたら再ログイン",[15,217,218,219,221],{},"利用者がやることは「ブラウザでGoogleにログインする」だけです。パスワードもAPIキーも、CLIの設定ファイルには何も書きません。トークンの取得・保管・更新はぜんぶ ",[144,220,184],{}," の仕事です。",[15,223,224,225,228],{},"AIも同じです。AI専用のキーは発行しません。",[19,226,227],{},"AIは、その端末の持ち主がすでに済ませたログインのセッションをそのまま使います。"," だから「このリクエストは誰の操作か」が常に明確で、その人が退職すればAIのアクセスも同時に消えます。",[71,230,232],{"id":231},"二段階のjwt検証-門番を信じすぎない","二段階のJWT検証 ― 門番を信じすぎない",[15,234,235],{},"トークンを持ったリクエストは、2回検証されます。",[200,237,238,248],{},[33,239,240,243,244,247],{},[19,241,242],{},"エッジ（Cloudflare Access）での検証。"," クライアントはトークンをヘッダ（",[144,245,246],{},"cf-access-token","）に載せて送る。Access がこれを検証し、ダメならこの時点で拒否",[33,249,250,253,254,257,258,261,262,265],{},[19,251,252],{},"オリジン（API）での再検証。"," Access は検証済みリクエストに ",[144,255,256],{},"Cf-Access-Jwt-Assertion"," ヘッダを付けてAPIへ転送する。API側はこのJWTを、Access が公開する公開鍵セット（JWKS、",[144,259,260],{},"/cdn-cgi/access/certs","）で",[19,263,264],{},"もう一度検証する","。署名に加えて、発行者（issuer＝チームドメイン）と宛先（audience＝アプリ固有のAUD値）の一致も確認する",[15,267,268],{},"「Access が通したなら信用すればいいのでは?」と思うかもしれませんが、オリジンでも検証するのは多層防御のためです。設定ミスや想定外の経路でAPIに直接届いたリクエストを、確実に弾けます。",[15,270,271],{},"注意点として、検証用の公開鍵は固定値で持ってはいけません。鍵は既定で6週間ごとにローテーションされるので、必ずJWKSエンドポイントから取得します。",[71,273,275],{"id":274},"入退社はidpの操作だけで完結する","入退社はIdPの操作だけで完結する",[15,277,278,279,282],{},"この構成のいちばんおいしいところです。「誰が社員か」の名簿は ",[19,280,281],{},"IdP（Google Workspace）ただ1か所","で管理します。Cloudflare Access も API も、独自のユーザ名簿を持ちません。",[30,284,285,291],{},[33,286,287,290],{},[19,288,289],{},"入社時",": Google Workspace にアカウントを追加するだけ。CLI・MCP・Webすべてに即アクセスできる",[33,292,293,296],{},[19,294,295],{},"退職時",": Google Workspace からアカウントを削除（または無効化）するだけ。Access は認証のたびにIdPを参照するので、削除されたユーザは新しいトークンを取れない。手元に残った短命トークンも期限切れで死ぬ",[15,298,299],{},"「退職者のAPIキーを消し忘れた」という事故が、構造的に起きません。",[71,301,303],{"id":302},"cliとmcpは同じバイナリ-入口が違うだけ","CLIとMCPは同じバイナリ ― 入口が違うだけ",[15,305,306,307,309],{},"CLIに ",[144,308,146],{}," というサブコマンドを用意し、これを実行するとMCPサーバとして振る舞うようにします。同じバイナリなので、認証・通信・データアクセスのコードはすべて共有です。",[79,311,312,325],{},[82,313,314],{},[85,315,316,319,322],{},[88,317,318],{},"入口",[88,320,321],{},"主な利用者",[88,323,324],{},"通信形式",[98,326,327,337],{},[85,328,329,331,334],{},[103,330,120],{},[103,332,333],{},"人間（ターミナル）、シェルを扱うAIエージェント",[103,335,336],{},"コマンド実行・標準出力（パイプ時はJSON）",[85,338,339,342,345],{},[103,340,341],{},"MCP",[103,343,344],{},"MCP対応のAIホスト（Claude Desktop / Claude Code等）",[103,346,347],{},"stdio上のJSON-RPC",[15,349,350,351],{},"CLIは出力先がパイプのときJSONに自動で切り替わるので、Claude Code のようにシェルを叩けるAIはCLIを直接使えます。MCPしか話せないホストにはMCPで応えます。どちらの入口から入っても、同じトークンで同じAPIを呼ぶので、",[19,352,353],{},"人間とAIが完全に同じ認可のもとで同じデータを見ます。",[15,355,356],{},"MCP側の実装で押さえる点は3つです。",[30,358,359,365,371],{},[33,360,361,364],{},[19,362,363],{},"stdioの規律",": プロトコルが流れる標準出力は専有し、ログは標準エラー出力へ出す",[33,366,367,370],{},[19,368,369],{},"遅延認証",": 起動時には認証しない。起動時に認証で落ちると、ホスト側から原因を追えなくなる。ツール呼び出しのたびにトークンを取り、401/403が返ったら一度だけ取り直して再試行する",[33,372,373,376],{},[19,374,375],{},"読み取り専用",": AIに公開するツールは参照系に絞る。AIからデータを変更できないことを型レベルで保証する",[71,378,380],{"id":379},"cliの配布-github-orgメンバーシップが第二のゲート","CLIの配布 ― GitHub Orgメンバーシップが第二のゲート",[15,382,383,384,387],{},"CLIは単一ファイルにバンドルして、GitHub Packages のnpmレジストリに",[19,385,386],{},"非公開パッケージ","として置きます。こうすると、別のレジストリや認証基盤を立てずに、ソースコードと同じGitHubの権限管理を配布のゲートに流用できます。",[30,389,390,400],{},[33,391,392,395,396,399],{},[19,393,394],{},"publish側（CI）",": GitHub Actions が自動発行する ",[144,397,398],{},"GITHUB_TOKEN"," で publish する。このトークンはワークフロー実行中だけ有効で、終わると失効する。長期トークンをsecretに保管・ローテーションする作業が消える",[33,401,402,405,406,409,410,413],{},[19,403,404],{},"install側（利用者）",": 非公開パッケージの取得には「Org メンバーのトークン（read:packages）」が要る。つまり ",[19,407,408],{},"GitHub Org のメンバーであることが、CLIをインストールできる条件そのもの","になる。トークンは GitHub CLI（",[144,411,412],{},"gh","）のログインセッションからその都度取り出して環境変数で渡せば、ディスクに平文保存しなくて済む",[15,415,416,417,420],{},"ここで誤解しやすいのが、",[19,418,419],{},"パッケージの非公開化はセキュリティ境界の本体ではない","という点です。境界はあくまで Cloudflare Access（IdP認証）。CLIのバイナリ自体は秘密情報を含まないので、仮に流出してもIdPログインなしではAPIに触れません。非公開配布は「無関係な人がそもそも入手できない」ようにする追加の一段です。",[15,422,423],{},"結果として、ゲートが二重になります。",[30,425,426,429],{},[33,427,428],{},"配布のゲート = GitHub Org メンバーシップ",[33,430,431],{},"APIのゲート = IdP（Google Workspace）認証",[15,433,434],{},"退職時に両方の名簿から外せば、入手経路と実行権限が同時に消えます。",[71,436,438],{"id":437},"idpログインできない経路の扱い","IdPログインできない経路の扱い",[15,440,441,442,445],{},"外部サービスからのWebhookや、外形監視のヘルスチェックは、ブラウザでGoogleにログインできません。これらは Cloudflare Access の ",[19,443,444],{},"Bypassポリシー","で保護対象から外し、別の手段で正当性を担保します。",[30,447,448,454],{},[33,449,450,453],{},[19,451,452],{},"Webhook",": 送信元が付与する署名（HMAC等）をオリジン側で検証する",[33,455,456,459],{},[19,457,458],{},"ヘルスチェック",": 認証不要の軽量エンドポイントを用意し、機密情報を返さない",[15,461,462,463,466],{},"ローカル開発用に認証をバイパスする経路を作る場合は、",[19,464,465],{},"本番に絶対持ち込まない","運用が前提です。",[71,468,470],{"id":469},"費用-50人以下ならidp代だけ","費用 ― 50人以下ならIdP代だけ",[30,472,473,479,482],{},[33,474,475,476],{},"Cloudflare Access（Zero Trust）は ",[19,477,478],{},"50ユーザーまで無料",[33,480,481],{},"Cloudflare Workers / D1 にも無料枠があり、小〜中規模ならその範囲に収まる",[33,483,484],{},"実質的な費用は IdP（Google Workspace のライセンス）のみ。多くの組織はすでに契約している",[15,486,487],{},"つまり50人以下の組織なら、追加コストほぼゼロで社内限定のAPI基盤を持てます。",[489,490],"hr",{},[71,492,494],{"id":493},"税理士事務所に導入する-私の環境での手順","税理士事務所に導入する ― 私の環境での手順",[15,496,497],{},"ここからは、この構成を税理士業務に導入する手順です。前提となる私の環境はこうです。",[30,499,500,503,506,509],{},[33,501,502],{},"Google Workspace 契約済み（独自ドメインのメール運用）",[33,504,505],{},"Cloudflare アカウントあり（このブログ自体が Pages + Workers + D1 で動いている）",[33,507,508],{},"GitHub アカウントあり",[33,510,511],{},"AIホストは Claude Code / Claude Desktop",[513,514,516],"h3",{"id":515},"何を載せるか-税理士業務の文脈api","何を載せるか ― 税理士業務の「文脈API」",[15,518,519],{},"まず、APIに集約する「業務の文脈」を決めます。税理士業務なら、たとえばこのあたりが候補です。",[79,521,522,532],{},[82,523,524],{},[85,525,526,529],{},[88,527,528],{},"データ",[88,530,531],{},"AIへの効き方",[98,533,534,542,550,558],{},[85,535,536,539],{},[103,537,538],{},"顧問先マスタ（決算期・消費税の課税方式・税務代理の範囲）",[103,540,541],{},"「○○社の申告期限いつ?」「簡易課税だっけ?」に即答できる",[85,543,544,547],{},[103,545,546],{},"申告・届出の期限と進捗ステータス",[103,548,549],{},"「今月締切の作業を一覧して」が一発で出る",[85,551,552,555],{},[103,553,554],{},"顧問先ごとの留意事項メモ（過去の指摘・特殊論点）",[103,556,557],{},"申告書レビュー時にAIが過去の経緯を踏まえられる",[85,559,560,563],{},[103,561,562],{},"業務手順・チェックリスト",[103,564,565],{},"「年末調整の手順を顧問先別の注意点込みで」と聞ける",[15,567,568,569,572],{},"逆に、",[19,570,571],{},"マイナンバー・口座情報などの特定個人情報はこのAPIに載せない","と最初に決めておきます。Access で守られているとはいえ、AIが読める場所に置く情報は「漏れても守秘義務違反の深刻度が一段低いもの」に絞るのが安全側です。",[513,574,576],{"id":575},"step-1-cloudflare-zero-trust-を有効化しgoogle-を-idp-連携する","Step 1: Cloudflare Zero Trust を有効化し、Google を IdP 連携する",[200,578,579,586],{},[33,580,581,582,585],{},"Cloudflare ダッシュボードで Zero Trust を有効化する（Freeプランで50ユーザーまで無料。チームドメイン ",[144,583,584],{},"\u003Cteam>.cloudflareaccess.com"," を決める）",[33,587,588,591],{},[19,589,590],{},"Settings → Authentication → Login methods"," で Google Workspace を追加する。Google Cloud Console 側で OAuth クライアントを作成し、クライアントID/シークレットを Cloudflare に登録する流れ",[15,593,594,595],{},"手順の詳細は公式ドキュメントが正確です: ",[596,597,603],"a",{"href":598,"target":599,"rel":600},"https://developers.cloudflare.com/cloudflare-one/identity/idp-integration/google-workspace/","_blank",[601,602],"noopener","noreferrer","→ Cloudflare Zero Trust: Google Workspace integration",[15,605,606],{},"ひとり事務所なら、まず「Google アカウントでのログイン（汎用Google連携）＋メールアドレス個別指定ポリシー」でも動きます。スタッフが増えてグループ単位で制御したくなったら Workspace 連携に格上げすれば十分です。",[513,608,610],{"id":609},"step-2-api-を-workers-d1-で作る","Step 2: API を Workers + D1 で作る",[15,612,613],{},"最小構成のAPIを作ります。D1に顧問先テーブルを置き、Workersで読み取りエンドポイントを生やします。",[156,615,619],{"className":616,"code":617,"language":618,"meta":164,"style":164},"language-bash shiki shiki-themes vitesse-light vitesse-light","npm create cloudflare@latest jimusho-api -- --template hono\ncd jimusho-api\nnpx wrangler d1 create jimusho-db\n","bash",[144,620,621,650,660],{"__ignoreMap":164},[622,623,626,630,634,637,640,644,647],"span",{"class":624,"line":625},"line",1,[622,627,629],{"class":628},"senZ8","npm",[622,631,633],{"class":632},"sdGka"," create",[622,635,636],{"class":632}," cloudflare@latest",[622,638,639],{"class":632}," jimusho-api",[622,641,643],{"class":642},"snbK4"," --",[622,645,646],{"class":642}," --template",[622,648,649],{"class":632}," hono\n",[622,651,653,657],{"class":624,"line":652},2,[622,654,656],{"class":655},"sz8Xr","cd",[622,658,659],{"class":632}," jimusho-api\n",[622,661,663,666,669,672,674],{"class":624,"line":662},3,[622,664,665],{"class":628},"npx",[622,667,668],{"class":632}," wrangler",[622,670,671],{"class":632}," d1",[622,673,633],{"class":632},[622,675,676],{"class":632}," jimusho-db\n",[156,678,682],{"className":679,"code":680,"language":681,"meta":164,"style":164},"language-sql shiki shiki-themes vitesse-light vitesse-light","-- schema.sql（最初は本当に最小でいい）\nCREATE TABLE clients (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL,\n  fiscal_month INTEGER,        -- 決算月\n  tax_scheme TEXT,             -- 'general' | 'simplified' | 'exempt'\n  notes TEXT\n);\n","sql",[144,683,684,690,706,721,735,749,764,773],{"__ignoreMap":164},[622,685,686],{"class":624,"line":625},[622,687,689],{"class":688},"sxvE3","-- schema.sql（最初は本当に最小でいい）\n",[622,691,692,696,699,702],{"class":624,"line":652},[622,693,695],{"class":694},"sHkkW","CREATE",[622,697,698],{"class":694}," TABLE",[622,700,701],{"class":628}," clients",[622,703,705],{"class":704},"sG7-3"," (\n",[622,707,708,711,715,718],{"class":624,"line":662},[622,709,710],{"class":704},"  id ",[622,712,714],{"class":713},"stQ0i","INTEGER",[622,716,717],{"class":713}," PRIMARY KEY",[622,719,720],{"class":704},",\n",[622,722,724,727,730,733],{"class":624,"line":723},4,[622,725,726],{"class":694},"  name",[622,728,729],{"class":713}," TEXT",[622,731,732],{"class":694}," NOT NULL",[622,734,720],{"class":704},[622,736,738,741,743,746],{"class":624,"line":737},5,[622,739,740],{"class":704},"  fiscal_month ",[622,742,714],{"class":713},[622,744,745],{"class":704},",        ",[622,747,748],{"class":688},"-- 決算月\n",[622,750,752,755,758,761],{"class":624,"line":751},6,[622,753,754],{"class":704},"  tax_scheme ",[622,756,757],{"class":713},"TEXT",[622,759,760],{"class":704},",             ",[622,762,763],{"class":688},"-- 'general' | 'simplified' | 'exempt'\n",[622,765,767,770],{"class":624,"line":766},7,[622,768,769],{"class":704},"  notes ",[622,771,772],{"class":713},"TEXT\n",[622,774,776],{"class":624,"line":775},8,[622,777,778],{"class":704},");\n",[156,780,784],{"className":781,"code":782,"language":783,"meta":164,"style":164},"language-typescript shiki shiki-themes vitesse-light vitesse-light","// src/index.ts（Hono。検証ミドルウェアは Step 4 で足す）\nimport { Hono } from 'hono'\n\nconst app = new Hono\u003C{ Bindings: { DB: D1Database } }>()\n\napp.get('/clients', async (c) => {\n  const { results } = await c.env.DB.prepare(\n    'SELECT id, name, fiscal_month, tax_scheme FROM clients ORDER BY name'\n  ).all()\n  return c.json(results)\n})\n\nexport default app\n","typescript",[144,785,786,791,820,826,865,869,911,949,959,971,992,998,1003],{"__ignoreMap":164},[622,787,788],{"class":624,"line":625},[622,789,790],{"class":688},"// src/index.ts（Hono。検証ミドルウェアは Step 4 で足す）\n",[622,792,793,796,800,804,807,810,814,817],{"class":624,"line":652},[622,794,795],{"class":694},"import",[622,797,799],{"class":798},"shFtX"," {",[622,801,803],{"class":802},"s4oTP"," Hono",[622,805,806],{"class":798}," }",[622,808,809],{"class":694}," from",[622,811,813],{"class":812},"sMJiu"," '",[622,815,816],{"class":632},"hono",[622,818,819],{"class":812},"'\n",[622,821,822],{"class":624,"line":662},[622,823,825],{"emptyLinePlaceholder":824},true,"\n",[622,827,828,831,834,837,840,843,846,849,852,855,858,862],{"class":624,"line":723},[622,829,830],{"class":713},"const ",[622,832,833],{"class":802},"app",[622,835,836],{"class":798}," =",[622,838,839],{"class":713}," new ",[622,841,842],{"class":628},"Hono",[622,844,845],{"class":798},"\u003C{",[622,847,848],{"class":802}," Bindings",[622,850,851],{"class":798},": { ",[622,853,854],{"class":802},"DB",[622,856,857],{"class":798},": ",[622,859,861],{"class":860},"sSkh3","D1Database",[622,863,864],{"class":798}," } }>()\n",[622,866,867],{"class":624,"line":737},[622,868,825],{"emptyLinePlaceholder":824},[622,870,871,873,876,879,882,885,888,890,893,896,899,902,905,908],{"class":624,"line":751},[622,872,833],{"class":802},[622,874,875],{"class":798},".",[622,877,878],{"class":628},"get",[622,880,881],{"class":798},"(",[622,883,884],{"class":812},"'",[622,886,887],{"class":632},"/clients",[622,889,884],{"class":812},[622,891,892],{"class":798},",",[622,894,895],{"class":713}," async",[622,897,898],{"class":798}," (",[622,900,901],{"class":802},"c",[622,903,904],{"class":798},")",[622,906,907],{"class":798}," =>",[622,909,910],{"class":798}," {\n",[622,912,913,916,919,922,924,926,929,932,934,937,939,941,943,946],{"class":624,"line":766},[622,914,915],{"class":713},"  const ",[622,917,918],{"class":798},"{",[622,920,921],{"class":802}," results",[622,923,806],{"class":798},[622,925,836],{"class":798},[622,927,928],{"class":694}," await",[622,930,931],{"class":802}," c",[622,933,875],{"class":798},[622,935,936],{"class":802},"env",[622,938,875],{"class":798},[622,940,854],{"class":802},[622,942,875],{"class":798},[622,944,945],{"class":628},"prepare",[622,947,948],{"class":798},"(\n",[622,950,951,954,957],{"class":624,"line":775},[622,952,953],{"class":812},"    '",[622,955,956],{"class":632},"SELECT id, name, fiscal_month, tax_scheme FROM clients ORDER BY name",[622,958,819],{"class":812},[622,960,962,965,968],{"class":624,"line":961},9,[622,963,964],{"class":798},"  ).",[622,966,967],{"class":628},"all",[622,969,970],{"class":798},"()\n",[622,972,974,977,979,981,984,986,989],{"class":624,"line":973},10,[622,975,976],{"class":694},"  return",[622,978,931],{"class":802},[622,980,875],{"class":798},[622,982,983],{"class":628},"json",[622,985,881],{"class":798},[622,987,988],{"class":802},"results",[622,990,991],{"class":798},")\n",[622,993,995],{"class":624,"line":994},11,[622,996,997],{"class":798},"})\n",[622,999,1001],{"class":624,"line":1000},12,[622,1002,825],{"emptyLinePlaceholder":824},[622,1004,1006,1009,1012],{"class":624,"line":1005},13,[622,1007,1008],{"class":694},"export",[622,1010,1011],{"class":694}," default",[622,1013,1014],{"class":802}," app\n",[15,1016,1017,1020,1021,1024,1025,1028],{},[144,1018,1019],{},"wrangler deploy"," でデプロイし、独自ドメインのサブドメイン（例: ",[144,1022,1023],{},"api.example.com","）をWorkersのカスタムドメインに割り当てます。",[19,1026,1027],{},"Access はCloudflareが管理するドメインに対して効かせる","ので、ここは独自ドメイン推奨です。",[513,1030,1032],{"id":1031},"step-3-access-アプリケーションでapiを囲う","Step 3: Access アプリケーションでAPIを囲う",[200,1034,1035,1041,1047,1054],{},[33,1036,1037,1038],{},"Zero Trust ダッシュボードの ",[19,1039,1040],{},"Access → Applications → Add an application → Self-hosted",[33,1042,1043,1044,1046],{},"対象ドメインに ",[144,1045,1023],{}," を指定",[33,1048,1049,1050,1053],{},"ポリシーを作成: Action = Allow、Include = ",[144,1051,1052],{},"Emails ending in @example.com","（自社ドメイン）。ひとり事務所なら自分のメールアドレスを直接指定でもいい",[33,1055,1056,1057,1060],{},"作成後に表示される ",[19,1058,1059],{},"AUD（Application Audience）値を控える","。Step 4 の検証で使う",[15,1062,1063,1064,1067],{},"この時点で ",[144,1065,1066],{},"https://api.example.com/clients"," をブラウザで開くと、Googleログイン画面にリダイレクトされるはずです。ログインできれば成功です。",[513,1069,1071],{"id":1070},"step-4-オリジン側でjwtを再検証する","Step 4: オリジン側でJWTを再検証する",[15,1073,1074,1075,1078],{},"Workers側に検証ミドルウェアを足します。",[144,1076,1077],{},"jose"," を使うと短く書けます。",[156,1080,1082],{"className":781,"code":1081,"language":783,"meta":164,"style":164},"import { createRemoteJWKSet, jwtVerify } from 'jose'\n\nconst TEAM_DOMAIN = 'https://\u003Cteam>.cloudflareaccess.com'\nconst AUD = '\u003CStep 3で控えたAUD値>'\nconst JWKS = createRemoteJWKSet(new URL(`${TEAM_DOMAIN}/cdn-cgi/access/certs`))\n\napp.use('*', async (c, next) => {\n  const token = c.req.header('Cf-Access-Jwt-Assertion')\n  if (!token) return c.json({ error: 'missing token' }, 403)\n  try {\n    const { payload } = await jwtVerify(token, JWKS, {\n      issuer: TEAM_DOMAIN,\n      audience: AUD,\n    })\n    c.set('userEmail', payload.email as string)  // 誰の操作かをログに残せる\n  } catch {\n    return c.json({ error: 'invalid token' }, 403)\n  }\n  await next()\n})\n",[144,1083,1084,1108,1112,1128,1144,1183,1187,1224,1255,1302,1309,1340,1351,1362,1368,1408,1419,1450,1456,1466],{"__ignoreMap":164},[622,1085,1086,1088,1090,1093,1095,1098,1100,1102,1104,1106],{"class":624,"line":625},[622,1087,795],{"class":694},[622,1089,799],{"class":798},[622,1091,1092],{"class":802}," createRemoteJWKSet",[622,1094,892],{"class":798},[622,1096,1097],{"class":802}," jwtVerify",[622,1099,806],{"class":798},[622,1101,809],{"class":694},[622,1103,813],{"class":812},[622,1105,1077],{"class":632},[622,1107,819],{"class":812},[622,1109,1110],{"class":624,"line":652},[622,1111,825],{"emptyLinePlaceholder":824},[622,1113,1114,1116,1119,1121,1123,1126],{"class":624,"line":662},[622,1115,830],{"class":713},[622,1117,1118],{"class":802},"TEAM_DOMAIN",[622,1120,836],{"class":798},[622,1122,813],{"class":812},[622,1124,1125],{"class":632},"https://\u003Cteam>.cloudflareaccess.com",[622,1127,819],{"class":812},[622,1129,1130,1132,1135,1137,1139,1142],{"class":624,"line":723},[622,1131,830],{"class":713},[622,1133,1134],{"class":802},"AUD",[622,1136,836],{"class":798},[622,1138,813],{"class":812},[622,1140,1141],{"class":632},"\u003CStep 3で控えたAUD値>",[622,1143,819],{"class":812},[622,1145,1146,1148,1151,1153,1155,1157,1160,1163,1165,1168,1171,1173,1176,1178,1180],{"class":624,"line":737},[622,1147,830],{"class":713},[622,1149,1150],{"class":802},"JWKS",[622,1152,836],{"class":798},[622,1154,1092],{"class":628},[622,1156,881],{"class":798},[622,1158,1159],{"class":713},"new ",[622,1161,1162],{"class":628},"URL",[622,1164,881],{"class":798},[622,1166,1167],{"class":812},"`",[622,1169,1170],{"class":694},"${",[622,1172,1118],{"class":632},[622,1174,1175],{"class":694},"}",[622,1177,260],{"class":632},[622,1179,1167],{"class":812},[622,1181,1182],{"class":798},"))\n",[622,1184,1185],{"class":624,"line":751},[622,1186,825],{"emptyLinePlaceholder":824},[622,1188,1189,1191,1193,1196,1198,1200,1203,1205,1207,1209,1211,1213,1215,1218,1220,1222],{"class":624,"line":766},[622,1190,833],{"class":802},[622,1192,875],{"class":798},[622,1194,1195],{"class":628},"use",[622,1197,881],{"class":798},[622,1199,884],{"class":812},[622,1201,1202],{"class":632},"*",[622,1204,884],{"class":812},[622,1206,892],{"class":798},[622,1208,895],{"class":713},[622,1210,898],{"class":798},[622,1212,901],{"class":802},[622,1214,892],{"class":798},[622,1216,1217],{"class":802}," next",[622,1219,904],{"class":798},[622,1221,907],{"class":798},[622,1223,910],{"class":798},[622,1225,1226,1228,1231,1233,1235,1237,1240,1242,1245,1247,1249,1251,1253],{"class":624,"line":775},[622,1227,915],{"class":713},[622,1229,1230],{"class":802},"token",[622,1232,836],{"class":798},[622,1234,931],{"class":802},[622,1236,875],{"class":798},[622,1238,1239],{"class":802},"req",[622,1241,875],{"class":798},[622,1243,1244],{"class":628},"header",[622,1246,881],{"class":798},[622,1248,884],{"class":812},[622,1250,256],{"class":632},[622,1252,884],{"class":812},[622,1254,991],{"class":798},[622,1256,1257,1260,1262,1265,1267,1269,1272,1274,1276,1278,1281,1284,1286,1288,1291,1293,1296,1300],{"class":624,"line":961},[622,1258,1259],{"class":694},"  if",[622,1261,898],{"class":798},[622,1263,1264],{"class":713},"!",[622,1266,1230],{"class":802},[622,1268,904],{"class":798},[622,1270,1271],{"class":694}," return",[622,1273,931],{"class":802},[622,1275,875],{"class":798},[622,1277,983],{"class":628},[622,1279,1280],{"class":798},"({ ",[622,1282,1283],{"class":655},"error",[622,1285,857],{"class":798},[622,1287,884],{"class":812},[622,1289,1290],{"class":632},"missing token",[622,1292,884],{"class":812},[622,1294,1295],{"class":798}," },",[622,1297,1299],{"class":1298},"sM54T"," 403",[622,1301,991],{"class":798},[622,1303,1304,1307],{"class":624,"line":973},[622,1305,1306],{"class":694},"  try",[622,1308,910],{"class":798},[622,1310,1311,1314,1316,1319,1321,1323,1325,1327,1329,1331,1333,1336,1338],{"class":624,"line":994},[622,1312,1313],{"class":713},"    const ",[622,1315,918],{"class":798},[622,1317,1318],{"class":802}," payload",[622,1320,806],{"class":798},[622,1322,836],{"class":798},[622,1324,928],{"class":694},[622,1326,1097],{"class":628},[622,1328,881],{"class":798},[622,1330,1230],{"class":802},[622,1332,892],{"class":798},[622,1334,1335],{"class":802}," JWKS",[622,1337,892],{"class":798},[622,1339,910],{"class":798},[622,1341,1342,1345,1347,1349],{"class":624,"line":1000},[622,1343,1344],{"class":655},"      issuer",[622,1346,857],{"class":798},[622,1348,1118],{"class":802},[622,1350,720],{"class":798},[622,1352,1353,1356,1358,1360],{"class":624,"line":1005},[622,1354,1355],{"class":655},"      audience",[622,1357,857],{"class":798},[622,1359,1134],{"class":802},[622,1361,720],{"class":798},[622,1363,1365],{"class":624,"line":1364},14,[622,1366,1367],{"class":798},"    })\n",[622,1369,1371,1374,1376,1379,1381,1383,1386,1388,1390,1392,1394,1397,1400,1403,1405],{"class":624,"line":1370},15,[622,1372,1373],{"class":802},"    c",[622,1375,875],{"class":798},[622,1377,1378],{"class":628},"set",[622,1380,881],{"class":798},[622,1382,884],{"class":812},[622,1384,1385],{"class":632},"userEmail",[622,1387,884],{"class":812},[622,1389,892],{"class":798},[622,1391,1318],{"class":802},[622,1393,875],{"class":798},[622,1395,1396],{"class":802},"email",[622,1398,1399],{"class":694}," as",[622,1401,1402],{"class":860}," string",[622,1404,904],{"class":798},[622,1406,1407],{"class":688},"  // 誰の操作かをログに残せる\n",[622,1409,1411,1414,1417],{"class":624,"line":1410},16,[622,1412,1413],{"class":798},"  }",[622,1415,1416],{"class":694}," catch",[622,1418,910],{"class":798},[622,1420,1422,1425,1427,1429,1431,1433,1435,1437,1439,1442,1444,1446,1448],{"class":624,"line":1421},17,[622,1423,1424],{"class":694},"    return",[622,1426,931],{"class":802},[622,1428,875],{"class":798},[622,1430,983],{"class":628},[622,1432,1280],{"class":798},[622,1434,1283],{"class":655},[622,1436,857],{"class":798},[622,1438,884],{"class":812},[622,1440,1441],{"class":632},"invalid token",[622,1443,884],{"class":812},[622,1445,1295],{"class":798},[622,1447,1299],{"class":1298},[622,1449,991],{"class":798},[622,1451,1453],{"class":624,"line":1452},18,[622,1454,1455],{"class":798},"  }\n",[622,1457,1459,1462,1464],{"class":624,"line":1458},19,[622,1460,1461],{"class":694},"  await",[622,1463,1217],{"class":628},[622,1465,970],{"class":798},[622,1467,1469],{"class":624,"line":1468},20,[622,1470,997],{"class":798},[15,1472,1473,1475,1476,1478,1479,1482],{},[144,1474,1118],{}," と ",[144,1477,1134],{}," はコードに直書きせず、",[144,1480,1481],{},"wrangler.toml"," の vars か secret に置きます。鍵そのものはJWKSから毎回取るので、ローテーションを意識する必要はありません。",[513,1484,1486],{"id":1485},"step-5-cloudflared-を入れてcliなしで疎通確認","Step 5: cloudflared を入れてCLIなしで疎通確認",[15,1488,1489,1490,1492],{},"利用者の端末（自分のPC）に ",[144,1491,184],{}," を入れます。",[156,1494,1498],{"className":1495,"code":1496,"language":1497,"meta":164,"style":164},"language-powershell shiki shiki-themes vitesse-light vitesse-light","winget install Cloudflare.cloudflared\n","powershell",[144,1499,1500],{"__ignoreMap":164},[622,1501,1502],{"class":624,"line":625},[622,1503,1496],{},[15,1505,1506,1507,1509],{},"CLIを書く前に、",[144,1508,184],{}," 単体でトークンが取れることを確認しておくと切り分けが楽です。",[156,1511,1513],{"className":616,"code":1512,"language":618,"meta":164,"style":164},"# ブラウザが開いてGoogleログイン → トークンがローカルにキャッシュされる\ncloudflared access login https://api.example.com\n\n# キャッシュ済みトークンを取り出してAPIを叩く\ncurl -H \"cf-access-token: $(cloudflared access token --app=https://api.example.com)\" \\\n  https://api.example.com/clients\n",[144,1514,1515,1520,1533,1537,1542,1575],{"__ignoreMap":164},[622,1516,1517],{"class":624,"line":625},[622,1518,1519],{"class":688},"# ブラウザが開いてGoogleログイン → トークンがローカルにキャッシュされる\n",[622,1521,1522,1524,1527,1530],{"class":624,"line":652},[622,1523,184],{"class":628},[622,1525,1526],{"class":632}," access",[622,1528,1529],{"class":632}," login",[622,1531,1532],{"class":632}," https://api.example.com\n",[622,1534,1535],{"class":624,"line":662},[622,1536,825],{"emptyLinePlaceholder":824},[622,1538,1539],{"class":624,"line":723},[622,1540,1541],{"class":688},"# キャッシュ済みトークンを取り出してAPIを叩く\n",[622,1543,1544,1547,1550,1553,1556,1559,1561,1564,1567,1569,1572],{"class":624,"line":737},[622,1545,1546],{"class":628},"curl",[622,1548,1549],{"class":642}," -H",[622,1551,1552],{"class":812}," \"",[622,1554,1555],{"class":632},"cf-access-token: ",[622,1557,1558],{"class":798},"$(",[622,1560,184],{"class":628},[622,1562,1563],{"class":632}," access token ",[622,1565,1566],{"class":642},"--app=https://api.example.com",[622,1568,904],{"class":798},[622,1570,1571],{"class":812},"\"",[622,1573,1574],{"class":642}," \\\n",[622,1576,1577],{"class":624,"line":751},[622,1578,1579],{"class":632},"  https://api.example.com/clients\n",[15,1581,1582],{},"JSONが返ってくれば、認証基盤は完成です。",[513,1584,1586],{"id":1585},"step-6-cli-を作るmcp-サブコマンド込み","Step 6: CLI を作る（mcp サブコマンド込み）",[15,1588,1589,1590,1593],{},"CLIの本体は「",[144,1591,1592],{},"cloudflared access token"," を呼んでヘッダに載せ、APIを叩く」だけの薄いラッパーです。",[156,1595,1597],{"className":781,"code":1596,"language":783,"meta":164,"style":164},"// トークン取得は cloudflared に丸投げする（これが秘密情報ゼロの正体）\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst exec = promisify(execFile)\nconst API = process.env.JIMUSHO_API_URL ?? 'https://api.example.com'\n\nconst getToken = async (): Promise\u003Cstring> => {\n  const { stdout } = await exec('cloudflared', ['access', 'token', `--app=${API}`])\n  return stdout.trim()\n}\n\nexport const apiGet = async (path: string): Promise\u003Cunknown> => {\n  const res = await fetch(`${API}${path}`, {\n    headers: { 'cf-access-token': await getToken() },\n  })\n  if (res.status === 401 || res.status === 403) {\n    // 期限切れ → 一度だけログインし直して再試行\n    await exec('cloudflared', ['access', 'login', API])\n    const retry = await fetch(`${API}${path}`, {\n      headers: { 'cf-access-token': await getToken() },\n    })\n    if (!retry.ok) throw new Error(`API error: ${retry.status}`)\n    return retry.json()\n  }\n  if (!res.ok) throw new Error(`API error: ${res.status}`)\n  return res.json()\n}\n",[144,1598,1599,1604,1624,1644,1648,1666,1696,1700,1731,1796,1809,1814,1818,1857,1892,1916,1921,1958,1963,2004,2037,2059,2064,2112,2126,2131,2174,2188],{"__ignoreMap":164},[622,1600,1601],{"class":624,"line":625},[622,1602,1603],{"class":688},"// トークン取得は cloudflared に丸投げする（これが秘密情報ゼロの正体）\n",[622,1605,1606,1608,1610,1613,1615,1617,1619,1622],{"class":624,"line":652},[622,1607,795],{"class":694},[622,1609,799],{"class":798},[622,1611,1612],{"class":802}," execFile",[622,1614,806],{"class":798},[622,1616,809],{"class":694},[622,1618,813],{"class":812},[622,1620,1621],{"class":632},"node:child_process",[622,1623,819],{"class":812},[622,1625,1626,1628,1630,1633,1635,1637,1639,1642],{"class":624,"line":662},[622,1627,795],{"class":694},[622,1629,799],{"class":798},[622,1631,1632],{"class":802}," promisify",[622,1634,806],{"class":798},[622,1636,809],{"class":694},[622,1638,813],{"class":812},[622,1640,1641],{"class":632},"node:util",[622,1643,819],{"class":812},[622,1645,1646],{"class":624,"line":723},[622,1647,825],{"emptyLinePlaceholder":824},[622,1649,1650,1652,1655,1657,1659,1661,1664],{"class":624,"line":737},[622,1651,830],{"class":713},[622,1653,1654],{"class":802},"exec",[622,1656,836],{"class":798},[622,1658,1632],{"class":628},[622,1660,881],{"class":798},[622,1662,1663],{"class":802},"execFile",[622,1665,991],{"class":798},[622,1667,1668,1670,1672,1674,1677,1679,1681,1683,1686,1689,1691,1694],{"class":624,"line":751},[622,1669,830],{"class":713},[622,1671,107],{"class":802},[622,1673,836],{"class":798},[622,1675,1676],{"class":802}," process",[622,1678,875],{"class":798},[622,1680,936],{"class":802},[622,1682,875],{"class":798},[622,1684,1685],{"class":802},"JIMUSHO_API_URL",[622,1687,1688],{"class":713}," ?? ",[622,1690,884],{"class":812},[622,1692,1693],{"class":632},"https://api.example.com",[622,1695,819],{"class":812},[622,1697,1698],{"class":624,"line":766},[622,1699,825],{"emptyLinePlaceholder":824},[622,1701,1702,1704,1707,1709,1712,1715,1718,1721,1724,1727,1729],{"class":624,"line":775},[622,1703,830],{"class":713},[622,1705,1706],{"class":628},"getToken",[622,1708,836],{"class":798},[622,1710,1711],{"class":713}," async ",[622,1713,1714],{"class":798},"():",[622,1716,1717],{"class":860}," Promise",[622,1719,1720],{"class":798},"\u003C",[622,1722,1723],{"class":860},"string",[622,1725,1726],{"class":798},">",[622,1728,907],{"class":798},[622,1730,910],{"class":798},[622,1732,1733,1735,1737,1740,1742,1744,1746,1749,1751,1753,1755,1757,1759,1762,1764,1767,1769,1771,1773,1775,1777,1779,1782,1785,1787,1789,1791,1793],{"class":624,"line":961},[622,1734,915],{"class":713},[622,1736,918],{"class":798},[622,1738,1739],{"class":802}," stdout",[622,1741,806],{"class":798},[622,1743,836],{"class":798},[622,1745,928],{"class":694},[622,1747,1748],{"class":628}," exec",[622,1750,881],{"class":798},[622,1752,884],{"class":812},[622,1754,184],{"class":632},[622,1756,884],{"class":812},[622,1758,892],{"class":798},[622,1760,1761],{"class":798}," [",[622,1763,884],{"class":812},[622,1765,1766],{"class":632},"access",[622,1768,884],{"class":812},[622,1770,892],{"class":798},[622,1772,813],{"class":812},[622,1774,1230],{"class":632},[622,1776,884],{"class":812},[622,1778,892],{"class":798},[622,1780,1781],{"class":812}," `",[622,1783,1784],{"class":632},"--app=",[622,1786,1170],{"class":694},[622,1788,107],{"class":632},[622,1790,1175],{"class":694},[622,1792,1167],{"class":812},[622,1794,1795],{"class":798},"])\n",[622,1797,1798,1800,1802,1804,1807],{"class":624,"line":973},[622,1799,976],{"class":694},[622,1801,1739],{"class":802},[622,1803,875],{"class":798},[622,1805,1806],{"class":628},"trim",[622,1808,970],{"class":798},[622,1810,1811],{"class":624,"line":994},[622,1812,1813],{"class":798},"}\n",[622,1815,1816],{"class":624,"line":1000},[622,1817,825],{"emptyLinePlaceholder":824},[622,1819,1820,1822,1825,1828,1830,1832,1834,1837,1839,1841,1844,1846,1848,1851,1853,1855],{"class":624,"line":1005},[622,1821,1008],{"class":694},[622,1823,1824],{"class":713}," const ",[622,1826,1827],{"class":628},"apiGet",[622,1829,836],{"class":798},[622,1831,1711],{"class":713},[622,1833,881],{"class":798},[622,1835,1836],{"class":802},"path",[622,1838,857],{"class":798},[622,1840,1723],{"class":860},[622,1842,1843],{"class":798},"):",[622,1845,1717],{"class":860},[622,1847,1720],{"class":798},[622,1849,1850],{"class":860},"unknown",[622,1852,1726],{"class":798},[622,1854,907],{"class":798},[622,1856,910],{"class":798},[622,1858,1859,1861,1864,1866,1868,1871,1873,1875,1877,1879,1882,1884,1886,1888,1890],{"class":624,"line":1364},[622,1860,915],{"class":713},[622,1862,1863],{"class":802},"res",[622,1865,836],{"class":798},[622,1867,928],{"class":694},[622,1869,1870],{"class":628}," fetch",[622,1872,881],{"class":798},[622,1874,1167],{"class":812},[622,1876,1170],{"class":694},[622,1878,107],{"class":632},[622,1880,1881],{"class":694},"}${",[622,1883,1836],{"class":632},[622,1885,1175],{"class":694},[622,1887,1167],{"class":812},[622,1889,892],{"class":798},[622,1891,910],{"class":798},[622,1893,1894,1897,1899,1901,1903,1905,1907,1910,1913],{"class":624,"line":1370},[622,1895,1896],{"class":655},"    headers",[622,1898,851],{"class":798},[622,1900,884],{"class":812},[622,1902,246],{"class":632},[622,1904,884],{"class":812},[622,1906,857],{"class":798},[622,1908,1909],{"class":694},"await",[622,1911,1912],{"class":628}," getToken",[622,1914,1915],{"class":798},"() },\n",[622,1917,1918],{"class":624,"line":1410},[622,1919,1920],{"class":798},"  })\n",[622,1922,1923,1925,1927,1929,1931,1934,1937,1940,1943,1945,1947,1949,1951,1954,1956],{"class":624,"line":1421},[622,1924,1259],{"class":694},[622,1926,898],{"class":798},[622,1928,1863],{"class":802},[622,1930,875],{"class":798},[622,1932,1933],{"class":802},"status",[622,1935,1936],{"class":713}," === ",[622,1938,1939],{"class":1298},"401",[622,1941,1942],{"class":713}," || ",[622,1944,1863],{"class":802},[622,1946,875],{"class":798},[622,1948,1933],{"class":802},[622,1950,1936],{"class":713},[622,1952,1953],{"class":1298},"403",[622,1955,904],{"class":798},[622,1957,910],{"class":798},[622,1959,1960],{"class":624,"line":1452},[622,1961,1962],{"class":688},"    // 期限切れ → 一度だけログインし直して再試行\n",[622,1964,1965,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1995,1997,1999,2002],{"class":624,"line":1458},[622,1966,1967],{"class":694},"    await",[622,1969,1748],{"class":628},[622,1971,881],{"class":798},[622,1973,884],{"class":812},[622,1975,184],{"class":632},[622,1977,884],{"class":812},[622,1979,892],{"class":798},[622,1981,1761],{"class":798},[622,1983,884],{"class":812},[622,1985,1766],{"class":632},[622,1987,884],{"class":812},[622,1989,892],{"class":798},[622,1991,813],{"class":812},[622,1993,1994],{"class":632},"login",[622,1996,884],{"class":812},[622,1998,892],{"class":798},[622,2000,2001],{"class":802}," API",[622,2003,1795],{"class":798},[622,2005,2006,2008,2011,2013,2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035],{"class":624,"line":1468},[622,2007,1313],{"class":713},[622,2009,2010],{"class":802},"retry",[622,2012,836],{"class":798},[622,2014,928],{"class":694},[622,2016,1870],{"class":628},[622,2018,881],{"class":798},[622,2020,1167],{"class":812},[622,2022,1170],{"class":694},[622,2024,107],{"class":632},[622,2026,1881],{"class":694},[622,2028,1836],{"class":632},[622,2030,1175],{"class":694},[622,2032,1167],{"class":812},[622,2034,892],{"class":798},[622,2036,910],{"class":798},[622,2038,2040,2043,2045,2047,2049,2051,2053,2055,2057],{"class":624,"line":2039},21,[622,2041,2042],{"class":655},"      headers",[622,2044,851],{"class":798},[622,2046,884],{"class":812},[622,2048,246],{"class":632},[622,2050,884],{"class":812},[622,2052,857],{"class":798},[622,2054,1909],{"class":694},[622,2056,1912],{"class":628},[622,2058,1915],{"class":798},[622,2060,2062],{"class":624,"line":2061},22,[622,2063,1367],{"class":798},[622,2065,2067,2070,2072,2074,2076,2078,2081,2083,2086,2088,2091,2093,2095,2098,2100,2102,2104,2106,2108,2110],{"class":624,"line":2066},23,[622,2068,2069],{"class":694},"    if",[622,2071,898],{"class":798},[622,2073,1264],{"class":713},[622,2075,2010],{"class":802},[622,2077,875],{"class":798},[622,2079,2080],{"class":802},"ok",[622,2082,904],{"class":798},[622,2084,2085],{"class":694}," throw",[622,2087,839],{"class":713},[622,2089,2090],{"class":628},"Error",[622,2092,881],{"class":798},[622,2094,1167],{"class":812},[622,2096,2097],{"class":632},"API error: ",[622,2099,1170],{"class":694},[622,2101,2010],{"class":632},[622,2103,875],{"class":798},[622,2105,1933],{"class":632},[622,2107,1175],{"class":694},[622,2109,1167],{"class":812},[622,2111,991],{"class":798},[622,2113,2115,2117,2120,2122,2124],{"class":624,"line":2114},24,[622,2116,1424],{"class":694},[622,2118,2119],{"class":802}," retry",[622,2121,875],{"class":798},[622,2123,983],{"class":628},[622,2125,970],{"class":798},[622,2127,2129],{"class":624,"line":2128},25,[622,2130,1455],{"class":798},[622,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172],{"class":624,"line":2133},26,[622,2135,1259],{"class":694},[622,2137,898],{"class":798},[622,2139,1264],{"class":713},[622,2141,1863],{"class":802},[622,2143,875],{"class":798},[622,2145,2080],{"class":802},[622,2147,904],{"class":798},[622,2149,2085],{"class":694},[622,2151,839],{"class":713},[622,2153,2090],{"class":628},[622,2155,881],{"class":798},[622,2157,1167],{"class":812},[622,2159,2097],{"class":632},[622,2161,1170],{"class":694},[622,2163,1863],{"class":632},[622,2165,875],{"class":798},[622,2167,1933],{"class":632},[622,2169,1175],{"class":694},[622,2171,1167],{"class":812},[622,2173,991],{"class":798},[622,2175,2177,2179,2182,2184,2186],{"class":624,"line":2176},27,[622,2178,976],{"class":694},[622,2180,2181],{"class":802}," res",[622,2183,875],{"class":798},[622,2185,983],{"class":628},[622,2187,970],{"class":798},[622,2189,2191],{"class":624,"line":2190},28,[622,2192,1813],{"class":798},[15,2194,2195,2196,2198],{},"MCPサーバは同じ ",[144,2197,1827],{}," を使い回して、参照系ツールだけを公開します。",[156,2200,2202],{"className":781,"code":2201,"language":783,"meta":164,"style":164},"// `jimusho mcp` で起動する部分\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\n\nconst server = new McpServer({ name: 'jimusho', version: '1.0.0' })\n\nserver.tool(\n  'list_clients',\n  '顧問先の一覧（決算月・消費税方式つき）を取得する',\n  {},\n  async () => ({\n    content: [{ type: 'text', text: JSON.stringify(await apiGet('/clients')) }],\n  })\n)\n\nawait server.connect(new StdioServerTransport())\n// 注意: console.log は使わない（stdoutはプロトコル専用）。ログは console.error へ\n",[144,2203,2204,2209,2229,2249,2269,2273,2319,2323,2334,2346,2357,2362,2375,2426,2430,2434,2438,2460],{"__ignoreMap":164},[622,2205,2206],{"class":624,"line":625},[622,2207,2208],{"class":688},"// `jimusho mcp` で起動する部分\n",[622,2210,2211,2213,2215,2218,2220,2222,2224,2227],{"class":624,"line":652},[622,2212,795],{"class":694},[622,2214,799],{"class":798},[622,2216,2217],{"class":802}," McpServer",[622,2219,806],{"class":798},[622,2221,809],{"class":694},[622,2223,813],{"class":812},[622,2225,2226],{"class":632},"@modelcontextprotocol/sdk/server/mcp.js",[622,2228,819],{"class":812},[622,2230,2231,2233,2235,2238,2240,2242,2244,2247],{"class":624,"line":662},[622,2232,795],{"class":694},[622,2234,799],{"class":798},[622,2236,2237],{"class":802}," StdioServerTransport",[622,2239,806],{"class":798},[622,2241,809],{"class":694},[622,2243,813],{"class":812},[622,2245,2246],{"class":632},"@modelcontextprotocol/sdk/server/stdio.js",[622,2248,819],{"class":812},[622,2250,2251,2253,2255,2258,2260,2262,2264,2267],{"class":624,"line":723},[622,2252,795],{"class":694},[622,2254,799],{"class":798},[622,2256,2257],{"class":802}," z",[622,2259,806],{"class":798},[622,2261,809],{"class":694},[622,2263,813],{"class":812},[622,2265,2266],{"class":632},"zod",[622,2268,819],{"class":812},[622,2270,2271],{"class":624,"line":737},[622,2272,825],{"emptyLinePlaceholder":824},[622,2274,2275,2277,2280,2282,2284,2287,2289,2292,2294,2296,2299,2301,2304,2307,2309,2311,2314,2316],{"class":624,"line":751},[622,2276,830],{"class":713},[622,2278,2279],{"class":802},"server",[622,2281,836],{"class":798},[622,2283,839],{"class":713},[622,2285,2286],{"class":628},"McpServer",[622,2288,1280],{"class":798},[622,2290,2291],{"class":655},"name",[622,2293,857],{"class":798},[622,2295,884],{"class":812},[622,2297,2298],{"class":632},"jimusho",[622,2300,884],{"class":812},[622,2302,2303],{"class":798},", ",[622,2305,2306],{"class":655},"version",[622,2308,857],{"class":798},[622,2310,884],{"class":812},[622,2312,2313],{"class":632},"1.0.0",[622,2315,884],{"class":812},[622,2317,2318],{"class":798}," })\n",[622,2320,2321],{"class":624,"line":766},[622,2322,825],{"emptyLinePlaceholder":824},[622,2324,2325,2327,2329,2332],{"class":624,"line":775},[622,2326,2279],{"class":802},[622,2328,875],{"class":798},[622,2330,2331],{"class":628},"tool",[622,2333,948],{"class":798},[622,2335,2336,2339,2342,2344],{"class":624,"line":961},[622,2337,2338],{"class":812},"  '",[622,2340,2341],{"class":632},"list_clients",[622,2343,884],{"class":812},[622,2345,720],{"class":798},[622,2347,2348,2350,2353,2355],{"class":624,"line":973},[622,2349,2338],{"class":812},[622,2351,2352],{"class":632},"顧問先の一覧（決算月・消費税方式つき）を取得する",[622,2354,884],{"class":812},[622,2356,720],{"class":798},[622,2358,2359],{"class":624,"line":994},[622,2360,2361],{"class":798},"  {},\n",[622,2363,2364,2367,2370,2372],{"class":624,"line":1000},[622,2365,2366],{"class":713},"  async",[622,2368,2369],{"class":798}," ()",[622,2371,907],{"class":798},[622,2373,2374],{"class":798}," ({\n",[622,2376,2377,2380,2383,2386,2388,2390,2392,2394,2396,2398,2400,2403,2405,2408,2410,2412,2415,2417,2419,2421,2423],{"class":624,"line":1005},[622,2378,2379],{"class":655},"    content",[622,2381,2382],{"class":798},": [{ ",[622,2384,2385],{"class":655},"type",[622,2387,857],{"class":798},[622,2389,884],{"class":812},[622,2391,161],{"class":632},[622,2393,884],{"class":812},[622,2395,2303],{"class":798},[622,2397,161],{"class":655},[622,2399,857],{"class":798},[622,2401,2402],{"class":802},"JSON",[622,2404,875],{"class":798},[622,2406,2407],{"class":628},"stringify",[622,2409,881],{"class":798},[622,2411,1909],{"class":694},[622,2413,2414],{"class":628}," apiGet",[622,2416,881],{"class":798},[622,2418,884],{"class":812},[622,2420,887],{"class":632},[622,2422,884],{"class":812},[622,2424,2425],{"class":798},")) }],\n",[622,2427,2428],{"class":624,"line":1364},[622,2429,1920],{"class":798},[622,2431,2432],{"class":624,"line":1370},[622,2433,991],{"class":798},[622,2435,2436],{"class":624,"line":1410},[622,2437,825],{"emptyLinePlaceholder":824},[622,2439,2440,2442,2445,2447,2450,2452,2455,2457],{"class":624,"line":1421},[622,2441,1909],{"class":694},[622,2443,2444],{"class":802}," server",[622,2446,875],{"class":798},[622,2448,2449],{"class":628},"connect",[622,2451,881],{"class":798},[622,2453,2454],{"class":713},"new",[622,2456,2237],{"class":628},[622,2458,2459],{"class":798},"())\n",[622,2461,2462],{"class":624,"line":1452},[622,2463,2464],{"class":688},"// 注意: console.log は使わない（stdoutはプロトコル専用）。ログは console.error へ\n",[513,2466,2468],{"id":2467},"step-7-claude-code-claude-desktop-に登録する","Step 7: Claude Code / Claude Desktop に登録する",[15,2470,2471],{},"Claude Code なら1コマンドです。",[156,2473,2475],{"className":616,"code":2474,"language":618,"meta":164,"style":164},"claude mcp add jimusho -- jimusho mcp\n",[144,2476,2477],{"__ignoreMap":164},[622,2478,2479,2482,2485,2488,2491,2493,2495],{"class":624,"line":625},[622,2480,2481],{"class":628},"claude",[622,2483,2484],{"class":632}," mcp",[622,2486,2487],{"class":632}," add",[622,2489,2490],{"class":632}," jimusho",[622,2492,643],{"class":642},[622,2494,2490],{"class":632},[622,2496,2497],{"class":632}," mcp\n",[15,2499,2500],{},"Claude Desktop なら設定ファイルに追記します。",[156,2502,2505],{"className":2503,"code":2504,"language":983,"meta":164,"style":164},"language-json shiki shiki-themes vitesse-light vitesse-light","{\n  \"mcpServers\": {\n    \"jimusho\": {\n      \"command\": \"jimusho\",\n      \"args\": [\"mcp\"],\n      \"env\": { \"JIMUSHO_API_URL\": \"https://api.example.com\" }\n    }\n  }\n}\n",[144,2506,2507,2512,2528,2541,2561,2584,2613,2618,2622],{"__ignoreMap":164},[622,2508,2509],{"class":624,"line":625},[622,2510,2511],{"class":798},"{\n",[622,2513,2514,2518,2521,2523,2526],{"class":624,"line":652},[622,2515,2517],{"class":2516},"sqvqQ","  \"",[622,2519,2520],{"class":655},"mcpServers",[622,2522,1571],{"class":2516},[622,2524,2525],{"class":798},":",[622,2527,910],{"class":798},[622,2529,2530,2533,2535,2537,2539],{"class":624,"line":662},[622,2531,2532],{"class":2516},"    \"",[622,2534,2298],{"class":655},[622,2536,1571],{"class":2516},[622,2538,2525],{"class":798},[622,2540,910],{"class":798},[622,2542,2543,2546,2549,2551,2553,2555,2557,2559],{"class":624,"line":723},[622,2544,2545],{"class":2516},"      \"",[622,2547,2548],{"class":655},"command",[622,2550,1571],{"class":2516},[622,2552,2525],{"class":798},[622,2554,1552],{"class":812},[622,2556,2298],{"class":632},[622,2558,1571],{"class":812},[622,2560,720],{"class":798},[622,2562,2563,2565,2568,2570,2572,2574,2576,2579,2581],{"class":624,"line":737},[622,2564,2545],{"class":2516},[622,2566,2567],{"class":655},"args",[622,2569,1571],{"class":2516},[622,2571,2525],{"class":798},[622,2573,1761],{"class":798},[622,2575,1571],{"class":812},[622,2577,2578],{"class":632},"mcp",[622,2580,1571],{"class":812},[622,2582,2583],{"class":798},"],\n",[622,2585,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610],{"class":624,"line":751},[622,2587,2545],{"class":2516},[622,2589,936],{"class":655},[622,2591,1571],{"class":2516},[622,2593,2525],{"class":798},[622,2595,799],{"class":798},[622,2597,1552],{"class":2516},[622,2599,1685],{"class":655},[622,2601,1571],{"class":2516},[622,2603,2525],{"class":798},[622,2605,1552],{"class":812},[622,2607,1693],{"class":632},[622,2609,1571],{"class":812},[622,2611,2612],{"class":798}," }\n",[622,2614,2615],{"class":624,"line":766},[622,2616,2617],{"class":798},"    }\n",[622,2619,2620],{"class":624,"line":775},[622,2621,1455],{"class":798},[622,2623,2624],{"class":624,"line":961},[622,2625,1813],{"class":798},[15,2627,2628],{},"これで「○○社の決算月と消費税の方式を教えて」とClaudeに聞くと、MCP経由でAPIを叩いて答えてくれます。AIに渡した認証情報はゼロ。使われるのは自分がブラウザで済ませたGoogleログインのセッションだけです。",[513,2630,2632],{"id":2631},"step-8-配布-ひとり事務所なら省略していい","Step 8: 配布 ― ひとり事務所なら省略していい",[15,2634,2635,2636],{},"GitHub Packages での非公開配布は「複数人にCLIを配る」ための仕組みなので、",[19,2637,2638],{},"ひとり〜数人の事務所なら最初は省略できます。",[30,2640,2641,2655],{},[33,2642,2643,2646,2647,2650,2651,2654],{},[19,2644,2645],{},"ひとりの間",": private リポジトリを clone して ",[144,2648,2649],{},"npm link","（または ",[144,2652,2653],{},"npm install -g .","）で足りる",[33,2656,2657,2660],{},[19,2658,2659],{},"スタッフに配り始めたら",": GitHub Organization を作り、本文前半のとおり GitHub Packages + Actions に移行する。スタッフのGitHubアカウントをOrgに入れることが配布のゲートになる",[15,2662,2663],{},"順番として「認証基盤（Step 1〜5）が先、配布の仕組みは人が増えてから」で問題ありません。セキュリティ境界は Access 側にあるからです。",[513,2665,2666],{"id":2666},"運用で決めておくこと",[30,2668,2669,2675,2681,2687],{},[33,2670,2671,2674],{},[19,2672,2673],{},"載せない情報を文書化する",": マイナンバー・口座番号・本人確認書類の類はAPIに入れない、と最初に書き残しておく。後から「ちょっとだけ」と載せ始めるのがいちばん危ない",[33,2676,2677,2680],{},[19,2678,2679],{},"スタッフの退職時",": Google Workspace のアカウント停止＋（配布をGitHubに移行済みなら）Orgからの除外。この2操作で入手経路もアクセス権も消える",[33,2682,2683,2686],{},[19,2684,2685],{},"freee等の外部SaaSとの関係",": 外部SaaSのデータを丸ごと複製するのではなく、「AIが頻繁に参照する要約・マスタ情報」だけをこのAPIに置く。正本が二重になると更新漏れの温床になる",[33,2688,2689,857,2692,2695],{},[19,2690,2691],{},"ローカル開発",[144,2693,2694],{},"wrangler dev"," ではAccessを通らないので、開発時だけ検証ミドルウェアをスキップするフラグを持たせる。ただしそのフラグが本番ビルドに混ざらないことをデプロイ前チェックに入れる",[71,2697,2698],{"id":2698},"まとめ",[30,2700,2701,2707,2713,2716,2719,2722],{},[33,2702,2703,2704],{},"APIキーは配らない。",[19,2705,2706],{},"API自体をIdPログインで守り、CLIとMCPがその認証を共有する",[33,2708,2709,2710,2712],{},"トークンの取得・キャッシュは ",[144,2711,184],{}," の仕事。CLIにもMCPにも秘密情報は置かない",[33,2714,2715],{},"検証はエッジとオリジンの二段階。オリジンではJWKSでissuerとaudienceまで確認する",[33,2717,2718],{},"「誰が社員か」はIdPだけが知っている。入退社はIdPの操作1回で全経路に反映される",[33,2720,2721],{},"CLIとMCPは同一バイナリ。人間とAIが同じ認可で同じデータを見る。AIには参照系だけ公開する",[33,2723,2724],{},"50人以下なら費用はIdP代だけ。ひとり税理士事務所なら、配布の仕組みは後回しにして認証基盤から始めればいい",[15,2726,2727],{},"「AIに業務の文脈を読ませたい。でもAPIキーをAIに渡すのは嫌だ」という人にとって、現時点でいちばん筋のいい落とし所だと思います。",[2729,2730,2731],"style",{},"html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sSkh3, html code.shiki .sSkh3{--shiki-default:#2E8F82;--shiki-dark:#2E8F82}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sqvqQ, html code.shiki .sqvqQ{--shiki-default:#99841877;--shiki-dark:#99841877}",{"title":164,"searchDepth":652,"depth":652,"links":2733},[2734,2735,2736,2737,2738,2739,2740,2741,2742,2754],{"id":73,"depth":652,"text":74},{"id":194,"depth":652,"text":195},{"id":231,"depth":652,"text":232},{"id":274,"depth":652,"text":275},{"id":302,"depth":652,"text":303},{"id":379,"depth":652,"text":380},{"id":437,"depth":652,"text":438},{"id":469,"depth":652,"text":470},{"id":493,"depth":652,"text":494,"children":2743},[2744,2745,2746,2747,2748,2749,2750,2751,2752,2753],{"id":515,"depth":662,"text":516},{"id":575,"depth":662,"text":576},{"id":609,"depth":662,"text":610},{"id":1031,"depth":662,"text":1032},{"id":1070,"depth":662,"text":1071},{"id":1485,"depth":662,"text":1486},{"id":1585,"depth":662,"text":1586},{"id":2467,"depth":662,"text":2468},{"id":2631,"depth":662,"text":2632},{"id":2666,"depth":662,"text":2666},{"id":2698,"depth":652,"text":2698},null,"APIキーの発行・配布・失効管理をやめて、API自体をIdP認証で守る。CLIとMCPを同一バイナリにして認証を共有する構成の解説と、税理士事務所に導入するときの具体的な手順。","md",{},"/internal-cli-mcp-cloudflare-access",false,"2026-06-12T00:00:00.000Z",{"title":5,"description":2756},"2026-06/2026-06-12/internal-cli-mcp-cloudflare-access",[2765,341,120,2766,2767,2768],"Cloudflare","認証","Zero Trust","税理士業務","Q_KpvVKXdrzaLJRZmIWM6aiInfQOWu3j2SPOngVwkTc",[2771,2780,2790,2799,2809],{"title":2772,"description":2773,"path":2774,"tags":2775,"publishedAt":2779,"updatedAt":2755},"クラウド会計公式API・OAuth認証の試行錯誤ログ - APIキーからOAuthアプリ登録まで","クラウド会計サービスのAPIキー取得からOAuth認証まで段階的に突破した記録。MCPのOAuthトークンでREST APIを直接叩けることを発見し、最終的に自前OAuthアプリでrefresh_token（540日有効）を取得するまでの過程","/mf-api-oauth-authentication-journey",[2776,2777,107,341,2766,2778],"クラウド会計","OAuth","会計","2026-03-26T00:00:00.000Z",{"title":2781,"description":2782,"path":2783,"tags":2784,"publishedAt":2789,"updatedAt":2755},"Nuxt の pnpm generate を measure-deploy.ps1 でフェーズ別に計測した","Cloudflare Pages デプロイの所要時間がどこで膨らんでいるか掴めず、PowerShell スクリプトで pnpm generate と wrangler deploy をフェーズ別に計測した。途中で pnpm generate が exit 1 で落ち、原因を verify-blog-payload.mjs まで追いかけ、Opus 4.7 1M context にモデルを切り替えてから Codex でレビューを受けて締めた一日。","/nuxt-build-deploy-time-measurement",[2785,2765,2786,2787,2788],"Nuxt","ビルド時間","計測","PowerShell","2026-06-04T00:00:00.000Z",{"title":2791,"description":2792,"path":2793,"tags":2794,"publishedAt":2798,"updatedAt":2755},"非公開記事一覧をGoogleにインデックスさせない仕組みを点検した話","/blog/unpublished に置いた非公開記事を検索エンジンに載せたくない。noindex meta を入れる前に、本番ビルドから6経路で除外し本番URLを404にする既存実装が要件を満たしていると分かった点検記録。","/unpublished-noindex",[2795,2796,2785,2797,2765],"SEO","noindex","SSG","2026-06-01T00:00:00.000Z",{"title":2800,"description":2801,"path":2802,"tags":2803,"publishedAt":2808,"updatedAt":2755},"912件の自己ループリダイレクトとOG画像署名切れを一気に直した日 — 本番障害2件をAIに調査させて根本原因まで詰めた記録","ユーザーの目視で発覚した2件の本番障害を、Claude Codeに git blame と secret rotation を代行させて根本原因まで詰めた。デプロイは473秒から293秒に短縮。","/og-image-redirect-loop-fix",[2765,2804,2805,2806,2807],"OGP","リダイレクト","デプロイ","本番障害","2026-05-28T00:00:00.000Z",{"title":2810,"description":2811,"path":2812,"tags":2813,"publishedAt":2808,"updatedAt":2755},"Claude Code の UI レビュー、Playwright じゃなくて Chrome DevTools MCP でも同じことはできる","Claude Code に Playwright を組み合わせて UI レビューを自動化する記事を読んで、Chrome DevTools MCP で代替可能かを整理した。技術的にはほぼ完全に代替できる。Playwright が効くのは決定論性・CI実行・コンテキスト効率の3点で、記事の本当の価値はツール選択ではなく評価軸の言語化と自走ループ設計にある、という話。","/ui-review-mcp-vs-playwright",[2814,2815,2816,2817,341],"Claude Code","Chrome DevTools MCP","Playwright","UIレビュー",[],"https://log.eurekapu.com/og/blog/internal-cli-mcp-cloudflare-access.png?v=2026-06-12T00%3A00%3A00.000Z&title=API%E3%82%AD%E3%83%BC%E3%82%92%E9%85%8D%E3%82%89%E3%81%9A%E3%81%AB%E3%80%8C%E7%A4%BE%E5%86%85%E3%81%AE%E4%BA%BA%E3%81%A8AI%E3%81%A0%E3%81%91%E3%81%8C%E4%BD%BF%E3%81%88%E3%82%8BCLI%2FMCP%E3%80%8D%E3%82%92%E4%BD%9C%E3%82%8B%20%E2%80%95%20Cloudflare%20Access%20%C3%97%20IdP%E8%AA%8D%E8%A8%BC&author=Kei%20Komatsu&sig=39ed11ecc35e9630",1781333884238]