🍦

AWS上に「パスキー認証付きMCPサーバー」を作る【Cognito + Lambda + FastMCP】

に公開

はじめに

OAuth認証付きのMCPサーバーをLambda上で動かしてみたかったので、頑張ってみました。ついでにパスキー認証にも対応しました。

アーキテクチャ

こんな感じの構成です。超シンプルですね。デプロイにはCDKを使いました。

リポジトリ

ソースコードはGitHubにおきました。
https://github.com/k-ibaraki/sample-oauth-lambda-mcp

主な使用技術

やりたいことを詰め込みすぎたので、使用した技術要素をまとめておきます。

  • Mangum
    • FastAPIなどのASGIアプリケーションをLambdaで動かすライブラリ
    • 今回はFastMCPをLambda上で動かすのに使用
  • MCPのOAuth認証
    • Streamable HTTPなどのリモートのMCPサーバーの認証手段
    • 端的に言って、実装難易度が結構高いです
  • FastMCPのOIDC Proxy
    • 2025年9月末にリリースされたFastMCPの新しい機能
    • DCR(動的クライアント登録)未対応のOIDCプロバイダーでの認証を可能とする
  • Amazon CognitoのManaged Login
    • 2024年のre:Inventで追加された機能
    • コードを書かなくてもログイン画面を用意できるマネージドな機能
    • パスキーにも対応できる
  • AWS CDK
    • AWSのリソースをコードで管理できるIaC用のフレームワーク

それでは、より詳しい説明をしていきます。

MangumでFastMCPをLambda上で動かす

今回は認証にFastMCPのOIDC Proxyを使いたく、かつLambda上で動かしたかったのですが、そのままだと動きません。方針として単純に思いつく方法は2つありました。

  • コンテナ化してLambda Web Adapterを使う
  • Mangumを使う

調べた限りだとPython製のMCPサーバーをLambdaで動かすときは前者のパターンが一般的のようです。私が以前Lambda上でMCPサーバーを動かしたときもLambda Web Adapterを使いました
正直なところそちらの方が楽だと思いますが、今回は「Cognito+Lambdaだけで作ったよ!」と言い切りたいというどうでもいい理由でMangumを採用しました。(Lambda Web Adapterを使うためにコンテナ化するのはLambdaっぽさが薄れる。ECR要るし。)

MangumとFastMCPの組み合わせが一般的にではない大きな理由として、FastMCPとMangumのライフスパン管理が競合するので「普通にコードを書くとエラーになって動かない」という致命的すぎる問題があります。
この問題は試行錯誤の結果、次のように設定することで回避できました。

  • FastMCP生成にcreate_streamable_http_appを使い、引数でstateless_http=True
    • これは、FastMCPをStatelessで動かす時に必要です。Lambda Web Adapterで動かすときにも必要な設定になります。
  • Mangum初期化時の引数でlifespan="auto"
    • 「ライフスパンが競合するならoffにしよう」という発想になりがちですが、そうするとFastMCPが正常に初期化できなくなります。autoが正解でした。
  • create_streamable_http_appMangun初期化をhandlerの中で実行
    • これが最もキーとなるポイントです。こうすることでリクエスト毎に独立してサーバーを立てるので、ライフスパン管理の競合が起こらなくなります。
    • (処理の効率が悪いので、本当はあまり使いたくない手段ではある)

サンプルレポジトリのコードだとこの部分になります。

https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/lambda/main.py#L62-L74

FastMCPのOIDC Proxyで、OAuth認証を実装

MCPのOIDC認証の実装にはFastMCPのOIDC Proxyを使います。
https://gofastmcp.com/servers/auth/oidc-proxy

