every Tech Blog

株式会社エブリーのTech Blogです。

オントロジーと組織OSとこれからのエンジニア

タイトル - オントロジーと組織OSとこれからのエンジニア

株式会社エブリーでCTOを務めている今井( @imakei_ )です。

本日からエブリーの開発部長陣が考える組織とAIについて 全10本にわたって開発部長陣とリレー形式で、ブログを投稿します!

今回はその AIブログリレー 第1本目 で、「オントロジー」と「組織OS」という考え方を入り口に、これからのエンジニア組織の役割について書きたいと思います。 結論から言うと、この組織OSづくりは、エンジニア組織が主導すべきだと考えています。

オントロジーと組織OS

まず前提を簡単に整理します。

弊社エブリーはAIファーストカンパニーとして、プロダクトでの活用はもちろん、開発現場でもAIの実用的な活用を進めてきました。その延長で最近考えているのが、「組織そのものを、AIが動けるOSにしていく」というテーマです。

ここで鍵になるのが「オントロジー」という考え方です。簡単に言えば、会社の現実を、人間とAIの双方が同じ意味で扱える共通言語として定義したもの、と捉えてください。中身は3つの要素で考えると分かりやすいです。ここでは、商品を扱うようなサービスを例に挙げると以下のようになります。

  • 名詞:「顧客」「注文」「在庫」が、具体的にどのデータの何を指すのか
  • 動詞:それに対して「返金する」「担当を変更する」など、何ができるのか
  • ルール:誰が閲覧・実行してよいか、変更履歴をどう残すか

この共通言語の上で、人とAIエージェントがともに意思決定を回せる状態——それを本記事では「組織OS」と呼びます。データを溜める基盤ではなく、意思決定が回り続ける基盤を作る、というイメージです。

そして本題は、これを誰が主導するのか、です。経営企画なのか、DX推進部門なのか、データ基盤チームなのか。自分は、エンジニア組織が主導すべきだと考えています。

なぜエンジニア組織が主導すべきなのか

理由を4つに分けて述べます。

1. 「現実の散らかり」を最もよく知っているから

弊社はデリッシュキッチン・トモニテ・TIMELINEと複数ドメインのプロダクトを展開しており、「顧客」という言葉ひとつとっても、サービスやシステムごとに定義もIDも異なります。

この散らかりを、日々データとシステムに向き合う中で肌感として把握しているのはエンジニアです。どこに正となるデータがあり、どこが信用でき、どこをつなぐと何が壊れるか。この地図を持っているのは現場のエンジニアであり、オントロジーの「名詞」を本当の意味で定義できるのもここを知っている人だと考えています。

2. 「動詞」を安全に実装できるのはエンジニアだから

オントロジーは名詞だけでは動きません。「返金する」「担当を変更する」といった動詞があって、初めて意思決定が現実に反映されます。

そしてこの動詞を、前提条件をチェックし、実行ログを残し、必要に応じて本番反映前にシミュレーションできる——そうした安全な形で実装するのは、まさにエンジニアリングの仕事です。ここはビジネス側がどれだけ要件を整理しても代替できない領域だと考えています。

3. AIを現実に接地させるのはエンジニアの仕事だから

エージェントを業務に組み込むうえで最も重要なのは、現実のモノと操作に正しく接続されているかどうかです。MCPでツールを提供するにしても、権限を紐づけて監査可能にするにしても、これはすべてエンジニアリングの領域です。

優れたモデルを選定する話ではなく、そのモデルを現実に配線する話だと捉えています。配線できる担い手がいなければ、上位でどれだけ旗を振っても組織は動きません。

4. 抽象化とモデリングがエンジニアの本職だから

「現実を、扱いやすい構造に切り分けてモデルにする」という営みは、エンジニアが日々の設計で行っていることそのものです。オントロジーづくりは、対象がDBスキーマから「会社の現実」に変わっただけで、求められる思考の型は変わりません。むしろエンジニア組織が最も得意とする作業だと考えています。

オントロジーと組織OSとエンジニアの関係

これからのエンジニア組織が担う役割

「顧客とは何か」といった名詞の"意味"そのものを決めるのは、事業判断であり、ビジネスの仕事です。ここはエンジニアが閉じた場で勝手に定義してよいものではありません。

一方で、これからのエンジニア組織には、その定義を「決めさせ、形にし、維持する」役割が求められると考えています。具体的には、次のような動きです。

  • 曖昧なまま放置されている定義に対し、「3つのシステムで意味が異なりますが、どれを正としますか」と問いを立て、決断を促す
  • 決まった定義を、ドキュメントやコード(データフローの定義、スキーマ、設定ファイル等)に落とし込み、形にする
  • それが時間とともに崩れないよう守り、育てる

言い換えれば、Howを握りながらWhatを引き出す、ということです。定義そのものはビジネスと協働して決めますが、その場を設計し、形にし、維持する力学はエンジニア組織が担う。辞書を「コードとして」保持するからこそ、エンジニアが自然とその番人になれるのだと考えています。

従来のエンジニア組織が「決まった仕様を実装する」役割だったとすれば、これからは「組織が何を意思決定すべきかの土台そのものを整える」役割へと重心が移っていく。これがAI時代のエンジニア組織の役割になっていくと捉えています。

エンジニア組織として、ここからどう動くか

具体的な進め方はこれから詰めていく段階ですが、最初の一歩として、社内FDE(Forward Deployed Engineer)の立ち上げから着手しようとしています。

FDEは、もともとは顧客の現場に深く入り込み、その場の業務課題を解きながらシステムを組み上げていくエンジニアを指す役割です。これを社内に向ける、というのが「社内FDE」のイメージです。事業の現場に入り込み、業務とデータの実態を一次情報として捉えたうえで、前述の「名詞・動詞・ルール」を現実に即して定義し、形にしていく。オントロジーづくりは、机上の設計ではなく現場に張り付く作業だと考えており、それを担う人をはっきり置くことが、主導していくうえでの起点になると捉えています。

弊社は職能横断でビジネスにアラインした組織構造をとっており、エンジニアがもともと事業の近くに座っています。その意味で、社内FDE的な動きは構造的にも相性がよいと考えています。

まずは課題の大きい領域に社内FDEを置き、そこで得た知見をもとに、共通言語をどう全社の共有資産へと育てていくか——このあたりを走りながら具体化していく予定です。進め方や得られた学びは、また別の機会に書ければと思います。

主導するうえで意識していること

最後に、旗を振るうえで気をつけている点を3つ挙げます。

1点目は、完璧な全社オントロジーを最初から作ろうとしないことです。すべてを一度に進めようとすると必ず頓挫します。最も課題の大きい1ドメインから、小さく始めるのが現実的だと考えています。

2点目は、エンジニアだけで定義を閉じないことです。Howは握りますが、Whatはビジネスを巻き込んで決める。ここを取り違えると、技術的には正しいが誰も使わない辞書ができあがります。

3点目は、「便利ツールづくり」と「基盤づくり」を混同しないことです。目の前の自動化は成果が見えやすく魅力的ですが、それを共有資産の規律に乗せるところまで進めて初めて「組織OS」と呼べる状態だと考えています。

おわりに

