MCP サーバーがユーザーの代理であなたの API を呼び出す方法:本番用トークン戦略
MCP サーバーのアウトバウンドトークン戦略を解説します:トークンパススルーやM2Mがなぜ失敗し、トークンエクスチェンジがどのようにユーザーの権限を維持するのかを説明します。
MCP サーバーのアウトバウンドトークン戦略を解説します:トークンパススルーやM2Mがなぜ失敗し、トークンエクスチェンジがどのようにユーザーの権限を維持するのかを説明します。
前回の記事では、Logto リモート MCP サーバーの構築における全体的な経験を共有しました。本記事では、アーキテクチャ設計と OAuth フローについて詳しく解説します。
MCP サーバーを境界として、リモート MCP サーバーの認証には インバウンド と アウトバウンド の二つの流れがあります:
インバウンドの流れは MCP スペックでよく定義されており、コミュニティでも多く議論・実装されています。実装ガイド も以前執筆しました。一方、アウトバウンドの流れはあまり注目されていません:MCP サーバーは自身アクセス用のトークンしか持っていない。ユーザーの代理でビジネス API をどう呼べば良いのでしょうか?
これは Logto MCP サーバー構築中、私たちが苦労し続けた課題です。Logto Cloud は典型的な B2B マルチテナント製品で、ユーザーは複数テナントに所属でき、それぞれの権限はテナントごとのロールに依存します。AI はユーザーとしてだけでなく、正しいテナントにも着地する必要があります。
この記事は私たちの意思決定プロセスに沿って進みます:
OAuth の視点では、リモート MCP サーバーは同時に2つの役割を果たします:
言い換えると、MCP サーバーは両側で異なる役割を担い、それぞれ異なるトークンを使います。MCP クライアントが OAuth で取得したトークンは MCP サーバーを audience としているため、ビジネス API で audience チェックをすると拒否されます。
つまりアウトバウンドの課題は本質的には **委任(delegation)**の問題です:MCP サーバーはどうやってユーザー権限内で、かつユーザーの資格情報を持たずに、下流 API をユーザーとして呼び出すのでしょうか?
MCP エンドポイントをビジネス API サービスに組み込んで同一プロセス・同一認証スタックを共用するのが最も直接的に見える選択肢ですが、私たちは独立デプロイを選びました:
MCP サーバーは mcp.logto.io 独自ドメインでデプロイされ、メインサービスとの私的結合はありません。将来的な OSS 化にも障壁はありません。
なお、組み込み型でもトークン問題の回避はできません:MCP エンドポイントとビジネス API が同じリソース ID になるため、MCP クライアントの取得トークンは API 全権限を帯びます。「どの権限でこの呼び出しが動くか」が単にプロセス内の権限受け渡しにスライドするだけで本質的課題は残ります。独立デプロイは権限境界の明示設計を強制し、この後の記事はその手順です。
アウトバウンドの議論はまず「MCP クライアントが取得するべきトークンの audience は何か?」という基本から始まります。
ビジネス API のリソース ID の再利用が最も単純です。Logto の Management API が既に OAuth 保護リソースなので、MCP クライアントが直接その audience でトークンを要求し、MCP サーバーがそのまま中継することもできます。初期の MCP サーバー実装の多くはこれを採りました。
しかしこのコストは大きい:MCP トークンの audience がビジネス API だと権限境界が消失します。
故に最初の決断:MCP サーバーは独立した保護リソースであり、自身のリソース ID(https://mcp.logto.io)とスコープを持つ。
これで権限境界が明確になり、アウトバウンドの問いも具体的になります:MCP クライアントのトークンは MCP サーバーしか触れない。では MCP サーバーは何で API を呼ぶ?
ここからアプローチを見ていきます。
最初のアイデアは自然なアナロジーから来ました。
Logto Console は SPA です。Management API への呼び出しは単純で、ユーザーがブラウザ内で OAuth サインインし、API 向けトークンを取得してフロントエンドでそのまま呼び出します。
では MCP サーバーも「もうひとつの Console」として振る舞えないでしょうか?MCP クライアントのサインイン時にビジネス API トークンを要求し、MCP サーバーは変換せずそのまま中継する:
この方式の魅力は圧倒的な単純さ:MCP サーバーがトークンを渡すだけです。しかし以下の明白な問題があります:
**まず上述の権限境界決定と真っ向から衝突します。**トークンパススルーでは MCP クライアントがビジネス API トークンを保有し、これは先ほど却下したパターンです。
**次に MCP サーバーが真の保護リソースでなくなります。**受け取るトークンの audience が自身でなくなるので audience チェックは無意味となり、「署名と発行者を確認するだけ」に劣化します。これは MCP スペックの認可定義(リソースサーバーとしての RFC 9728 準拠リソース宣言)に適合しません。ブラウザの SPA を装ったバックエンド サービスに過ぎません。
**さらに MCP クライアントも協力してくれません。**スペック準拠クライアントは Protected Resource Metadata にしたがい audience に MCP サーバーを指定したトークンを要求します。ビジネス API のトークン要求に強制的に切り替える標準手段がなく、この経路はクライアント側で通りません。
ユーザートークンがダメなら、MCP サーバー自身のアイデンティティはどうでしょう?
MCP サーバーに M2M(machine-to-machine)アプリケーションを与え、クライアントクレデンシャルでトークンを取得しビジネス API を呼ぶ。これは内部サービス連携の標準手法でもあります。
インバウンド認証も問題なく動作します:MCP クライアントのトークンは MCP サーバーを audience とし、サーバー側で通常通り検証します。そのうえで、MCP サーバー自身の M2M トークンで処理します。
致命的欠点は M2M トークンの権限がユーザーの権限と無関係なこと:
M2M はユーザー文脈の不要な状況(定期バッチ、システム間同期など)には適合しますが、MCP サーバーは違います:全呼び出しが特定ユーザーの代理ですから、そのユーザーの権限とアイデンティティで実行すべきです。
二つのアプローチで得られた教訓をまとめると、正しい設計要件が見えてきます:
この要件を満たす標準メカニズムが**トークンエクスチェンジ(RFC 8693)**です。MCP サーバーは「ユーザーを表現する資格情報」を認可サーバーに渡して下流 API 用トークンに交換し、ユーザーのアイデンティティと権限が交換を通じて維持されます。
Logto では、この「ユーザーを表現する資格情報」は ユーザーなりすまし(user impersonation) 機能から来ます:サブジェクトトークンです。これはサーバーが特定ユーザー向けに発行する短命資格情報で、「次のトークンエクスチェンジはこのユーザーで実行する」という意味になります。ユーザーが特別な作業や設定をする必要はありません。サブジェクトトークンは短命・単回利用で、消費されたら失効します。
このアーキテクチャには4つの役割があります。MCP サーバーは mcp.logto.io で独立デプロイされています:
ツール呼び出し一回分のフルトークンフロー:
手順解説:
**① インバウンド検証。**MCP クライアントがユーザートークン付きでツール呼び出し。トークンの audience は MCP サーバー自身のリソース ID、scope は mcp:all。MCP サーバーは署名・発行者・audience・scope を検証し、ユーザーID を取得(ここまでがインバウンド)。このトークンは下流には伝わりません。
**② サービス本人確認。**MCP サーバーは自身の M2M 資格情報でアクセストークンを取得します。専用 scope access:mcp:api は次の専用エンドポイントを呼ぶ唯一の用途です。
**③ サブジェクトトークン要求。**MCP サーバーが POST /api/mcp/subject-tokens(Cloud 側の MCP 専用エンドポイント)を呼び、二つの資格情報を同時提示:
Authorization ヘッダ:M2M トークン…「私は公式 MCP サーバー」の証明x-mcp-user-token ヘッダ:ユーザートークン…「このユーザーが私を認可しており、認可は有効」の証明Cloud 側でユーザートークンを完全検証(署名・発行者・有効期限、audience は MCP サーバーのリソース ID かつ scope に mcp:all を含むこと)。検証後、userId はトークンの sub claim から直接得る。このエンドポイントにはユーザー指定用のパラメータはありません。
この設計は M2M 資格情報の悪用を防ぎます。もしエンドポイントが任意 userId を受け入れれば、M2M 資格情報を持つ者は全ユーザーになりすませてしまう。この方式では、MCP サーバーは有効なユーザートークンが提示されて初めて当該ユーザーの資格情報を交換できます。
発行されるサブジェクトトークンは短命・単回利用です。実装ではキャッシュせず、毎回新規要求。
**④ 作業用トークンへの交換。**サブジェクトトークンで標準トークンエクスチェンジを実施、用途に応じて2種類のトークンを取得:
**⑤ アウトバウンド呼び出し。**交換済トークンでビジネス API を呼び、MCP クライアントへ適切な結果を返却します。
マルチテナントの文脈もエクスチェンジ段階で解決:テナントの選択は“接続”層でなく“交換”層に乗る。list_tenants(Cloud API トークンで取得)で候補リストし、会話中にユーザーが選べば、そのテナントの組織トークンを交換取得。すべてのテナントで単一エンドポイントにて運用でき、新規テナントも即反映されます。
過去のアプローチの失敗点と照らすと:
x-mcp-user-token チェックでアウトバウンドチェーンが遮断される振り返ると、最終設計での M2M 資格情報の役割は**「私が誰か」を証明し、「ユーザーとして振る舞う能力」はユーザーの生きた認可とともに都度交換獲得**です。
全体を通じて、リモート MCP サーバーのトークン戦略は次のポイントに集約されます:
簡単なテストです:仮に MCP クライアント所持トークンが流出しても、攻撃者ができるのは MCP サーバーでの制御ツール呼び出しだけに留まるべきです。直接 API 権限まで及ぶなら、権限境界は崩れています。
MCP エコシステムはまだ急速に進化中。インバウンド認証はスペックでカバーされていますが「MCP サーバーが下流 API をどう呼ぶか」は各実装任せ。私たちの実践が判断材料になれば幸いです。
自社製品向け MCP サーバーを構築するなら、Logto の AI 向け認証ソリューションもご参照ください:AI アプリ・エージェント・MCP サーバーの認証。この認証チェーンの実動作は Logto MCP サーバー で体験できます。
本記事で扱った技術参照: