本文へスキップ
Hironobu Iga

MCP のツール呼び出しを AuthZEN で認可する COAZ-MCP

MCP のメッセージを AuthZEN の判断要求に写すワーキンググループドラフト COAZ-MCP について、解決しようとしている課題、既定マッピングと宣言マッピングの仕組み、現在のステータスを整理しました。

公開日

はじめに

前回の記事で、認可の標準仕様 AuthZEN を調べた結果を整理しました。その中で、2026 年 6 月に新しいワーキンググループドラフトが 2 本公開されたことを紹介しました。2 本のうち、MCP(Model Context Protocol)のツール呼び出しを認可するほうが気になっていたので、読んでみることにしました。

ところが、アナウンスにあった「AuthZEN Profile for Model Context Protocol Tool Authorization」を開くと、本文がほとんどありません。残っていたのは「この文書は置き換えられた」という案内だけでした。

前回と同じく学び始めの整理で、以下は 2026 年 8 月時点の読書メモです。

COAZ という名前と 2 本のドラフト

行き先を追うと、6 月に 1 本だったドラフトは、ワーキンググループのリポジトリで次の 2 本に分割されていました。

  • COAZ Framework:プロトコルに依存しない枠組み。任意のプロトコルのメッセージを AuthZEN の判断要求に写すときの共通ルール(マッピングの形、リテラルと式の区別、適合条件)を定める
  • COAZ-MCP:その枠組みの MCP 向けバインディング。MCP の JSON-RPC メッセージを AuthZEN の判断要求に写す方法を定める

COAZ は Compatible with OpenID AuthZEN の略で、ドラフトによると「cozy(コージー)」と読むそうです。

解決しようとしている課題

そもそも MCP には、OAuth 2.1 を土台にした認可の仕組みがすでにあります(HTTP 系のトランスポート向けの任意の仕組みで、stdio 接続では使わず、環境から資格情報を得ることが推奨されています)。なぜ別の仕様が要るのでしょうか。ドラフトは、OAuth だけでは足りない点を 2 つ挙げています。

1 つ目は粒度です。アクセストークンのスコープが表すのは「このクライアントにどの範囲の操作を許すか」という粗い区分で、スコープだけでは、「この呼び出しが実際に触ろうとしている顧客レコードに、この利用者がアクセスしてよいか」のような、引数に応じた判断を標準化できません。判断そのものは残るので、置き場所と組み立て方は実装ごとにばらばらになります。前回の記事に書いた「認可ロジックがアプリに散らばる」問題の、MCP 版です。

2 つ目は主体の区別です。エージェントが自律的に動く構成では、アクセストークンがエージェント自身に発行されることがあります。このとき「エージェントは誰か」と「その背後で誰の代理として動いているか」を要求ごとに区別して伝える方法を、MCP の認可の仕組みは定めていませんでした。

COAZ-MCP は、MCP のメッセージを AuthZEN の判断要求(subject / action / resource / context)に写して PDP(ポリシー決定ポイント。認可の判断を担う側)に問い合わせることで、この 2 つに答えようとしています。

既定マッピングが受け持つ範囲

読んでみて意外だったのは、対象が「ツール呼び出し」だけではないことです。COAZ-MCP は、クライアントからサーバへ向かう ping 以外の要求それぞれに既定マッピング(default mapping)を定めていて、tools/call に限らず resources/readprompts/get なども PDP の判断を通ります。

通さないものもあります。ping と通知は素通しと定められていて PDP を呼ばず、どのマッピングにも該当しない未知のメソッドは拒否に倒します(フェイルクローズド)。逆方向、つまりサーバからクライアントへ向かう要求(sampling/createMessage など)は、クライアントのトークンで認可する枠組みに乗らないため、今回の版ではスコープ外です。

tools/call の既定マッピングは次の形です。

{
  "evaluation": {
    "subject": { "type": "identity", "id": "$token.sub" },
    "context": { "agent": "$token.?client_id" },
    "action": { "name": "tools/call" },
    "resource": { "type": "tool", "id": "$params.name" }
  }
}

頭の $ は式の印で、$token はアクセストークンのクレームを、$params は JSON-RPC の params を参照します。つまり既定では「トークンの sub が指す主体が、このツール名を呼んでよいか」という判断要求になります。メソッドごとに個別のマッピングを用意しなくても、この形で判断要求を組み立てられる、というのが土台です。

ツール単位で上書きする宣言マッピング

ただ、これだけでは 1 つ目の課題だった粒度に答えられていません。既定マッピングの resource はツール名どまりで、「どの顧客のレコードか」までは判断に使えないためです。そこで MCP サーバは、ツールの inputSchemax-authzen-mapping を書くことで、そのツールに限った宣言マッピング(declared mapping)を定義できます。値のうち、単一の $ で始まる文字列だけが CEL1 の式として評価され、それ以外はリテラルのまま使われます($ で始まるただの文字列を書きたいときは、$$ とエスケープします)。