AI時代に入り、エンジニア組織の役割は「機能を作る」ことから、もう一段広がろうとしていると感じています。会社の現実をモデルに落とし込み、人とAIがともに動く土台——すなわち組織OSそのものを作る、という役割です。

そしてこの仕事は、現実のシステムに最も近く、動詞を安全に実装でき、AIを現実に配線でき、抽象化を本職とする、エンジニア組織が最もうまく担える領域だと考えています。経営や事業を巻き込みながらも、駆動するのは自分たちだと引き受けにいく価値のあるテーマです。

弊社でも、「個人のAI活用」から「組織のAI活用」へと軸足を移そうとしている今、ここはエンジニア組織が主導していきたいと考えています。


出典・参考

本記事における「オントロジー」の考え方は、以下のPalantirの記事を参考にしています。

「26卒エンジニアの輪を広げNight」を開催しました!!

こんにちは、25卒エンジニアの190です。

2026年6月5日、弊社のオフィスを会場に「26卒エンジニアの輪を広げNight」というイベントを開催しました。さまざまな会社に入社した26卒エンジニアに集まってもらい、お互いの働き方やこれからの抱負を共有して交流してもらう、というイベントです。

このイベントは、弊社25卒の私とuho-wqあかがわまさともの3人で企画・運営しました。

本記事では、企画した背景・当日の内容・運営してみて気づいたことをまとめます。

背景

私たち25卒は、入社1年目に社外のエンジニアと知り合える機会に恵まれ、そこでできたつながりが日々の支えや刺激になっています。

一方で、入社して間もない時期は、他社の同期がどのように働いているのか、どのようなことを考えているのかを知る機会は意外と多くありません。

「後輩にも自社だけではなく、他社のエンジニアと交流してほしい」という思いから、26卒が他社のエンジニアと交流できる場を自分たちで用意しよう、と考えたのがこのイベントのきっかけです。 イベントの目的は、技術力の向上ではなく、次の点に置きました。

  • 他社の同期が今どんなことに取り組み、何を考えているのかを知る
  • そのうえで、気軽に相談し合える関係をつくる

一言でいえば「同期という仲間を増やしてもらうこと」がゴールです。上の世代を見て学ぶことも大切ですが、隣にいる同期の頑張りを知ることが、入社直後のこの時期にはいちばんの刺激になると考えました。

企画にあたって考えたこと

コンテンツを設計するうえで、いくつか前提を置きました。

まず、懇親会を除いて技術の詳細な話は扱わない方針にしました。26卒はまだ研修中の会社も多く、業務の詳細を話しづらいケースがあると考えたためです。そのかわり、誰でも前向きに話せる「これからどう働いていきたいか」というテーマを共通の軸に据えました。

また、自己開示のハードルをできるだけ下げることを意識しました。初対面同士が大半になるので、いきなり話すのではなく、付箋に書き出してから共有する、という段取りにしています。

当日の内容

当日のタイムテーブルは以下のとおりです。

時刻 内容
19:15 開場
19:30 オープニング
19:35 会場スポンサーLT(弊社)
19:40 公募LT(3名)
20:00 グループワーク
20:20 クロージング
20:25 懇親会
21:30 終了

LT

LTは2部構成です。まず、会場スポンサーである弊社からHir_KがLTを行いました。続いて、公募で集まった3名の方に発表してもらいました。

LTのテーマは「今後の働き方や抱負」です。技術的な内容ではなく、これからどんなエンジニアになりたいか・何を大事にしたいか、といった切り口でお願いしました。

ニックネーム
Sekibuuun3109さん
HwaI12さん
takkiiiiiiiiさん

抽象的なテーマなので集まるか少し不安もありましたが、ありがたいことに公募3枠とも埋まりました。どの発表も入社して数ヶ月のリアルな気持ちが反映されていて、聞いている側も自分ごととして受け止められる内容でした。

LTの様子

グループワーク

グループワークのテーマは「今年度の抱負」です。流れは次のとおりで、各グループに分かれて実施しました。

  1. 付箋に自分の今年度の抱負を書き出す(5分)
  2. グループ内で共有する(10分)

抱負を書き出す際は、「できるようになりたいこと」「目標にしている先輩の特徴」などを例として提示しました。共有のパートでは、はじめに自己紹介(所属・職種・いま取り組んでいること)をしてから抱負を発表し、お互いに気になった点を質問し合ってもらう形にしました。

グループワークの様子

どのグループも活発に議論している様子が見られました。付箋を起点にしたことで、初対面同士でも会話のきっかけがつかみやすかったのではないかと思います。また、テーマを未来の話に寄せたことで、前向きな雰囲気だったのも良かったです。

懇親会

最後は立食形式の懇親会です。弊社が会場スポンサーとして、ケータリング、ドリンクを用意しました。

グループワークで一度話したことが会話のきっかけになっていて、グループの内外を問わず交流が広がっていました。26卒のコミュニティを作る動きも見られて良かったです。

運営してみて気づいたこと

私自身、初めての社外向けイベント運営だったので、終わってみるといくつか学びがありました。

  • テーマを「これからの話」に寄せたのは有効だった。
    • 比較や反省ではなく抱負を軸にしたことで、入社直後でも話しやすい空気になりました。
  • 付箋に書き出してから話すと、初対面でも話しやすい。
    • 書き出す作業がワンクッションになり、会話のハードルが下がったと思います。
  • グループワークが懇親会の会話のネタづくりになった。
    • 一度お互いのことを話しておくと、それがそのまま懇親会での会話のきっかけになります。懇親会を盛り上げるうえで、その前のグループワークが効いていたように感じました。

まとめ

いろいろな会社・バックグラウンドの26卒エンジニアが集まってくれて、運営としても充実したイベントになりました。参加してくださったみなさん、ありがとうございました。

集合写真(公開許諾済み)

最後に

弊社では、ともに働く仲間を募集しています。

テックブログを読んで少しでも弊社に興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

https://corp.every.tv/recruits/engineer

Code w/ Claudeで学んだAnthropic式開発手法

— 実装、レビューからHarnessの整備、そしてHarnessを評価するEvalsへ

1. はじめに

こんにちは、開発本部開発1部デリッシュキッチン開発部所属の西本(@daikon265)です。

6月11日に開催されたCode w/ Claude: Extended Tokyoに参加してきました。参加して感じたのは、Claude Codeの活用が「どうコードを書かせるか」から、「Claudeが安全に働ける環境をどう作り、どう改善し続けるか」に移っていることでした。

これまでClaude Codeの活用では、Harnessを使ってClaudeをうまく制御する話が中心だったと思います。

一方で今回のカンファレンスでは、そのHarness自体をどう評価し、どう育てるかという視点も強く出ていました。

Anthropicのエンジニアとの1on1で、私は次のような質問をしました。

モデルが進化するにつれて、
今のHarnessはだんだん形骸化して、
むしろ邪魔になっていくのではないか?

返ってきた答えは印象的でした。

その直感は正しい。モデルが賢くなるにつれて、Harnessの重要性は下がっていく。Anthropic社内では、新しいモデルを定量評価し、その結果に基づいて、CLAUDE.mdを小さくするか、Skillsがノイズになっていないかを判断している。

Anthropicの中ではすでに、Evalsを使ってHarnessを評価し、必要なものを残し、不要になったものを削る運用が回っているということです。

本稿では、この考え方を抽象論に留めず、実際の開発フローに落とし込んで考えます。

大きくは、次の3つのフェーズで整理します。

1. 実装フェーズ
   1-1. Plan:実装前に何を具体化するか
   1-2. Implementation:サブエージェントにどう実装を分担させるか

2. レビュー・マージフェーズ
   2-1. PRで何を見せるか
   2-2. Claudeと人間でレビューをどう分担するか

3. 運用・改善フェーズ
   3-1. マージ後に何を観察するか
   3-2. 学びをどうHarnessに戻すか

それぞれのフェーズで、カンファレンスで語られていた内容を紹介しつつ、自分たちの開発に取り入れるならどういう形になりそうかを整理していきます。

2. 実装フェーズ

実装フェーズは、さらに PlanImplementation に分けて考えます。

ここでいうPlanは、実装前に仕様判断、実装判断、検証判断を具体化する工程です。自分も以前から、Claudeにいきなり実装させる前に、まず計画を作らせることはしていました。

ただ、その計画の立て方や粒度は曖昧になりがちです。

どこまでClaudeに質問させるのか。
どこまで仕様を固めるのか。
どの時点で検証条件を決めるのか。
どの情報を残しておけば、後続の実装やレビューで使えるのか。

今回のワークショップで分かりやすかったのは、まさにこの「Planをどう具体化するか」でした。

2-1. Plan:実装前に何を具体化するか

2-1-1. まずClaudeに質問させる

「How we Claude Code」ワークショップでは、エージェントが長時間自律稼働できるようになってきたことで、ミスのコストが上がっているという問題意識が語られていました。そこで紹介されていたのが、コードを書かせる前にClaudeへユーザーをインタビューさせ、曖昧さやエッジケースを引き出す方法です。

たとえば、次のような依頼は危険です。

友達用の割り勘アプリを作って

この依頼だけでも、Claudeはそれらしいものを作れます。

しかし、保存方法はどうするのか、精算はアプリ内で行うのか、通貨は複数対応するのか、誰が誰にいくら払うべきかをどう最小化するのか、といった判断をClaudeが勝手に埋めることになります。

ワークショップで紹介されていたのは、次のようにClaudeに質問させる進め方です。

いくつか確信が持てない点があります。
まず私にインタビューして、仕様の曖昧なところを整理してください。

質問は一度に多くしすぎず、重要なものから聞いてください。
回答をもとに、実装前に合意すべき仕様をまとめてください。

デモでは、Claudeが用途、分割方法、保存方法、精算方法、通貨、共有方法などを質問し、回答に矛盾があれば追加で確認していました。その結果として、実装に入る前に詳細なspecが生成されていました。

ここで大事なのは、Claudeに質問させることで、実装前に判断が必要なことを表に出すことです。

2-1-2. 検証条件を先に決める

Planで特に重要なのが、検証条件を先に決めることです。

検証に関するデモでは、実装前に Fixture、Invariant、Probe を決めることが強調されていました。

項目 意味
Fixture 検証時に再現する状態
Invariant 常に成り立っていてほしい性質
Probe 検証時に観測するポイント

たとえばTo Doアプリなら、こうなります。

Fixture Invariant Probe
完了済みのTodoが1件ある 完了状態のデータと画面表示が一致している DOM上の状態属性、取り消し線の表示
非常に長いテキストのTodoがある レイアウトが崩れない 表示領域、折り返し、スクロール
Todoが0件 空状態が表示される empty stateの文言、追加ボタンの表示

この考え方があると、Claudeも人間も「何をもって正しいと判断するのか」を共有できます。

2-1-3. Probeを実装上で観測できるようにする

Fixture / Invariant / Probe を決めても、実装上でその状態を観測できなければ検証はしづらくなります。

このデモで特徴的だったのが、アプリの状態をDOMにスタンプする方法です。たとえばTodoが完了済みであれば、DOMに次のような属性を出します。

<li
  class="todo-item todo-item--done"
  data-verified-done="true"
>
  oat milk
</li>

未完了であれば、data-verified-done="false" になります。

ここで見たいのは、内部状態と画面表示の一致です。

data-verified-done="true"
かつ
画面上も完了状態として表示されている

Reactの内部状態を直接見に行く方法もありますが、実装依存になりやすく、検証のたびに扱いが面倒になります。そこで、検証したい状態をDOM属性として外に出しておくことで、Claudeや検証スクリプトが確認しやすい観測点を作ります。

つまりDOMスタンプは、Probeを成立させるための仕込みです。

これにより、内部状態としては完了済みなのに、画面上では完了済みに見えない、といったズレを検出しやすくなります。ワークショップでも、Reactのメモリ上ではTodoが完了済みなのに、画面に取り消し線が出ないケースを、この考え方で検出していました。

2-1-4. 自分たちのPlanでは何を残すか

ここまでの内容を自分たちの開発に落とすなら、Planでは次の3つを残します。

ファイル 対応する考え方 書くこと
spec.md インタビューで固めた仕様 作るもの、作らないもの、制約、未決事項
plan.md 実装の進め方 参照すべき既存実装、変更順序、実装方針
verification.md Fixture / Invariant / Probe / 観測点 再現する状態、守るべきルール、観測するポイント、コード上の観測点

verification.md には、単なる「テストする」で終わらせず、次の形で残します。

Fixture Invariant Probe 実装上の観測点(追加)
完了済みのTodoが1件ある 完了状態のデータと画面表示が一致している DOM上の状態属性、取り消し線の表示 data-verified-done
非常に長いテキストのTodoがある レイアウトが崩れない 表示領域、折り返し、スクロール Storybook / Playwright snapshot
Todoが0件 空状態が表示される empty stateの文言、追加ボタンの表示 empty state selector

Planのゴールは、Claudeが実装に入る前に、仕様判断・実装判断・検証判断を分離して、後から追える形にすることです。ドキュメントは、その判断を残すための手段として扱います。

2-1-5. Bunの実装ガイドを一般化する

「Rewriting Bun in Rust」では、ClaudeにBunをZigからRustへ書き直させ、約11日で巨大なPRをマージした事例が紹介されていました。

この事例で参考になるのは、実装に入る前に、複数のClaudeが同じ判断基準で動けるように、事前にルールを作っていた点です。

Bunでは、最初にClaudeと約3時間対話し、Zigの書き方をRustではどう表現するかを整理しました。その結果をポーティングガイドとしてまとめ、以後のClaudeインスタンスが必ず参照する資料にしています。さらに、Bunコードベース内の構造体やフィールドを調査し、ライフタイムに関する情報も共通資料として使っていました。

「ZigのパターンをRustにマッピングする」と言うと少し分かりにくいですが、要するにこれは 変換ルール集 です。

Bunの例
  - Zigのこの書き方は、Rustではこう書く
  - メモリ管理はこの方針にする
  - crate分割はこの考え方にする
  - ライフタイムが複雑な箇所はこの資料を参照する

これを自分たちの開発に置き換えると、次のようになります。

自分たちの例
  - 古いAPI呼び出しは、新しいclientに置き換える
  - 独自UIは、design system componentに寄せる
  - 古いhooksは、新しいhooksに統一する
  - DB更新は、このmigration方針に従う
  - エラーハンドリングは、この形式に揃える

普段の開発では、これを implementation-guide.md として扱えばよさそうです。

変更の種類 作るもの
小さな修正 spec.md / plan.md / verification.md
複数箇所に同じ方針を適用する変更 implementation-guide.md を追加
複数のClaudeを並列に動かす変更 implementation-guide.md をほぼ必須にする

複数のClaudeを並列に動かす場合、全員が同じ判断基準を持っていないと、ファイルごとに実装スタイルがばらつきます。

implementation-guide.md は、そのばらつきを抑えるための共通ルールです。

コラム:HTMLプロトタイプはプロトタイプ開発で使える

ワークショップでは、Planができた後にHTMLプロトタイプを作る方法も紹介されていました。

背景にあるのは、Anthropicの “Demos, not memos” という考え方です。Markdownの仕様書はClaudeには読みやすい一方で、人間が直感的に判断するには弱い。人間は、文字だけで仕様を読むより、触れるものを見たほうが「ここが違う」「この方向性は好き」と反応しやすい。そこで、ClaudeにHTMLで軽量なプロトタイプを作らせ、触りながら方向性を固める、という流れです。

自分でこれを取り入れるなら次のような感じがいいと考えています。

初期探索
  - エンジニアがClaudeとHTMLプロトタイプを作る
  - 最低限触れる状態にして公開する
  - ユーザーや市場の反応を見る

ブラッシュアップ
  - 反応が良ければ、Figmaでデザイナーに詳細を詰めてもらう
  - デザインシステムに載せる
  - 本格的に磨く

この進め方は、今後増えていくと思います。

2-2. Implementation:サブエージェントに実装を分担し、失敗を再配布する

Planができたら、実装に入ります。

ある程度の規模のタスクでは、複数のサブエージェントに分担させて並列に進めることが増えてきていると思います。

ここで参考になるのが、Bunの実装方法です。

Bunの事例では、並列実行時の制御も重要でした。重い操作や競合しやすい操作を各エージェントに任せきりにしないようにしていました。複数のClaudeがそれぞれbuildやtest、git操作を実行すると、同じ確認を重複して行ったり、同じworktree内で互いに干渉したり、全体の進行を遅くしてしまうからです。

そのため、build / test / git操作のような重い処理は、メイン側のワークフローで管理します。サブエージェントは実装や修正に集中し、メインエージェントが結果を集約して、失敗ログを分類し、担当するサブエージェントへ再配布します。

このループでは、役割を大きく3つに分けます。

役割 担うこと
Main Agent spec.md / plan.md / verification.md を読み、作業を分割する。実装結果を集約し、build / test / lint / typecheck をまとめて実行する。失敗ログを分類し、必要なサブエージェントへ再配布する。
Sub Agents 割り当てられた範囲を実装する。必要に応じて、実装担当とレビュー担当を分ける。レビュー担当は diff や spec を読み、仕様違反、バグ、テスト不足を探す。
Human 仕様判断、設計判断、本番リスクを確認する。特にクリティカルな変更では、AIの検証結果だけでなくコードや動作も見る。

各サブエージェントに任せる範囲は絞ります。各サブエージェントがそれぞれ build や test を走らせ、個別に判断し始めると、重い処理が重複し、失敗ログの扱いもばらつきます。

そのため、検証と再配布は Main Agent 側に集約します。Main Agent が失敗ログを見て、次に誰へ戻すかを判断します。

失敗の種類 戻し先
build failure backend 担当の Sub Agent
UI regression frontend 担当の Sub Agent
test failure test 担当、または該当実装担当の Sub Agent
migration error DB 担当の Sub Agent
spec とのズレ 実装担当の Sub Agent
横断的な設計ミス Main Agent が plan を見直す

このループの本質は、検証結果を次のタスク分配の入力にすることです。失敗を検出したら、原因ごとに担当へ戻します。さらに同じ失敗が繰り返されるなら、プロンプト、ルール、verification、implementation-guide 側に戻して、生成プロセス自体を直します。

3. レビュー・マージフェーズ

レビュー・マージフェーズで重要なのは、検証結果を人間が判断しやすい形で見せることです。

3-1. PRでは検証結果を証拠として見せる

PRには、変更概要や影響範囲だけでなく、動作確認や検証結果を載せます。

特にUIやワークフローが関わる変更では、スクリーンショットや動画が有効です。実際にどう動いたか、どの状態で確認したかが見えると、レビュアーは判断しやすくなります。

PRでは、最低限次の情報を揃えます。

PRに載せるもの 目的
変更概要 何を変えたかを短く把握する
影響範囲 どの画面・機能・APIに影響するかを見る
検証結果 どのFixture / Invariant / Probeを確認したかを見る
スクリーンショット・動画 実際の動作やUIの崩れがないことを見る
レビュアーに見てほしい箇所 判断が必要な点を明確にする

3-2. 検証ダッシュボードで状態と結果を見える化する

検証ダッシュボードのデモで特徴的だったのは、検証用のダッシュボードを作っていた点です。

通常のテストログでは、どのテストが通ったかは分かります。ただ、UIやワークフローの検証では、それだけだと「どの状態で、何を確認したのか」が見えにくい。そこで、検証ダッシュボード上に、どの画面・コンポーネントを、どのfixtureで、どのinvariantに対して検証したのかを表示します。

たとえば、次のような情報が見える状態です。

見えるもの
対象 画面、コンポーネント、ワークフロー
Fixture 空状態、長いテキスト、大量データ、権限なしユーザー
Invariant レイアウトが崩れない、状態と表示が一致する、権限外操作ができない
Probe DOM属性、表示テキスト、APIレスポンス、エラー表示
結果 pass / fail、必要に応じてスクリーンショットや録画

これは、テストを増やす話というより、検証結果をレビューできる形に変換する話です。Claudeも人間も、同じ証拠を見ながら「何をもって正しいと判断したのか」を確認できます。

3-3. レビューはClaudeと人間で見る観点を分ける

レビューでは、Claudeと人間の役割を分けます。

見る人 主に見ること
Claude specとのズレ、テスト不足、エッジケース漏れ、権限・セキュリティの問題、既存挙動の破壊
Human 実際の動作、仕様解釈、設計判断、ユーザー影響、本番リスク

Anthropicのエンジニアとの1on1では、社内ではすべてのPRに対してClaudeによる自動コードレビューが走り、その後に人間のレビュアーがハイレベルな観点で確認すると聞きました。

チーム内の運用イメージとしては、Claudeが細かいロジックやspecとのズレを拾い、人間が動作、設計、リスクを判断する形です。クリティカルな変更では、人間もコードを読みます。

4. 運用・改善フェーズ

マージ後も観察と改善は続きます。

むしろ、マージ後に何を観察し、何をHarnessに戻すかが、Claude Codeをチーム開発に組み込むうえで重要になります。

4-1. マージ後も検証を増やす

「Rewriting Bun in Rust」では、テストスイートを通してマージした後も、品質強化が続いていました。マージ後にも別の角度から壊れ方を探す検証を増やしていたことが印象的でした。

たとえば、次のような検証です。

- セキュリティ上の問題を機械的に探す
- ランダムな入力を大量に与えてクラッシュを探す
- メモリリークのような実行時の問題を探す
- テストでは通らなかったコード経路をさらに踏みに行く