OIDC ProxyはFastMCPの2.12.4(2025/09/27)にリリースされた新しい機能で、MCPサーバーにDCR未対応のOIDCプロバイダーに対応した認証を付けることができます。
MCPサーバーのOAuth対応というとDCRのイメージが強いですが、現実にはDCRに対応したOAuthプロバイダーはほとんど存在しないですし、今後積極的にDCR対応する可能性も期待はできないでしょう。しかしこのOIDC Proxyを使うことで、多くのOIDCプロバイダーの認証に対応したMCPサーバーを作ることができるようになりました。

では、このライブラリを使えば簡単に認証が実装できるかというと、今回はLambdaを使っていることで大きな問題があります。Lambdaは状態を保持できませんが、OAuthの認証処理中は状態を保持する必要があります。つまり 本来はLambdaだけではOAuth認証は実装できません。 というのが、最初に書いた「誤魔化している」点になります。

Lambdaのwarm startで、無理やり状態を保持する

本来するべきことを書くとストレージやDBにデータを保存して永続化するべきです。AWSの場合DynamoDBなどを使うのがよいでしょう。しかし今回はLambdaしか使っていません。ではどうしているかというと、Lambdaのライフサイクルを悪用しています。
Lambdaはステートレスではありますが、実際にはリクエストの度に実行環境が再生成されるわけではありません。簡単に書くと、一度起動すると同じ環境がしばらく使い回されます。これをwarm startといいます。正しく理解したい方はAWSのドキュメントを確認することをお勧めします。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtime-environment.html

OAuthでログインするためには、最低限ログインフロー中だけデータを保持していれば良いです。通常ログインフローは数十秒程度で終わるので、高確率で同じ実行環境が使用されます。そして同じ実行環境なら以前残したデータを参照できます。

なお「高確率で」と書いたことから分かるように、全く保証された動作ではありません。実際に10回に1回くらいは失敗しました。また、自分しか使ってないならいいですが、複数人が同時に使うシステムだと同時に動く実行環境の数も増えるので、ログイン失敗の確率も上がるでしょう。
また、長期的な運用にも問題があります。本来保存されるべきMCPクライアントの情報が消えます。ドラマとかでよくある「一晩寝ると記憶が消える人」状態です。この状態になると、MCPクライアント側が正しく状況を把握して適切な対応をしないと不整合がおきます。[1]

まともに使えるレベルの実装をするには、こんな誤魔化しではなく永続化をしましょう。

OIDC Proxy実装上の注意

今回の「誤魔化し」を実現するために必要なポイントがあります。先程Mangumの説明で「ライフスパン管理の競合を避けるためにcreate_streamable_http_appMangun初期化をhandlerの中で実行」と書きました。これに対してOIDC ProxyFastMCPの初期化は、グローバルスコープにしてデータを維持する為にhandlerの外で実行する必要があります。

https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/lambda/main.py#L40-L53

Amazon CognitoのManaged Loginでパスキーを実装

Amazon CognitoのManaged Loginはマネージドな機能なので該当機能をCDKで設定すれば使うことができます。
ですがパスキーは比較的新しい機能なので2025年10月時点では、CDKのL2以上のConstructが見つかりませんでした。つまり、CDKとしての便利さを享受できないので、素の設定を入れる必要があります。

具体的には、以下の3箇所でL1 Constructを使ってパスキーを有効化が必要でした。

  1. Cognitoのユーザープールにパスキーを設定

https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/stack/main-stack.ts#L38-L50

  1. Cognitoのクライアントでパスキーを許可

https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/stack/main-stack.ts#L92-L98

  1. Managed Loginブランディングの設定をしてUIをパスキーに対応

https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/stack/main-stack.ts#L100-L105

正直かなり面倒くさかったです。早くパスキー対応L2 Constructが出てくれると助かります。

CDKのStack分割について

今回は、CDKのStackを2つに分けています。

  • MainStack
    • 必要なリソースを一通り作る
  • UpdateStack
    • カスタムリソースを使って作成したリソースに正しい設定値を設定する

なぜリソース作成と設定を分けているかというと、Cognitoの設定値にLambdaのURLが必要で、Lambdaの設定値にCognitoのIDが必要なので、全部まとめて作ろうとすると依存関係を解決できないからです。