{
  "name": "get_customer",
  "inputSchema": {
    "type": "object",
    "properties": {
      "id": { "type": "string" }
    },
    "required": ["id"],
    "x-authzen-mapping": {
      "evaluation": {
        "subject": { "type": "identity", "id": "$token.sub" },
        "action": { "name": "get_customer" },
        "resource": { "type": "customer", "id": "$params.arguments.id" },
        "context": { "agent": "$token.?client_id" }
      }
    }
  }
}

これで、トークンの sub が alice、引数の id が cust-12345 の呼び出しに対して「alice は顧客 cust-12345 に get_customer を実行してよいか」という粒度の判断要求になります。1 回の呼び出しに複数の判断が要るツール(コピー操作で、コピー元の read とコピー先の write を両方確かめる、など)のために、複数の判断を 1 つの要求にまとめる形も用意されています。

このマッピングが tools/list の応答に乗って運ばれる、という点はよくできていると感じました。MCP クライアントとサーバの間にゲートウェイを置く構成では、ゲートウェイが PEP(ポリシー実施ポイント。判断を求めて結果を適用する側)を務めますが、tools/list を中継するだけで各ツールのマッピングが手に入るので、マッピングを配るための帯域外の設定は要りません。サーバの業務ロジックを知らない基盤チームのゲートウェイでも、サーバの宣言した認可を機械的に実施できる、という分担です。

もっとも、既定マッピングなら個別のマッピングを書かずに済むことや、宣言マッピングの配布に帯域外の設定が要らないことと、準備なしで動くことは別です。この仕組みが前提にするのは、COAZ を解釈する PEP、CEL の評価器、判断を返す PDP、そして検証できる JWT 形式のアクセストークンとクレームの取り決めです。$token は JWT のクレームを参照しますが、OAuth 2.1 自体はアクセストークンを JWT に限定していません。

主体の区別と、宣言をどこまで信じるか

2 つ目の課題だった主体の区別は、マッピングの形そのものに埋め込まれています。判断要求の subject が「背後の利用者」、context の agent がエージェント、という役割分担です。subject.id の出どころは既定ではトークンの sub ですが、sub がエージェント自身を指す発行形態のために、代理関係を表す別のクレームを使うことをデプロイメント全体で取り決められるようになっています。

ただし、「この利用者なら許すが、このエージェント経由なら許さない」という判断までこの形だけで安全にできるか、というと、そうは読めませんでした。そもそも既定マッピングが agent に写す client_id は OAuth のクライアント登録の識別子で、エージェント実体の同一性を証明する値ではありません。トークンとの突き合わせが定められているのは subject.id だけで、宣言マッピングが組み立てる context.agent は検証の対象外です。エージェントの同一性を根拠にするなら、PEP か PDP がエージェント ID を別の経路で検証することになります。context.agent にも裏付けを持たせるべきかという問いも、issue として開いたままです。

宣言マッピングそのものへの信頼にも、線が引かれています。マッピングを書くのは MCP サーバ、つまり「認可される側」です。そこでドラフトは、subject.id を検証済みトークンのクレームに合わせることを推奨しています。MUST ではなく SHOULD なのは、トークンで利用者の身元を運べない構成のために、検証できない出どころから設定する余地を残しているからです。subject.type や context を含むそれ以外の属性は、信用できない入力として扱い、それだけを本人性や権限の証明にしないよう求めています。

それでも、宣言された actionresource がツールの実際の処理を正しく表しているかまでは、PEP には検証できません。マッピングの書き手であるサーバをどこまで信じるかは、脅威モデルに含めて考えることになります。許可を得たあとで転送前に操作の中身が変わることを防ぐ規定は、まだ提案されている段階です。

現在のステータス

COAZ Framework も COAZ-MCP も、ワーキンググループドラフトの第 1 版です。前回の記事で扱った Authorization API 1.0(Final Specification)とは違い、Implementer’s Draft にもなっていない、いちばん変わりやすい段階の文書です。実際、名前も文書の分け方もすでに一度変わっています。

しかもこれは OpenID の AuthZEN ワーキンググループ側のドラフトであって、MCP 本体の仕様でも、採択された MCP 拡張でもありません。MCP 側では、拡張仕様のリポジトリに提案の issue が開いている段階です。

これから試したいこと

前回挙げた検証の候補に、次を足したくなりました。

  • ツール定義に x-authzen-mapping を書いた MCP サーバを PDP と組み合わせて、tools/call を認可してみる
  • ゲートウェイを PEP にする構成で、既定マッピングだけでどこまで守れるかを確かめる

ドラフトが動いている段階なので、細部を追いかけるより、まず既定マッピングの形をそのまま動かして感触をつかむのがよさそうだと考えています。

参考リンク

Footnotes

  1. Common Expression Language。Google 発の小さな式言語で、Kubernetes の admission control などでも使われています。