講演で語られていた思想は、「動くことを信用したくない。動くことの実証的な根拠を持ちたい」というものでした。

マージ後に検証を増やすことは、次の開発ループを強くするための投資です。

4-2. EvalsでHarnessを育てる

Evalsは、実装・レビュー・運用を回した後に、Harnessを改善するための仕組みとして効いてきます。

「Evals for taste: Hill-climbing a slide-generation agent」では、Evalsは「成功とは何か」を定義し、エージェントが改善しているかを確認するためのものだと説明されていました。最適化対象はプロンプトだけでなく、モデル選択、ツール設計、エージェントのアーキテクチャにも広がる、という話もありました。

「Tool, skill, or subagent? Decomposing an agent that outgrew its prompt」でも、Evalsを改善の絶対的な指針にすることが最大のメッセージとして語られていました。評価結果が上がることを確認できたからこそ、ツール、Skills、サブエージェント構成を改善できたという話です。

冒頭で触れた1on1の話に戻ると、モデルが賢くなれば、以前は必要だった細かい指示が不要になる。場合によっては、古いCLAUDE.mdやSkillsがノイズになる。だからこそ、Anthropic社内では新しいモデルを定量評価し、その結果に基づいて、CLAUDE.mdを小さくするか、Skillsがノイズになっていないかを判断しているそうです。

Harnessは作ったあとも、Evalsによって評価され、必要なら削られる。つまり、EvalsはClaudeの出力を採点しながら、Harnessを育てるための仕組みでもある。

自分たちも、Evalsによって「これはまだ効いているか」「もうノイズになっていないか」を見ていく必要があります。

4-3. 失敗をどこに戻すか

マージ後に観察するものは、次のHarness更新の材料として見ます。

観察するもの Harnessに戻す先
本番で出たバグ eval / verification
繰り返し出るレビュー指摘 review skill / checklist
仕様の取り違え specの作り方
テスト漏れ verifier / contract test
不要になった指示 CLAUDE.md / Skill の削除候補

Skillsは、関連するタスクのときだけロードされる手順書として使うのが本質です。「Tool, skill, or subagent? Decomposing an agent that outgrew its prompt」でも、長いプロンプトをオンデマンドのSkillsに分解し、必要なときだけコンテキストをロードする考え方が紹介されていました。

Harnessを育てるほど、余計なものを削る視点も必要になります。

増やすもの 削るもの
tests / evals / verifiers / CI / monitoring 古いCLAUDE.mdの指示 / ノイズになったSkill / 自明すぎるルール

「Evals for taste: Hill-climbing a slide-generation agent」では、evalスコアが飽和したらgrader自体を見直すべきだという話もありました。見た目は明らかに改善しているのにスコアが上がらないなら、評価側の解像度が足りない可能性があります。その場合は、graderの評価基準を具体化する価値があります。

Claudeの出力とあわせて、評価そのものも育てる。そのループがあると、Harnessの改善が感覚頼みから評価駆動に変わります。

5. 自分たちの開発に落とすなら

ここまでの内容を実務に落とすなら、次の4段階で始めるのがよさそうです。

フェーズ やること 残すもの
Plan 仕様、実装方針、検証条件を分けて整理する spec.md / plan.md / verification.md
Implementation Main Agent が作業を分割し、Sub Agents が実装する。検証結果は Main Agent が分類して再配布する 失敗ログ、修正タスク
Review / Merge PRでは検証結果や動作の証拠を見せる。Claudeは詳細、人間は仕様・設計・リスクを見る 検証ログ、スクリーンショット、レビュー結果
Post-merge 本番やレビューで出た失敗を、その場限りにせずHarnessへ戻す eval、verification、skill、checklist

このフローでは、判断基準、検証可能性、レビュー可能な証拠、Harnessへのフィードバックを揃えることが大事です。

anthropic-dev-loop

この流れは再利用できるように、Skillとしても整理しました。Claude CodeやCodexで同じ流れを使いたいときは、このSkillを読み込ませることで、Plan、実装分割、検証、レビュー、Harness更新までを一連の手順として扱えます。

https://github.com/nishimoto265/anthropic-dev-loop

6. おわりに

今回は、Code w/ Claude Tokyo / Extended Tokyoで紹介されていたAnthropicの開発手法を、自分たちの開発フローに落とし込むならどうなるのか、私なりの知見を交えながら整理しました。

Code w/ Claude Tokyo / Extended Tokyoの中で特に印象的だったのは、やはり Evals です。

私自身、評価関数があり、それに基づいてエージェントや開発フローが将来的には自動で改善されていくのではないかと考えていました。その考え方が Evals に現れており、Harnessを評価し、必要なものを残し、不要なものを削っていく手法は、今後さらに重要になっていくと感じています。

今後もAnthropicのエンジニアの手法を参考にしながら、Claude Codeを前提にした開発フローを自分たちなりに改善していきたいと考えています。

Lakeflow Connectで広告データを取り込む

はじめに

こんにちは。
開発本部 開発1部 デリッシュリサーチでデータエンジニアをしている吉田です。

今回は、Databricksのマネージドなデータ取り込み機能であるLakeflow Connectを使って、複数の広告プラットフォームからデータを取り込んだ事例を紹介します。

注意点

本記事で扱うLakeflow Connectのマネージドコネクタは、執筆時点でいずれもベータ版の機能です。
利用している機能とその結果は執筆時点のものです。
また、リリースタイプの一覧のとおり、ベータ版は本番環境での利用が推奨されておらず、仕様変更の可能性がある点にご注意ください。

背景

広告運用の分析には、各広告プラットフォームのデータ(キャンペーンやインサイトなど)が必須です。
これらをデータ基盤に取り込むには、プラットフォームごとに取り込みJobを作成する必要があります。
しかし、取り込みJobの作成には以下のような辛さがあります。

  • API仕様の調査、実装
  • 認証情報の管理
  • APIのアップデート追従

特にAPIのアップデートに合わせた取り込みJobの追従などは都度工数が発生するため、運用における負担となります。

そこで、取り込みをマネージドに任せられるLakeflow Connectのマネージドコネクタを採用しました。

Lakeflow Connectとは

Lakeflow Connectは、Databricksの統合データエンジニアリング機能群Lakeflowの取り込みコンポーネントです。
SaaSやデータベース向けのマネージドコネクタなどを提供し、取り込み処理をサーバーレスで実行します。取り込み先のテーブルはUnity Catalogで一元管理されます。

マネージド SaaS コネクタ

コネクタには2種類あります。

  • マネージドコネクタ
    • Salesforceなどのアプリやデータベース向け
    • Lakeflow Spark Declarative Pipelinesの上に構築され、ソース固有の認証・CDC(増分取り込み)・スキーマ展開・自動リトライを提供する
  • 標準コネクタ
    • クラウドストレージやメッセージバス向けのカスタマイズ可能なコネクタ

マネージドコネクタは、様々なSaaSに対応するコネクタが用意されており、今回はGoogle Ads、Meta Ads、TikTok Adsのコネクタを利用しました。

アーキテクチャ

SaaS系コネクタの取り込みは、以下のシンプルな構成です。

  • Connection
    • 各プラットフォームへの認証情報を保持するUnity Catalogのオブジェクト
  • Ingestion pipeline
    • 接続を通じてデータを取り込み、ストリーミングテーブルへ書き込む
  • Destination
    • Unity Catalog上のストリーミングテーブル

