MCPサーバーからユーザー本人の権限でAPIを呼ぶ4つの方式:トークンのパススルーを避ける選び方と実装【MCPセキュリティ 2026】
MCPサーバーから社内SaaSや外部APIをユーザー本人の権限で呼ぶ方法。トークンのパススルーが禁止される理由、仕様に沿った4方式(サービス資格情報、RFC 8693のトークン交換、URLモードのelicitationによるユーザー別OAuth・APIキー)の比較と選び方、接続リンクの本人確認コード(不正6件をすべて拒否)、14項目のチェックリスト。
最終更新:2026年10月26日 / 対象:MCP仕様 2026-07-28版(Authorization、Security Best Practices、Elicitation)、RFC 8693(OAuth 2.0 Token Exchange)、RFC 8707
結論(30秒で読める要約)
MCPサーバーから社内SaaSや外部APIを「ユーザー本人の権限で」呼びたいとき、MCP Securityの観点で最初に決めるべきなのは「下流のAPIに届くのは誰の資格情報か」です。いちばん手軽な答え、つまりMCPクライアントから受け取ったトークンをそのまま下流へ渡す方法は、MCP仕様で禁止されています(MUST NOT pass through)。それ以外は設計の選択です。
仕様に沿った方式は4つあります。
- サービス用の資格情報+サーバー側での認可:MCPサーバー自身のキーでAPIを呼び、ユーザーごとの権限はMCPサーバーが判定します。最も簡単ですが、MCPサーバーが認可の要になるため、ロジックのバグがそのまま他ユーザーへの情報漏えいになります。
- トークン交換(RFC 8693、Microsoft EntraのOn-Behalf-Of):ユーザーのMCPトークンを認可サーバーに渡し、下流API向けの新しいトークンと交換します。MCPサーバーと同じIdPを信頼している社内APIに最適です。
- URLモードのelicitationによるユーザーごとのOAuth:MCPサーバーが外部SaaSのOAuthクライアントになり、ユーザーごとのトークンを保管します。Google、Slack、GitHubなどの外部SaaS向けです。
- URLモードのelicitationによるユーザーごとのAPIキー:同じ流れで、APIキーしか提供していないSaaS向けです。MCPのフォームでキーを集めてはいけません。
目安は「下流APIが自社のIdPを信頼しているならトークン交換、独自の認可サーバーを持つならユーザーごとに別のOAuthフロー」です。サービス用の資格情報は、下流APIがユーザーという概念を持たない場合に限り、使うならユーザーごとの認可を何よりも厳しくテストします。7章では、多くの実装が見落とす確認「外部サービスのログインを完了した人が、それを始めた本人か」を行うコードを、テスト結果つきで載せています。
目次
- MCPサーバーが「ユーザーの代わりに」APIを呼ぶとは
- トークンのパススルーが禁止されている理由
- 仕様に沿った4つの方式の比較
- どの方式を選ぶか
- 方式2の実装:トークン交換
- 方式3・4の実装:URLモードのelicitation
- 検証:接続用リンクを正しいユーザーに結びつける
- ユーザーごとの下流トークンの保管
- 実際の事例から学ぶこと
- チェックリスト(14項目)
- よくある失敗(症状→原因→対処)
- NodeFlareならこう解決できる
- よくある質問
1. MCPサーバーが「ユーザーの代わりに」APIを呼ぶとは
ユーザーの代わりにAPIを呼ぶとは、下流のAPIがMCPサーバーの権限ではなく、MCPクライアントを使っている本人の権限を適用することです。社内SaaSで経理プロジェクトを見られない社員は、MCP経由のツール呼び出しでも見られないのが正しい状態です。
APIを包むMCPサーバーは、2つの役割を同時に持っています。MCPの認可仕様に沿って整理すると次のとおりです。
| 区間 | MCPサーバーの役割 | 資格情報 | 発行元 |
|---|---|---|---|
| MCPクライアント → MCPサーバー | OAuth 2.1のリソースサーバー | 宛先(audience)がこのMCPサーバーのアクセストークン | MCPサーバー用の認可サーバー |
| MCPサーバー → 下流API | OAuthクライアント(またはAPIキーの保持者) | 下流API用の別の資格情報 | 下流APIの認可サーバー、またはAPI自身 |
仕様はこの境界をはっきり書いています。「MCPサーバーが上流のAPIへリクエストする場合、そのAPIに対してはOAuthクライアントとして振る舞ってよい。上流APIで使うアクセストークンは、上流の認可サーバーが発行する別のトークンである」。つまり問うべきは「ユーザーのトークンを使い回せるか」(できません)ではなく、「ユーザーを表す2枚目の資格情報をどう手に入れるか」です。
2. トークンのパススルーが禁止されている理由
トークンのパススルーとは、MCPサーバーがクライアントから受け取ったトークンを、そのまま下流のAPIへ転送することです。2026-07-28版の仕様は2か所でこれを禁じています。サーバーは「他のトークンを受け入れたり中継したりしてはならない(MUST NOT accept or transit)」、そして「MCPクライアントから受け取ったトークンをパススルーしてはならない(MUST NOT pass through)」。
Security Best Practicesのページが挙げる理由は次の4つです。
| リスク | 何が起きるか |
|---|---|
| セキュリティ対策の迂回 | トークンの宛先に依存したレート制限や検証がすり抜けられる |
| 監査証跡の崩壊 | 下流APIにはエンドユーザーのトークンが届くため、ログに残る主体が実際と食い違う |
| 信頼境界の破綻 | 複数のサービスで通用するトークンがあると、1つを破られただけで他にも入られる |
| データの持ち出し | クレームを確認しないサーバーは、盗まれたトークンの「踏み台」として使われる |
実務上の理由もあります。下流のAPIがMCP用のトークンを受け付けるなら、そのトークンは2つのサービスで有効だということです。これはまさに、RFC 8707による宛先の限定(audience binding)が防ごうとしている使い回しです。
3. 仕様に沿った4つの方式の比較
どの方式でも、下流APIにはそのAPI向けに発行された資格情報が届きます。違うのは「誰が権限を判定するか」と「MCPサーバーが何を保管するか」です。
| 1. サービス資格情報+サーバー側認可 | 2. トークン交換 | 3. ユーザーごとのOAuth(URL elicitation) | 4. ユーザーごとのAPIキー(URL elicitation) | |
|---|---|---|---|---|
| 下流に渡る資格情報 | MCPサーバーが持つキー1つ | リクエストごとの短命トークン(宛先=下流API) | ユーザーごとのアクセストークンとリフレッシュトークン | ユーザーごとのAPIキー |
| ユーザー権限を判定するのは | MCPサーバー | 下流API | 下流API | 下流API |
| 下流のログに残る主体 | サービスアカウント(ユーザー名は自前で付けた場合のみ) | ユーザー(IdPが act クレームを付ければ代理人も) |
ユーザー | ユーザー |
| MCPサーバーが保管するもの | シークレット1つ | ユーザーごとには何も保管しない | ユーザーごとのリフレッシュトークン | ユーザーごとのAPIキー |
| 前提条件 | 自社API、またはサービス用の権限を持つAPI | 下流APIが同じIdPを信頼し、IdPがRFC 8693かOBOに対応 | SaaSが独自のOAuthを持ち、クライアントがURLモードに対応 | クライアントがURLモードに対応 |
| 仕様上の根拠 | Authorization:Access Token Privilege Restriction | RFC 8693/仕様の「上流トークンは別物」 | Elicitation:URL Mode for OAuth Flows | Elicitation:URL Mode for Sensitive Data |
| 主なリスク | 認可バグによるユーザー間・テナント間の漏えい | IdPのポリシーが広すぎる、交換時に権限を絞っていない | トークン保管庫の侵害、接続用リンクを使ったフィッシング | 3と同じ+期限のないキー |
方式1は間違いではありません。仕様が求めているのは「別の資格情報であること」だけです。ただし、認可の判断がすべて自分で書いたコードに移り、そこが最もバグを生みやすい場所になります(9章)。
4. どの方式を選ぶか
下流APIが「何を信頼しているか」で選ぶのが近道です。
| 状況 | 選ぶ方式 | 理由 |
|---|---|---|
| 社内APIで、MCPサーバーと同じIdP(Entra ID、Okta、Keycloakなど)を使っている | 2. トークン交換 | APIがユーザーごとの権限を判定し続け、MCPサーバーは何も保管しない |
| OAuthを持つ外部SaaS(Google Workspace、Slack、GitHub、Salesforceなど) | 3. ユーザーごとのOAuth | SaaS自身の同意画面と権限がユーザーごとに適用される |
| APIキーしかない外部SaaS | 4. URL elicitationでユーザーごとのキー | キーをクライアントやモデルのコンテキストに通さずに済む |
| ユーザーという概念がないAPI(天気、検索、公開データ) | 1. サービス資格情報 | 守るべきユーザー固有のデータがない。キーは読み取り専用に絞る |
| 自社APIだが、まだIdPと連携できない | 1で始め、2へ移行 | サーバー側でユーザーごとの判定を入れ、セキュリティのコードとしてテストする |
| 人間以外の呼び出し元(CI、定期実行のエージェント) | 1、またはClient Credentials | 代理する「本人」がいない |
判断を誤りやすいケースが2つあります。1つは「SaaSに管理者用のなりすましヘッダーがある」場合です。これは方式2ではなく方式1です。サーバーが「誰にでもなれる」強い資格情報を持つので、漏れたときの影響は全ユーザーに及びます。もう1つは、方式4でチーム共有のキーを使う場合です。これはユーザーごとではなく、ローテーションがしにくくなっただけの方式1です。
5. 方式2の実装:トークン交換
トークン交換では、MCPサーバーがユーザーのMCPアクセストークンを認可サーバーに提示し、宛先が下流APIの新しいトークンを受け取ります。ユーザーのMCPトークン自体はMCPサーバーから外に出ないので、パススルーにはあたりません。
リクエストの形はRFC 8693で決まっています。grant typeは urn:ietf:params:oauth:grant-type:token-exchange で、主なパラメーターは次のとおりです。
| パラメーター | MCPサーバーでの値 |
|---|---|
subject_token |
検証済みのMCPアクセストークン(宛先=このMCPサーバー) |
subject_token_type |
urn:ietf:params:oauth:token-type:access_token |
audience または resource |
下流APIの識別子 |
scope |
そのツールに必要なものだけ(例:crm.contacts.read) |
actor_token(任意) |
MCPサーバー自身のトークン。結果のトークンに act クレームを付けるため |
POST /oauth2/token HTTP/1.1
Host: idp.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <MCPサーバーのクライアント資格情報>
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<検証済みのMCPアクセストークン>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=https://crm.internal.example.com
&scope=crm.contacts.read
RFC 8693は2つの意味を区別しています。なりすまし(impersonation)では、新しいトークンは単にユーザー本人のように見えます。委任(delegation)では、トークンに act クレームが入り、MCPサーバーが代理人として記録されます。IdPが対応しているなら委任を選ぶと、下流のログに「誰が」と「どのMCPサーバー経由で」の両方が残ります。
2026年10月時点の主なIdPの対応状況です。
| IdP | 仕組み | 補足 |
|---|---|---|
| Microsoft Entra ID | On-Behalf-Of(OBO)フロー | grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer、requested_token_use=on_behalf_of。受け取ったトークンの aud が、OBOを要求するアプリ自身である必要がある |
| Keycloak 26.2以降 | Standard Token Exchange(RFC 8693) | 26.2で正式サポート。クライアントごとに有効化する。なりすましと外部IdPのトークン交換は今後の対応予定とされている |
| Auth0 | Token Vault | 外部サービスのトークンを保管し、Auth0のトークンと交換で取り出す。方式3と重なる |
安全に使うためのルールは3つです。
- 検証済みのトークンだけを交換に出す(発行者、署名、
aud、有効期限)。Entraのドキュメントも「別のアプリ向けのトークンは引き換えられない」と明記しています。 - 交換のたびに権限を絞る。そのツールに必要な下流のスコープだけを要求し、ユーザーが持ちうる全権限は要求しない。
- どのクライアントがどの宛先へ交換できるかを、IdPのポリシーで制限する。Keycloakではクライアントポリシーで設定できます。
MCP側でのトークン検証そのものは、MCP OAuthサーバー実装ガイドで詳しく解説しています。
6. 方式3・4の実装:URLモードのelicitation
URLモードのelicitationは、MCPクライアントからは中身が見えないWebページへユーザーを案内するための、仕様上の仕組みです。2025-11-25版で追加され、「今後の改訂で変わる可能性がある」機能と位置づけられています。用途はまさにこの記事のテーマで、仕様は「MCPサーバーが、ユーザーの代わりに機密情報や第三者の認可を得る必要があるとき」に使うものだと説明しています。
2026-07-28版では、サーバーは elicitation/create を含む InputRequiredResult を返します。クライアントはユーザーに確認してURLを開き、ユーザーの回答とサーバーの requestState を付けて、元の tools/call を再送します。
{
"method": "elicitation/create",
"params": {
"mode": "url",
"url": "https://mcp.example.com/connect?t=<署名付き・1回限りのトークン>",
"message": "連絡先を読み取るため、CRMのアカウントを接続してください。"
}
}
ユーザーごとのOAuth(方式3)の流れは次のとおりです。
- ツール呼び出しにCRMへのアクセスが必要だが、このユーザーの
subに対応するトークンが保管されていない。 - サーバーはURL elicitationを返す。URLの行き先はSaaSの認可画面ではなく、自社の接続用ページにする。
- ユーザーが通常のブラウザでページを開く。ページは、ログイン中のユーザーがelicitationを発生させた
subと同じかを確認する(7章)。 - ページは新しい
stateを付けてSaaSの認可画面へリダイレクトする。ユーザーはSaaS自身の画面で同意する。 - SaaSが自社サーバーへ戻し、サーバーは認可コードをトークンに交換して、
subに結びつけて保管する。 - クライアントがツール呼び出しを再送し、サーバーは保管したトークンでAPIを呼ぶ。
仕様の要件をサーバー側とクライアント側に分けて整理します。
| 側 | 要件(2026-07-28版 Elicitation) |
|---|---|
| サーバー | パスワード、APIキー、アクセストークン、決済情報をフォームモードで求めてはならない(MUST NOT)。URLモードを使う(MUST) |
| サーバー | URLに資格情報や個人情報を含めてはならない。認証済みの状態で使えるURLを送ってはならない |
| サーバー | URL elicitationで得た資格情報をMCPクライアントへ送ってはならない |
| サーバー | MCPサーバー自体へのログインにURL elicitationを使ってはならない |
| サーバー | URLを開いたユーザーが、elicitationを発生させたユーザーと同じであることを確認しなければならない |
| サーバー | ユーザーの識別はMCPの認可(sub クレームなど)から行い、クライアントが送ってきた値を信じない |
| クライアント | URL全体を表示し、同意なしに開いたり先読みしたりせず、クライアントやモデルが中身を読めない方法で開く |
方式4も流れは同じです。接続用ページにHTTPSのフォームを置いてユーザーにAPIキーを貼り付けてもらい、サーバーが sub に結びつけて保管します。キーがMCPクライアントにもモデルのコンテキストにも通らないことが、この方式の要点です。
注意点として、URLモードはクライアント側の対応が必要です。対応クライアントは機能宣言に "elicitation": {"url": {}} を含めます。仕様は「クライアントが対応していないモードでelicitationを送ってはならない」としているので、未対応のクライアントには、フォームに切り替えるのではなく「ブラウザでこのページからアカウントを接続してください」と伝えるツールエラーを返します。
7. 検証:接続用リンクを正しいユーザーに結びつける
仕様は、URL elicitationを悪用したフィッシングを具体的に説明しています。悪意のあるユーザーAliceがelicitationを発生させ、そのリンクを同じサービスを使うBobに開かせます。BobがSaaSにログインすると、Bobのトークンが「Aliceのアカウント」に結びつき、アカウント乗っ取りになります。仕様が求める対策は、「elicitationを始めたユーザーと、認可フローを完了したユーザーが同一であることを保証しなければならない(MUST)」です。
次のコードは、検証済みの sub に結びつけた署名付き・1回限り・有効期限10分の接続用トークンを発行し、SaaSへリダイレクトする前に、自社サイトにログインしているユーザーと照合します。Python標準ライブラリだけで動きます。
"""URLモードのelicitationを、それを発生させたMCPユーザーに結びつける。
標準ライブラリのみ。Python 3.13で動作確認済み。"""
import base64, hashlib, hmac, json, secrets, time
SECRET = secrets.token_bytes(32) # 本番ではシークレットストアから読み込む
TTL = 600 # 10分
_used: set[str] = set() # 本番では有効期限付きのRedisやDBを使う
def _b64(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def _unb64(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
def mint_connect_token(sub, provider, now=None):
"""tools/call の中で呼ぶ。sub は検証済みのMCPアクセストークンから取る。"""
now = now or time.time()
body = json.dumps({"sub": sub, "prv": provider, "jti": secrets.token_urlsafe(16),
"exp": int(now) + TTL}, separators=(",", ":")).encode()
return _b64(body) + "." + _b64(hmac.new(SECRET, body, hashlib.sha256).digest())
def redeem_connect_token(token, session_sub, now=None):
"""/connect でSaaSへリダイレクトする前に呼ぶ。
session_sub は自社サイトのセッションCookieから得たログイン中のユーザー。"""
now = now or time.time()
try:
body_s, mac_s = token.split(".")
body, mac = _unb64(body_s), _unb64(mac_s)
except Exception:
raise PermissionError("malformed")
if not hmac.compare_digest(mac, hmac.new(SECRET, body, hashlib.sha256).digest()):
raise PermissionError("bad signature")
claims = json.loads(body)
if claims["exp"] < now:
raise PermissionError("expired")
if session_sub is None:
raise PermissionError("not logged in")
if not hmac.compare_digest(claims["sub"], session_sub):
raise PermissionError("user mismatch") # 仕様が挙げるフィッシングのケース
if claims["jti"] in _used:
raise PermissionError("replayed")
_used.add(claims["jti"])
return claims # ここで初めて、claims["sub"] に結びつけた新しい state でSaaSのOAuthを開始する
7つのケースで試したところ、正当な1件だけが通り、不正な6件はすべて拒否されました。
| ケース | 結果 |
|---|---|
| 本人が10分以内にリンクを開く | 受理 |
| ログイン中の別ユーザーがリンクを開く(仕様のフィッシング例) | 拒否:user mismatch |
| 誰もログインしていない | 拒否:not logged in |
攻撃者がトークン内の sub を書き換える |
拒否:bad signature |
| 11分以上たってから開く | 拒否:expired |
| 同じリンクを2回使う | 拒否:replayed |
| でたらめなトークン | 拒否:malformed |
このコードがあえて扱っていない点が2つあります。session_sub は自社のログインセッションから取る必要があり、MCPトークンを発行するのと同じ認可サーバーでログインさせると、2つの sub をそのまま比較できます。また、SaaS側のOAuthで使う state は、これまでどおり1回限り・短い有効期限にし、上の確認が通った後で初めて発行します。
8. ユーザーごとの下流トークンの保管
方式3と4を選ぶと、MCPサーバーは状態を持つことになります。仕様も「MCPサーバーが第三者のトークンの保管と管理に責任を持つ」と書いています。この保管庫は、接続した全ユーザーの有効な資格情報が入った、システムで最も価値の高い場所になります。
| 要件 | 理由 |
|---|---|
レコードのキーは検証済みMCPトークンの sub にし、ツールの引数から来た値は使わない |
他人のトークンを読まれるのを防ぐ(仕様の「IDを持っていることは認証ではない」という原則) |
| トークンは暗号化して保存し、暗号鍵はDBの外に置く | DBのダンプだけでは使えるトークンが手に入らないようにする |
| リフレッシュトークンを保管し、アクセストークンは使う直前に取得する。どちらもログに出さない | 短命なアクセストークンで漏えい時の被害を抑える |
| SaaSのスコープは最小限で始め、書き込み系ツールを初めて使うときに追加で求める | MCP仕様のスコープ最小化の考え方と同じ |
| ユーザーが接続を解除でき、解除時にはSaaS側でも失効させる | 自社のレコードを消しても、提供元のトークンは生きている |
| 退職・異動時は、そのユーザーのレコードを消して提供元でも失効させる | 退職者のCRMトークンがサーバーに残らないようにする |
| 下流のトークンをツールの結果に含めない | URL elicitationで得た資格情報はMCPクライアントへ送ってはならない(仕様) |
自分でホスティングする場合、この保管庫は再起動や再デプロイをまたいで残る必要があります。選択肢はホスティングしたMCPサーバーの永続ストレージの記事で整理しています。保管庫を自作したくなければ、Auth0 Token Vaultのような管理型の保管サービスにトークンを預け、MCPサーバーはトークン交換で取り出す方法もあります。その場合も、接続用ページでの本人確認は同じように必要です。
9. 実際の事例から学ぶこと
下流の資格情報の選び方が結果を左右した公開事例が2つあります。
| 事例 | 何が起きたか | 下流の認証への教訓 |
|---|---|---|
| AsanaのMCPサーバー(2025年) | 2025年5月1日に公開。6月4日に見つかったロジックの不具合により、一部のデータが「1か月以上」他組織のユーザーから見える状態だった。AsanaはBleepingComputerに影響は「約1,000社」と説明 | 誰が何を見られるかをMCPサーバーが判定する方式1では、認可のバグがそのままテナント間の漏えいになる。そのコードはセキュリティのコードとして、2テナント以上でテストする |
| GitHub MCPの「toxic agent flow」(Invariant Labs、2025年5月) | 公開リポジトリのIssueに仕込まれたプロンプトインジェクションにより、エージェントがユーザーの非公開リポジトリを読み、その内容を公開側に投稿した | タスクより広い下流の資格情報は、インジェクションを情報の持ち出しに変える。Invariantは「必要なリポジトリだけ」にアクセスを絞るよう勧めている |
方式2や3を選んでも、プロンプトインジェクションそのものは防げません。できるのは次の2つです。下流APIがユーザー本人の権限を判定し続けること、そしてツールごとに権限を絞ることで、注入された指示が届く範囲を狭めることです。呼び出し元ごとに絞った資格情報の発行方法はMCP APIキー管理の実践ガイドにまとめています。書き込み系のツールには、人の確認も残しておきましょう。
10. チェックリスト(14項目)
受け取り側(クライアント → MCPサーバー)
- [ ] すべてのリクエストのトークンについて、発行者・署名・有効期限・宛先(このMCPサーバー)を検証している
- [ ] ユーザーの識別は検証済みトークンの
subで行い、ツールの引数やクライアントが操作できるヘッダーは使わない - [ ] 受け取ったトークンを他のサービスへ転送していない
方式の選択
- [ ] 下流APIごとに、採用した方式(1〜4)と理由を一覧にしている
- [ ] ユーザーごとの方式が使えるAPIで、管理者権限やなりすまし用の資格情報を使っていない
- [ ] 方式1のサービスには、2ユーザー・2テナント以上でのユーザー別認可テストがある
トークン交換
- [ ] 交換時に宛先を指定し、そのツールに必要な最小限のスコープだけを要求している
- [ ] どのクライアントがどの宛先へ交換できるかを、IdPのポリシーで制限している
- [ ] IdPが委任に対応していれば、下流のトークンに
actクレームが付いている
URLモードのelicitationとトークン保管庫
- [ ] 資格情報をフォームモードのelicitationで求めていない
- [ ] 接続用ページで、ログイン中のユーザーとelicitationを発生させたユーザーの一致を確認し、リンクは1回限りで期限がある
- [ ] 保管したトークンは暗号化し、
subをキーにし、ツールの結果やログに出していない - [ ] ユーザーが接続を解除でき、解除や退職時には提供元でも失効させている
- [ ] MCPサーバーからの外向き通信を、下流APIのホストだけに絞っている
11. よくある失敗(症状→原因→対処)
| 症状 | 原因 | 対処 |
|---|---|---|
| 下流APIが、MCPサーバー宛てに発行されたトークンを受け付ける | APIが aud を確認しておらず、サーバーがトークンを転送している |
転送をやめ、トークン交換か別のOAuthフローに切り替える。API側の宛先確認も直す |
| 全ユーザーに同じCRMのレコードが見える | 方式1でサービス用のキーを使い、ユーザーごとの絞り込みがない | 方式2か3へ移行するか、ユーザーごとの判定とテストを追加する |
| Aliceのツール結果にBobのGoogleドライブが出てくる | 接続用リンクが、それを発生させたユーザーに結びついていない | リダイレクト前に、セッションのユーザーと署名済みの sub を照合する(7章) |
| ユーザーがチャットのフォームにAPIキーを貼り付けた | 機密情報にフォームモードのelicitationを使った | 自社ドメインのHTTPSページを使うURLモードに切り替える |
| 退職者のトークンがSaaSでまだ有効 | 接続解除で自社のレコードは消したが、提供元で失効させていない | 解除・退職時に提供元の失効エンドポイントを呼ぶ |
| トークン交換で、ユーザーの全スコープを持つトークンが返ってくる | 交換リクエストに scope や宛先の制限がない |
ツールごとのスコープを要求し、許可する宛先をIdPのポリシーで決める |
| 一部のクライアントで「未対応のモード」としてツールが失敗する | クライアントが elicitation.url を宣言していない |
機能宣言を確認し、未対応なら接続用ページを案内するエラーを返す |
12. NodeFlareならこう解決できる
NodeFlareはMCPサーバーのホスティング基盤です。トークン交換や外部SaaSのOAuthフローを代わりに実行するものではなく、方式2〜4はあなたのサーバーのコードで実装します。NodeFlareが担うのは、そのコードの周りを固める対策です。この記事で挙げたリスクに沿って対応を整理します。
| この記事のリスク | NodeFlareの機能 | 内容 |
|---|---|---|
| 呼び出し元が、使ってはいけないツールに届く(2章・受け取り側) | ツール単位のスコープ | tools:call:<ツール名>、resources:read:<uri>、prompts:get:<name> などのスコープを、リクエストがサーバーに届く前にMCPのメソッドと対象ごとに確認します。拒否時はJSON-RPCエラーを返し、サーバーには到達しません。tools/list には、その呼び出し元が使えるツールだけが表示されます |
| 全員で1つの資格情報を共有している | 呼び出し元ごとの mcp_* APIキーとOAuth |
キーはハッシュ化して保存され、同じIPから誤ったキーが続くとそのIPを10分間ロックします。OAuthのディスカバリーはRFC 8414とRFC 9728に沿っています |
| ユーザーのトークンをサーバー自身で検証したい(方式2〜4) | auth_enabled = false |
呼び出し元の資格情報をそのままサーバーへ渡すので、サーバー側でトークンを検証し sub を読めます。この場合NodeFlareのツール単位のスコープ確認はその呼び出し元に適用されないため、サーバー側で同等の確認が必要です |
| サービス用の資格情報、交換用のクライアントシークレット、暗号鍵が漏れる | 暗号化されたシークレット | 環境変数は保存時に暗号化され、VMの起動時にだけ注入されます。ビルド引数、イメージのレイヤー、ログには残りません。ビルドログでは既知の形式のトークンが伏せ字になります |
| 盗まれた下流トークンが別の宛先へ送られる | Egress制御 | モードは off(既定)、observe(記録のみ)、enforce(遮断)。許可リストは最大100件で、完全なホスト名、先頭1つのワイルドカード(例:*.example.com)、IPアドレスを指定でき、DNSのホスト名でホスト側から強制します |
| 接続用ページやMCPエンドポイントに、どこからでも届いてしまう | 送信元IPの許可リスト | restricted ポリシーでは、登録したIPv4/IPv6のCIDRだけを受け付けます。認証の前、VMの起動前に判定し、リストが空なら全拒否になります |
| あるサーバーが破られ、別のサーバーのトークン保管庫に届く | Firecracker microVMによる分離 | サーバーごとに専用のmicroVMで動き、NodeFlareが運用する専用の物理サーバー上で稼働します |
| 誰が何を呼んだか記録がない | 監査ログ | 呼び出しごとに caller_kind(api_key/oauth/anonymous)、client_ip、outcome(scope_denied など)、http_status を記録します。引数は値ではなくキー名だけを残します。保持期間はFreeが7日、ProとTeamが30日です |
できないことも明記しておきます。NodeFlareはプロンプトインジェクションそのものを防ぐものではなく、ツール呼び出しが下流のどのユーザーとして動くかも把握しません。その結びつけはサーバー側の役割です。また、ユーザーごとのトークン保管庫には永続ストレージが必要です。Freeプランは一時ストレージのみで、ProとTeamには10GBが含まれます。外部のデータベースに保管庫を置く方法もあります。
最小の始め方:MCPサーバーをデプロイし、下流用のサービス資格情報やOAuthのクライアントシークレットを暗号化された環境変数に入れます。呼び出し元ごとに必要な tools:call:<ツール名> だけを持つ mcp_* キーを発行し、Egressを observe で動かしてから、SaaSのホスト名を登録して enforce に切り替えます。1サーバーまでは無料で試せます → nodeflare.tech
13. よくある質問
MCPサーバーは、ユーザーのアクセストークンを自社APIへそのまま転送してよい?
いいえ。それはトークンのパススルーで、MCP仕様が禁止しています(MCPクライアントから受け取ったトークンをパススルーしてはならない)。下流APIには、トークン交換で得たトークンや、そのAPI用にユーザーが認可したOAuthトークンなど、別の資格情報を使います。
MCPサーバーからユーザー本人の権限でAPIを呼ぶには?
下流APIでユーザーを表す2枚目の資格情報を手に入れます。APIが同じIdPを信頼しているならトークン交換(RFC 8693やEntraのOn-Behalf-Of)、APIが独自の認可サーバーを持つならURLモードのelicitationで始めるユーザーごとのOAuthフローが一般的です。
トークン交換とトークンのパススルーは何が違う?
トークン交換では、MCPサーバーはユーザーのトークンを認可サーバーにだけ送り、宛先が下流APIの新しいトークンを受け取ります。下流APIはMCP用のトークンを一度も目にしないので、パススルー禁止の趣旨に反しません。
MCPのURLモードのelicitationとは?
MCPサーバーがクライアントに対し、外部サービスへのログインやAPIキーの入力など、クライアントを通すべきでないやり取りのためにWebページを開くよう求める仕組みです。2025-11-25版の仕様で追加され、クライアントはURL全体を表示し、ユーザーの同意を得てから開く必要があります。
MCPのelicitationのフォームでAPIキーを入力してもらってよい?
いいえ。仕様は、パスワード、APIキー、アクセストークン、決済情報をフォームモードで求めることを禁じ、URLモードを使うよう定めています。URLモードなら、機密情報がMCPクライアントにもモデルのコンテキストにも入りません。
URLモードのelicitationにはどんなフィッシングのリスクがある?
攻撃者が自分でelicitationを発生させ、そのリンクを別のユーザーに開かせると、被害者の外部サービスのトークンが攻撃者のアカウントに結びついてしまいます。サーバーは、リンクを開いたユーザーがelicitationを発生させた本人かを、署名付きリンク内の sub とログイン中のセッションを照合するなどして確認しなければなりません。
下流APIにはサービスアカウントを使ってもよい?
下流APIにユーザー固有のデータがない場合か、IdPとまだ連携できない自社APIの一時的な手段としてなら使えます。サービスアカウントでは各ユーザーの権限をMCPサーバー自身が判定するため、認可のバグがユーザー間の情報漏えいに直結します。
ユーザーの外部サービス用OAuthトークンはどこに保管すべき?
検証済みMCPトークンの sub をキーにした暗号化ストアに保管し、暗号鍵はデータベースの外に置きます。保管するのはリフレッシュトークンにし、アクセストークンは必要なときに取得します。ログには出さず、接続解除や退職時には提供元でも失効させます。
MCP仕様はトークン交換を必須にしている?
いいえ。仕様が求めているのは、上流用のトークンが別物であることと、MCPトークンをパススルーしないことだけで、上流用トークンの取得方法は決めていません。トークン交換、別のOAuthフロー、サーバー自身の資格情報のどれも、正しく実装すれば仕様に沿っています。
トークン交換のactクレームは何のため?
RFC 8693で定義された act クレームは、委任が行われたことと、ユーザーの代わりに動いた主体を記録します。これがあると、下流APIのログに「どのユーザーが」と「どのMCPサーバー経由で」の両方が残ります。
まとめ
下流APIの呼び出しについてMCP仕様が決めているのは、「サーバーが受け取ったトークンはサーバーの外に出さない」という1点だけです。残りは方式の選択です。下流APIが同じIdPを信頼しているならトークン交換、独自の認可サーバーを持つならURLモードのelicitationによるユーザーごとのOAuth、APIキーしかないなら同じURLフローでユーザーごとのキー、代理すべきユーザーがいないときに限ってサービス用の資格情報を使います。どれを選んでも、資格情報は検証済みの sub に結びつけ、接続フローを終えた人が始めた本人であることを確かめ、プロンプトインジェクションが成功しても届く範囲が狭くなるよう、下流の資格情報を絞っておきましょう。
参考資料(一次情報)
- MCP仕様 2026-07-28版:Authorization
- MCP仕様 2026-07-28版:Authorization Security Considerations
- MCP Security Best Practices(Token Passthrough、Confused Deputy、State Handle Hijacking)
- MCP仕様 2026-07-28版:Elicitation(URLモード)
- RFC 8693:OAuth 2.0 Token Exchange
- RFC 8707:Resource Indicators for OAuth 2.0
- Microsoft identity platform:On-Behalf-Ofフロー
- Keycloak:Standard Token Exchange is now officially supported in 26.2
- Auth0:Token Vault
- BleepingComputer:Asana warns MCP AI feature exposed customer data to other orgs
- Invariant Labs:GitHub MCP exploited
- NodeFlareドキュメント