具体的には以下のように役割分担しています。

  • MainStack
    1. Cognitoのユーザープールを作成
    2. CognitoのManaged Loginを作成
    3. Cognitoアプリクライアントを作成
    4. Lambda関数を作成
    5. Lambdaの関数URLを作成
  • UpdateStack
    1. CognitoのアプリクライアントのClient Secretを取得
    2. CognitoのユーザープールにLambdaの関数URLを設定
    3. Lambdaの環境変数にCognitoの各種IDやURL等を設定

CognitoとLambdaしか作っていないのに結構大変でした。

ユーザー登録とパスキー登録について

CDKでリソースをDeployしたら、実際に使う前にユーザー登録が必要です。
今回のサンプルはユーザー登録する機能をつけていないので、ユーザー登録はCognitoのコンソールから実施します。
ユーザー管理のページから「ユーザーを作成」を押下して作りましょう。作成されたら「パスワードを強制的に変更」状態になりますが、一旦は気にしなくてOKです。

ユーザーを作成したらログインします。デプロイした時のCDKの出力にloginUrlという項目があるので、そのURLにアクセスします。
コードで言うと以下の部分です。デプロイ時の出力をロストした場合は、再デプロイして再表示するか、各種パラメータからURLを構築してください。
https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/stack/main-stack.ts#L184

ログインすると、パスワードの強制変更を要求されるので変更します。

パスワードを変更すると、MCPサーバーのLambdaにリダイレクトされます。次のようなエラーが表示されますが気にしなくてよいです。

続いてパスキーを設定します。デプロイした時のCDKの出力にuserAddPasskeyUrlという項目があるので、そのURLにアクセスします。
コードで言うと以下の部分です。デプロイ時の出力をロストした場合は、再デプロイして再表示するか、各種パラメータからURLを構築してください。
https://github.com/k-ibaraki/sample-oauth-lambda-mcp/blob/main/stack/main-stack.ts#L187

パスキー登録画面が表示されるので、画面に従って登録しましょう。

パスキー登録後は先程と同様にリダイレクトされてエラーになりますが、やはり気にしなくてよいです。
以上でパスキーの登録まで完了です。

実際にMCPサーバーを試してみた

最後に実際にClaude(web版)に登録して試してみます。
なお、ここまでMCPサーバーの中身には全く触れませんでしたが、足し算をするだけの単純なサンプルになっています。

まずは、コネクタの設定でカスタムコネクタを追加します。デプロイしたLambdaのURLを設定します。

切断状態で追加されるので「連携/連携させる」ボタンを押下します。

Cognitoの認証画面に飛ばされるので、先程登録したパスキーでログインします。

Claudeに戻って戻ってきてエラーが出なければ接続完了です。
(なお前述の通り正しい手順でも運が悪いと時々失敗するので、そのときはやり直します。)

接続完了したら、チャット画面から実際に試すとツールが使えます。

無事にLambda上のパスキー認証付きのMCPサーバーをデプロイして、使うことができました!

まとめ

軽い気持ちでパスキー認証付きMCPサーバーをLambdaで動かそうと思ったら想定より大変でしたがなんとか実装できました。

以下やったことのまとめです。

  • FastMCP+Mangumにおけるライフスパン管理の競合を回避してLambda上で動かす
  • OIDC Proxyを利用してFastMCPにCognitoを使ったOAuth認証を実装
  • Lambdaのwarm startを悪用してOAuth認証中の状態を維持
  • CognitoのManaged Login + パスキーをCDKでデプロイ
  • CDKでリソース作成後に、各種設定値をカスタムリソースを使って変更
  • Lambdaにデプロイしたパスキー認証付きMCPサーバーをClaudeから使用!
脚注
  1. この不整合によりVSCodeで不整合が発生して私は困っています。 ↩︎

NCDCエンジニアブログ

Discussion