パイプラインの作成

パイプラインはUI・Databricks Asset Bundles・REST APIなどから作成できますが、今回はPipelines API(/api/2.0/pipelines)をノートブックから呼び出して作成しました。
パイプライン定義を用意することで、容易に取り込みパイプラインを作成することができます。

パイプライン定義の例(Meta Ads)

以下のJsonはMeta Adsのパイプライン定義の例です。

{
  "name": "<pipeline-name>",
  "catalog": "<catalog-name-for-event-logs>",
  "schema": "<schema-name-for-event-logs>",
  "ingestion_definition": {
    "connection_name": "<connection-name>",
    "objects": [
      {
        "table": {
          "source_schema": "act_<account-id>",
          "source_table": "campaigns", -- キャンペーンの取り込み
          "destination_catalog": "<save-to-catalog>",
          "destination_schema": "<save-to-schema>",
          "table_configuration": {
            "scd_type": "SCD_TYPE_1"
          }
        }
      },
      {
        "table": {
          "source_schema": "act_<account-id>",
          "source_table": "ad_insights", -- インサイトの取り込み
          "destination_catalog": "<save-to-catalog>",
          "destination_schema": "<save-to-schema>",
          "table_configuration": {
            "scd_type": "SCD_TYPE_1",
            "metamarketing_parameters": {
              "level": "campaign",
              "start_date": "2026-01-01",
              "time_increment": "1"
            }
          }
        }
      }
    ]
  }
}

table_configuration では取り込み時の挙動を指定します。
scd_type で履歴の保持方式(SCD_TYPE_1 は最新の状態のみを保持)を、metamarketing_parameters でレポートの集計レベル(level)や粒度(time_increment)などコネクタ固有のパラメータを指定します。

UI上での管理

作成したLakeflow Connectのパイプラインは、Jobs & Pipelinesから確認できます。
取り込みは指定したテーブル単位で行われ、失敗したテーブルや特定のテーブルのみを再実行することも可能です。

現時点での使用感

現在、3つのコネクタを利用していますが、最終的にMeta Adsのコネクタは使用を停止しました。
Meta Adsコネクタでは、データ量の多いアカウントでは取り込みが安定しないケースがありました。
私たちの環境では主にad_insightsad_setsの取り込みで、以下のエラーが発生しました。

The Meta API failed since the amount of data requested exceeds the limit the Meta servers can process.

取得するカラムや期間を絞る対応を加えましたが解決せず、最終的には自前実装に切り替える選択をしました。
Tokenの更新の実装や、アカウントや取得したい項目が増えるたびの対応など、マネージドに任せることができない辛さがあり、コネクタの今後のアップデートに期待したいです。

まとめ

Lakeflow Connectにより、Google Ads / TikTok Adsの広告データ取り込みは、API実装なしにパイプライン定義だけで作成できました。
複数アカウント・複数テーブルの定義をspecとしてコードで管理でき、増分取り込みも設定だけで実現できています。
一方でMeta Adsのように、データ量やコネクタの制約から現時点ではマネージドに任せきれず、自前実装で補ったケースもありました。
それでも、取り込みの作り込みの大部分から解放され、データ活用そのものに集中できるようになったメリットは大きいと感じています。ベータ版ゆえの制約は今後のアップデートでの改善に期待したいです。

全社共通ゲートウェイによるセキュアな社内リモート MCP サーバーを実現した話

全社共通ゲートウェイによるセキュアな社内リモート MCP サーバーを実現した話

目次

はじめに

こんにちは。 開発本部開発3部トモニテ開発部所属の庄司(@ktanonymous)です。

エブリーの開発組織では、日常業務から離れて新しい技術やアイデアに挑戦する「挑戦week」という取り組みを定期的に開催しています。 先日行われた挑戦weekの中で、私たちのチームは全社共通で利用できるリモート MCP (Model Context Protocol) サーバー向けの認証・認可ゲートウェイを設計・実装しました。 本記事では、その全体アーキテクチャや認証・認可フロー、実装のポイントなどを紹介したいと思います。

※ 挑戦weekの詳細については過去の記事で紹介していますので、興味のある方は以下をご覧ください。

tech.every.tv

背景と課題

弊社では非エンジニア職にも Claude が配布され、職種を問わず AI 活用が盛んになってきています。 業務の中で Claude を利用するにあたり、社内情報の取得のために社内 API へ接続したいという需要が高まってきていると感じました。 今後、その手段として各チームがリモート MCP サーバーを立てるケースが増えていくと考えられます。

リモート MCP サーバーはインターネット経由でアクセスされるため、社外の人間が利用できないように認証・認可を考慮する必要がありました。 一方で、MCP サーバーを実装するたびに各チームが認証・認可を設計・実装するのはコスト面でも効率面でも避けたいものです。 そこで、共通で利用できる認証・認可基盤を作り、各チームの MCP サーバーはそれぞれのツールの実装に専念できるようにすることを目指しました。

今回検討した要件は以下の通りです。

  • Claude (Web / Desktop / Code) などの各種 MCP クライアントから、社内のリモート MCP サーバーのツールを利用できる
  • 弊社の Google Workspace アカウントでログイン済みのユーザーのみがツールを利用できる
  • 各チームが新しく MCP サーバーを追加するとき、認証・認可の実装を不要にする

方式の検討

認証・認可の共通化にあたり、大きく分けて以下 3 つのアプローチを検討しました。

  1. ライブラリ方式: 認証・認可処理を共通ライブラリとして実装し、各 MCP サーバーに組み込む
  2. サーバー方式: 認可サーバーを立て、各 MCP サーバーがトークンを問い合わせることで検証する
  3. ゲートウェイ方式: ゲートウェイがリクエストを一括で受けて認証・認可を行い、検証済みのリクエストだけを後段の MCP サーバーへ転送する

3 つの内、ライブラリ方式は MCP サーバーの実装言語ごとにライブラリを用意する必要があり、 サーバー方式は、責務こそ分離できるものの、各 MCP サーバー側に認可サーバーへトークンを問い合わせる実装が必要になります。 一方、ゲートウェイ方式であれば、認証・認可をゲートウェイに一元化でき、後段の MCP サーバーは実装言語を問わず認証・認可も意識せずに済みます。 近年の MCP ゲートウェイ製品の設計とも方向性が近いことから、今回はゲートウェイ方式を採用しました。

なお、mcp-context-forgeagentgateway といった既存の OSS MCP ゲートウェイも調査しましたが、 今回の要件に対して機能・ボリュームが大きすぎたため採用を見送り、必要最小限のゲートウェイを実装することにしました。

全体アーキテクチャ

全体のアーキテクチャは以下の通りです。

アーキテクチャ概要
アーキテクチャ概要

今回実装したゲートウェイは大きく 4 つの要素から構成されています。

  • MCP クライアント: Claude Code / Claude Desktop など。OAuth 2.0 のパブリッククライアントの立ち位置
  • ゲートウェイ: ECS Fargate 上で稼働する Go (Echo) 製のリバースプロキシ。JWT の検証と後段 MCP サーバーへのルーティングを担う。クライアントから直接見える唯一の MCP サーバー。
  • Amazon Cognito: 認可サーバー兼 IdP。Google Workspace アカウントへのフェデレーションを行い、アクセストークン (JWT) を発行する
  • MCP サーバー群: 各チームが実装するリモート MCP サーバー。private subnet に配置し、ゲートウェイ経由でのみアクセス可能であり、クライアントから直接的には見えていない

