
Cognito認証認可リモートMCPサーバーをAWS App Runnerにホスティングする(実験的)
はじめに
Cloudflareを使ったMCPサーバーの認証、認可による保護方法が解説されています
GitHub OAuth AppとCloudflare Workersを使った例はこちらの記事が参考になります。
上記の記事では、Cloudflareが公開しているデモ用のソースコードが使われており、GitHubのOAuth Appを使ったサードパーティ認証フロー付きリモートMCPサーバーの解説でよく利用されます。
MCPをSSEで提供する場合、ユーザー数分ストリームを維持する必要があります。(Streamable HTTPだともっと楽になるのですが...。)また認証、認可のフロー上永続化が必要な部分もありますし、セッション管理も必要です。Cloudflare Workersは軽量なV8 isolateベースのエッジコンピューティング環境、KVは高速読み取りの分散キーバリューストア、Durable Objectsは強い整合性を持つステートフル実行環境であり、これらを組み合わせることでリモートMCPサーバーでSSEを使ったリアルタイム通信と永続化、セッション維持が出来ます。前述したサンプルは、これらを活用したコードになっています。
今回の本題ですが、そのサンプルの認証、認可処理は、CloudflareがMITライセンスで公開しているworkers-oauth-provider(Beta版)が使われています。Cloudflare Workers上で動くOAuth 2.1プロバイダ実装用のTypeScriptライブラリです。
前述の通り、エコシステムに依存はしているものの、コードは2000行くらいでした。今回cloudflare依存部分を剥がして、AWS上で同じように動かせないか試したところ、動作しました。
今回はせっかくなので、サードパーティ認証にCognitoを利用して、前述のCloudflareと同じようなサンプルを作ってみました。
リモートMCPサーバーの認証、認可の学習教材として良いかなと思ったのでコードを供養公開しようと思います。(※ 私自身まだ処理を完全に追えていないのでおって詳細は今後学習、記事化します...)
実装はこちらに置いています。本記事で紹介するのは実装の一部ですので、不明点がありましたら適宜参照してください。
forkしたworkers-oauth-providerはnpm linkしてバンドルしているので、手元で動かすのが大変かもです。その場合リポジトリのDockerfileを参考に手順を踏めば再現可能だと思います。コンテナで動かせばより確実です。
構築手順
デプロイ
CDKを使ってデプロイします。スタック自体はひとつにまとまっています。詳細は後述し、一旦動作だけ紹介します。
サンプルリポジトリをcloneします。
git clone github.com/shuntaka9576/apprunner-oauth-remote-mcp-server
cd ./apprunner-oauth-remote-mcp-server
npm install
CDKをデプロイします。作成されるリソースは以下の通りです。
- Cognito
- DynamoDB
- App Runner
コンテナをローカルでビルドして、ECRへpushするためにdockerの設定が必要です。初回デプロイはそこそこ時間がかかるかもしれません
cd iac
# aws-valutなどでAWS環境へassume roleする
デプロイします。
npx cdk deploy
デプロイが完了すると、実行結果からApp Runnerのエンドポイントが取得できるのでこちらを控えます。
mcp-server-stack: deploying... [1/1]
mcp-server-stack: creating CloudFormation changeset...
✅ mcp-server-stack
✨ Deployment time: 239.76s
Outputs:
mcp-server-stack.McpServerUrl = <AWSで生成されたprefix>.ap-northeast-1.awsapprunner.com # <-- 控える
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:622455551446:stack/mcp-server-stack/4f612e00-2ac8-11f0-8f86-06cf394e5cf1
✨ Total time: 244.36s
完了したらマネジメントコンソールから手動でデプロイします。
理由は、初回起動時のみパラメータストアが作成される前にコンテナが起動してしまい、アプリの値がdummyになっているためです。こちらの問題はアプリ起動時にパラメータを全部取得しなければ回避可能です。気になる場合実装を変えてみて下さい。
Claude(Web)と連携
MCPサーバーの認可画面をApproveします。
Cognitoの画面に遷移します。Sign upします。
メールアドレスにVerfification codeが届くので入力します。
設定が完了し、登録に成功が確認できます。
ツールの確認をします。
こちらの実装が登録されていることが確認できます。
実際にMCPでツールが利用できることが確認できます。
実装
ライブラリの変更箇所
workers-oauth-provider の変更部分は主に2箇所です。永続化のKV部分と、cloudflareランタイム依存部分です。
Cloudflare Workers KV依存部分は、任意のDBに差し替えられるように拡張しています。
JavaScriptライタイムのcloudflareに依存部分は、Node.jsだと実行できないため型を差し替えています
処理の流れ
公式の仕様書を引用しつつ説明します。
流れの説明ではクライアントの動作がわかりやすい @modelcontextprotocol/inspector
を利用します。
npx @modelcontextprotocol/inspector@latest
仕様書[1][2]よりシーケンスを引用します。まずサーバーのメタデータ検出を行います。
inspectorに先ほどURLにsseをつけてConnectを押下します。
実行時の通信内容は以下の通りです。
シーケンス通り、以下の動作確認できます。
- クライアントがMCPリクエストをして401を返す
- クライアントが
/.well-known/oauth-authorization-server
にMCPサーバーのメタ情報を取得 - クライアントが
/register
エンドポイントでOAuth 2.0 Dynamic Client Registrationが実行
DynamoDBにクライアントの登録が確認できます。値は以下の通りです。
client:UCPSJ1XkTEcs8EoD
{"clientId":"UCPSJ1XkTEcs8EoD","redirectUris":["http://127.0.0.1:6274/oauth/callback"],"clientName":"MCP Inspector","clientUri":"https://github.com/modelcontextprotocol/inspector","grantTypes":["authorization_code","refresh_token"],"responseTypes":["code"],"registrationDate":1746571816,"tokenEndpointAuthMethod":"none"}
その後authorize
エンドポイントをPOSTで実行します。この処理はCloudflareのdemoコードをサンプルに、Cognitoの認可画面にリダイレクト(302)するようにしています。
リダイレクト後のURLと画像は以下の通りで、HostedUIが表示されています。ログイン済みの状態です。
https://{congitoのドメインprefix}.auth.ap-northeast-1.amazoncognito.com/login?
client_id=e...&
redirect_uri=https%3A%2F%2F3rqhgiw2my.ap-northeast-1.awsapprunner.com%2Fcallback&
scope=openid+profile+email&
state=ey...&
response_type=code
前の画像からSigin inするとMCP Server(App Runner)の/callback
URLへ認証コード付きでGETし、MCPサーバーは、サードパーティのセッションにバインドされた独自のアクセストークンを生成します。
該当する処理は下記です。
[3]で該当するシーケンスは以下です。
シーケンスにGenerate bound MCP Tokenとあるように、以下の画像のように、独自のアクセストークンがDynamoDBで作成されていることが確認できます。
その後MCPクライアントのInspectorであるhttp://127.0.0.1:6274/oauth/callback
へリダイレクトされます。
その後再度/.well-known/oauth-authorization-server
エンドポイントを実行し、/token
エンドポイントでaccessTokenを取得しています。
その後取得したtokenを利用して、authorization bearerヘッダーの値にセットして接続が確立されていることがわかります。
さいごに
このような形でCongitoでサードパーティ認証を挟んだリモートMCPサーバーが構築できました。常駐のコンテナホスティングサービスであれば再現できそうですね。まだまだworkers-oauth-providerの理解が浅いので、引き続き処理を読んで気づきがあればアップデートしたいです。
最初はLambda x Lambda Web Adapter構成で試していたのですが、OAuthのフローは通るのですがその後のSSEのセッション維持ができず(ローカルでは動作したものの)断念しました。こちらもセッション永続化するなど回避策があれば試してみたいと思います。
そのほか詰まったネタを最後に書いておきます。
- pnpm linkが上手くいかず、npmに変えました
@modelcontextprotocol/inspector
にcallbackでinspector側に戻ってきた際に、stdioのリクエストを投げる挙動があり、恐らくSPAでうまくリクエストが固定されていないのかな?と思う場面がありました。最初の画面でリロードしてSSEに変え直すと起きなくなりました- Sessionストレージにサーバー情報が保存されているので最初から始めたいときは削除するのが良いと思います
- App RunnerはARMビルド未対応なんですね!Lambdaと同じノリでデプロイしたらformat errorになりました
- npm linkしてtscでビルドするとエラーになるので、tsupでバンドルしました
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/c255349bd98d9dc73f3579c828d195bd09c0bee8/docs/specification/2025-03-26/basic/authorization.mdx?plain=1#L87-L105 ↩︎
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/c255349bd98d9dc73f3579c828d195bd09c0bee8/docs/specification/2025-03-26/basic/authorization.mdx?plain=1#L119C1-L133C4 ↩︎
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/c255349bd98d9dc73f3579c828d195bd09c0bee8/docs/specification/2025-03-26/basic/authorization.mdx?plain=1#L344-L364 ↩︎