MCP handshake(initialize)に外部 API 依存の検証を挟まない — 失敗が 401 challenge にならず OAuth 回復が構造的に死ぬ
MCP
OAuth
API設計
知識
判断
OAuth 保護された Streamable HTTP MCP サーバーで、接続オプション(対象スコープの解決など)を initialize リクエストの処理中に外部 API 呼び出しで検証すると、その API 呼び出しの失敗(トークン期限切れ・401・ネットワークエラー・想定外レスポンス)が handshake の失敗として表面化する。未捕捉例外を一律の汎用エラー(400 系の Bad request など)に丸めていると、クライアントには原因不明の JSON-RPC エラーしか見えず、接続自体が確立できない。
なぜ回復不能になるか
MCP クライアントの OAuth 回復フローは「HTTP 401 + WWW-Authenticate challenge を受けたら再認可する」ことで動く。handshake 中の外部 API 起因の失敗を 401 challenge 以外で返すと、クライアントは再認可のトリガーを得られず、単なる起動失敗として諦める。対照的に、handshake でトークンを検証しないサーバーなら、期限切れトークンでも initialize は成功し、後続のツール呼び出しで 401 を受けた時点で回復できる。つまり「handshake に検証を足す」ことは、失敗時の回復経路を潰す非対称を生む。
判断基準
- initialize は外部依存なしで常に成功させる。接続オプションの解決・検証は最初のツール呼び出しまで遅延させ、失敗はツールエラー(isError)として文脈付きで返す。
- handshake 中にどうしても外部 API を呼ぶ設計にする場合は、認証起因の失敗を明示的に捕捉して 401 + WWW-Authenticate challenge に変換し、それ以外も専用のエラーメッセージで返す。汎用 catch に落とさない。
- 下流 API が「該当なし」を空配列でなく JSON の null で返すと、クライアント側の添字アクセスが型エラーで落ちて同じ汎用エラーに合流する。境界の失敗系(不正トークン・不存在の識別子・null レスポンス)ごとに、クライアントが受け取るステータスとメッセージを個別にテストする。
検証
無効なトークン+オプション付き接続で initialize を送り、401 challenge(または明確なエラーメッセージ)が返ることを確認する。オプションなしとオプションありで、同じ無効トークンに対する挙動が非対称になっていないかを比較するのが検出の近道。