意識した点として、JWT を検証するのはゲートウェイだけという点があります。 後段の MCP サーバーは JWT 検証を実装せず、ゲートウェイが注入する検証済みのユーザー情報 (後述の X-Auth-* ヘッダー) を信頼します。 その前提を成立させるため、MCP サーバーはインターネットに直接公開せず、ゲートウェイからのみ到達できるネットワーク構成としました。

認証・認可フロー

MCP の認可は MCP Authorization 仕様で定義されており、 OAuth 2.1 をベースに、PRM (RFC 9728) などの仕様を組み合わせて構成されています。
今回のゲートウェイもこの仕様に沿って実装しています。

具体的には以下のようなフローとなっています。

認証・認可フロー
認証・認可フロー

クライアントが認可サーバーを発見するまで

MCP クライアントには MCP サーバーの接続先を登録するため、認可サーバーがどこにあるかを予め知ることはできません。 クライアントがトークンなしでアクセスすると、ゲートウェイは 401 Unauthorized を返し、 WWW-Authenticate ヘッダーの resource_metadata パラメータで Protected Resource Metadata (PRM) の URL を通知します。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp-gateway.example.com/.well-known/oauth-protected-resource"

PRM は、保護されたリソース (今回はゲートウェイ) が「どの認可サーバーに保護されているか」「どのスコープをサポートするか」といった自身のメタデータを公開するための仕様です。 クライアントはこのメタデータを参照することで、トークンの取得先を機械的に発見できます。 クライアントがこの URL にアクセスすると、PRM の仕様で定義された以下のような JSON が返ります。

{
  "resource": "https://mcp-gateway.example.com/",
  "authorization_servers": ["https://cognito-idp.ap-northeast-1.amazonaws.com/<user-pool-id>"],
  "scopes_supported": ["openid", "email", "profile"],
  "bearer_methods_supported": ["header"]
}

クライアントは authorization_servers から認可サーバー (Cognito) を発見し、 さらに Cognito の Authorization Server Metadata (/.well-known/openid-configuration) を取得して、 認可エンドポイントやトークンエンドポイントを把握します。 Authorization Server Metadata は、認可サーバーが「どこで認可リクエストやトークン発行を受け付けるか」「どの機能をサポートするか」といった自身の設定情報を公開するための仕様です。 Cognito の場合、以下のような JSON が返ります (主要なフィールドのみ抜粋)。

{
  "issuer": "https://cognito-idp.ap-northeast-1.amazonaws.com/<user-pool-id>",
  "authorization_endpoint": "https://<domain>.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize",
  "token_endpoint": "https://<domain>.auth.ap-northeast-1.amazoncognito.com/oauth2/token",
  "jwks_uri": "https://cognito-idp.ap-northeast-1.amazonaws.com/<user-pool-id>/.well-known/jwks.json",
  "scopes_supported": ["openid", "email", "phone", "profile"]
}

この 2 段階のメタデータ取得により、クライアント側に認可サーバーの情報を事前設定することなく、OAuth フローを開始することができます。

トークンの取得

認可サーバーの発見後は、通常の OAuth 2.0 Authorization Code フロー (PKCE 付き) です。 クライアントがブラウザを開いて Cognito の Hosted UI に遷移し、Cognito は Google へフェデレーションします。 ユーザーが自社の Google Workspace アカウントでログインすると、クライアントはアクセストークン (JWT) を取得します。

Cognito 側の設定のポイントは以下の通りです。

  • パブリッククライアント + PKCE 必須: Claude などの MCP クライアントは利用者の手元で動くため、クライアントシークレットを保持できません。そこでパブリッククライアントとして登録し、PKCE を利用するようにします。
  • アプリクライアントの事前登録: Cognito では接続元のアプリケーションを「アプリクライアント」として登録します。MCP クライアントごとにユーザープールを登録し、対応するコールバック URL (認可コードの返却先) を設定することで事前に利用するクライアントを登録します。

なお、MCP Authorization 仕様では、クライアントの登録方法として事前登録のほかに、 Client ID Metadata Documents (URL を client_id として扱い、認可サーバーがその URL からクライアント情報を取得する方式) や Dynamic Client Registration (RFC 7591) による動的登録も定義されています。
今回は、Cognito がこれらに対応していないことと、社内利用ではクライアントの種類が限られる (基本的に Claude 系のみ) ことから、仕様でも正規の選択肢とされている事前登録制を採用しました。

また、Cognito のアクセストークンには標準では email クレームが含まれないため、 Pre Token Generation Lambda トリガーを利用して、トークン生成時に email クレームを注入しています。 これにより、後段の MCP サーバーが「誰からのリクエストか」をメールアドレスで判定できるようになります。

ゲートウェイでの JWT 検証

ゲートウェイは、リクエストごとに JWT を検証します。 署名検証には github.com/coreos/go-oidc (v3.18.0) を利用し、 Cognito の JWKS (JSON Web Key Set) は初回取得後にキャッシュされます。

検証項目は以下の通りで、署名・有効期限といった基本的な検証に加えて、クレームベースのチェックを重ねています。

検証項目 内容 失敗時
署名 / iss / exp JWKS による署名検証、発行者・有効期限の確認 401
token_use "access" であること (ID トークンの誤用防止) 401
client_id 事前登録したアプリクライアントの許可リストに含まれること 403
email ドメイン エブリードメインであること 403
scope 必須スコープを満たすこと (設定時のみ) 403

token_use の検証は Cognito 固有のポイントです。 Cognito は ID トークン用とアクセストークン用にそれぞれ別の署名鍵を持ちますが1、両方の公開鍵が同一の JWKS で公開されます。 そのため、JWKS 内のいずれかの鍵で署名が検証できることだけを条件にすると、ID トークンも検証を通過してしまいます。 クライアントが誤って ID トークンを Authorization ヘッダーに載せてきた場合に備えて、token_use クレームが "access" であることを確認しています。

なお、検証項目に aud (audience) クレームは含まれていません。 MCP Authorization 仕様ではリソースサーバーによるトークンの audience 検証が求められていますが、 Cognito のユーザープールが発行するアクセストークンには aud クレームが含まれず、代わりに client_id クレームが含まれます。 そのため、今回の実装では client_id の許可リスト検証によって、トークンが事前登録済みのクライアントに発行されたものであることを確認する形をとっています。

ゲートウェイの実装ポイント

ゲートウェイ本体は Go (1.26.3) + Echo (v4.15.2) で実装しました。 ここでは設計上のポイントについて触れます。

設定ファイルによるバックエンド管理

ゲートウェイがバイパスする MCP サーバー (バックエンド) は YAML で宣言的に管理しています。

backends:
  - name: server-a
    url: http://server-a.internal:8081
  - name: server-b
    url: http://server-b.internal:8082

ゲートウェイは起動時にこのファイルを読み込み、/<name>/mcp というパスを各バックエンドの /mcp にマッピングします。 たとえば POST /server-a/mcp へのリクエストは http://server-a.internal:8081/mcp に転送されます。 プロキシ部分は Go 標準ライブラリのリバースプロキシをベースに、以下のように実装しています。 標準実装は転送先のホストを書き換えるだけでパスはそのまま転送するため、転送直前に呼ばれるリクエストの書き換え処理を拡張して、パスプレフィックスの除去を加えています。

func newBackendProxy(targetURL, stripPrefix string) (*httputil.ReverseProxy, error) {
    u, err := url.Parse(targetURL)
    if err != nil {
        return nil, fmt.Errorf("parse %q: %w", targetURL, err)
    }

    rp := httputil.NewSingleHostReverseProxy(u)
    originalDirector := rp.Director
    rp.Director = func(req *http.Request) {
        // 標準の書き換え処理 (転送先を u に向ける) を実行した上で、
        // ゲートウェイ側のパスプレフィックスを除去 (/server-a/mcp → /mcp)
        originalDirector(req)
        req.Host = u.Host
        if stripPrefix != "" {
            req.URL.Path = strings.TrimPrefix(req.URL.Path, stripPrefix)
            if req.URL.Path == "" {
                req.URL.Path = "/"
            }
        }
    }
    // バックエンドに到達できない場合は 502 を返す
    rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
        log.Printf("[proxy] upstream error %s %s: %v", r.Method, r.URL.Path, err)
        http.Error(w, "bad gateway", http.StatusBadGateway)
    }
    return rp, nil
}

新しい MCP サーバーを追加したいチームは、サーバーをデプロイしてこの YAML に 1 エントリ追記するだけで、 認証・認可付きのリモート MCP サーバーを公開できます。

検証済みユーザー情報の伝搬となりすまし防止

ゲートウェイは JWT の検証後、Authorization ヘッダーを除去し、検証済みのクレームを X-Auth-* ヘッダーとしてバックエンドへのリクエストに注入します。

  • X-Auth-Sub: ユーザー識別子 (sub クレーム)
  • X-Auth-Email: メールアドレス (Pre Token Generation Lambda で注入した email クレーム)
  • X-Auth-Client-Id: OAuth クライアント ID
  • X-Auth-Scope: 許可されたスコープ一覧

このとき重要なのが、クライアントから送られてきた X-Auth-* ヘッダーを必ず削除してから再注入することです。

// 受信した Authorization / X-Auth-* を全て削除(なりすまし防止)
stripIncomingAuthHeaders(req.Header)

// 検証済みクレームから X-Auth-* を再注入
req.Header.Set("X-Auth-Sub", claims.Sub)
req.Header.Set("X-Auth-Email", claims.Email)
req.Header.Set("X-Auth-Client-Id", claims.ClientID)
req.Header.Set("X-Auth-Scope", strings.Join(claims.Scopes, " "))

これにより、クライアントが偽の X-Auth-Sub を付けてリクエストすることで他人になりすますのを防ぎます。 ゲートウェイがクレームヘッダーの削除と再注入を強制するため、バックエンド側は常にゲートウェイで検証済みの X-Auth-* ヘッダーの利用を保証できます。

加えて、バックエンドにはゲートウェイ経由でしか到達できないようにネットワークを構成しています (後述)。 X-Auth-* を信頼できるのは「ゲートウェイを必ず通る」ことが前提なので、アプリケーション実装とネットワーク構成をセットで設計する必要があります。

起動時の OIDC Discovery による fail-fast

ゲートウェイは起動時に Cognito へ OIDC Discovery (/.well-known/openid-configuration の取得) を行います。 issuer の設定ミスなどがあればこの時点で起動エラーになるため、リクエストを受けてから認証エラーが多発する、という事態を防げます。

モック認可サーバーによるローカル開発

ローカル開発用に、OIDC Discovery・JWKS・トークン発行だけを備えた最小限のモック認可サーバーを用意しました。 ゲートウェイから見ると issuer の URL が違うだけなので、実際の Cognito と同じコードパスで JWT 検証まで通しでテストできます。 docker compose でゲートウェイ・サンプルバックエンド・モック認可サーバーを一括起動できるようにしており、AWS 環境なしで認証フロー全体を確認できます。

インフラ構成

インフラは Terraform で管理しています。要点は以下の通りです。

  • ECS Fargate: ゲートウェイは private subnet に配置し、外部への通信は NAT Gateway 経由
  • ALB: TLS を終端し、ゲートウェイへ転送。セキュリティグループでゲートウェイへの入力は ALB からのみに制限
  • Cognito User Pool: Google フェデレーション、アプリクライアント、Resource Server、Pre Token Generation Lambda を Terraform で定義
  • デプロイ: GitHub Actions から OIDC でロールを引き受けて ECR push と ECS デプロイを実行

MCP クライアント (Claude など) は固定 IP を持たないため、ALB は HTTPS (443) を全公開とし、アクセス制御は JWT 検証と WAF に任せます。 バックエンドの MCP サーバーは private subnet 内でゲートウェイからのみ到達できるようにし、ゲートウェイを経由しない場合はネットワーク的に到達不可能にしています。

社内での活用事例

実際に社内で Redash を操作するリモート MCP サーバーをゲートウェイを利用して社内向けにリリースされました。 Claude とのチャットだけで、クエリの実行やダッシュボードの操作といった Redash 上のほとんどの操作ができます。

ローカル版の Redash MCP サーバーについて、以前の挑戦weekの記事で紹介しています。

tech.every.tv

Redash MCP サーバーをリモート化し、認証・認可をゲートウェイに集約することで、個人ごとに API Key などの配布や登録が不要になり、 ビジネスサイドのメンバーであっても、Redash アカウントを持っていれば Google アカウントにログインするだけで API 経由で Redash を操作できるようになりました。

Redash MCP を社内に公開しました
Redash MCP を社内に公開しました

今後の課題

短期間での構築だったため、以下のような課題が残っています。

  • きめ細かなポリシー管理: 現状は「自社ドメインの社員であること」の確認までで、厳密に権限管理をする場合には、所属チームなどの属性に応じて利用できる MCP サーバーやリソースを制限する仕組みが必要になります。
  • VPC 間の接続: 各チームの MCP サーバーは別の VPC や AWS アカウントで稼働するケースもあります。そのため、今後さらに MCP サーバーの利用を展開していくためには、VPC Peering などによる VPC 間接続を検討する必要があります。

おわりに

本記事では、全社共通のリモート MCP サーバー向け認証・認可ゲートウェイの設計と実装を紹介しました。

ゲートウェイ方式を採用したことで、認証・認可の実装をゲートウェイに一元化でき、 各チームは MCP サーバーのツール実装に専念して、設定ファイルへの追記だけで認証付きのリモート MCP サーバーを公開できるようになりました。 MCP の認可まわりは仕様の整備が活発に進んでいる領域なので、今後も動向を追いながら基盤を育てていきたいと思います。

この記事が、社内での AI 活用の中で同じような課題感を持っている方の参考になれば幸いです。

最後まで読んでいただき、ありがとうございました。


  1. Understanding user pool JSON web tokens (JWTs) - Amazon Cognito (2026年6月11日閲覧)。「Amazon Cognito generates two pairs of RSA cryptographic keys for each user pool. One private key signs access tokens, and the other signs ID tokens.」と記載されています。