🤖

AWSのAPI GatewayとCognitoを使ってOAuth認可付きリモートMCPサーバーをつくる

に公開

はじめに

2025-03-25版のMCPサーバーの仕様において、HTTPトランスポートを利用するMCPサーバーの認可に関する仕様が策定されました。本仕様ではOAuth 2.1に基づく認可コードフローを利用してユーザの一時的なクレデンシャルを利用したアクセスが可能となりました。これにより、長寿命で権限の強いAPIキーをMCPサーバーに埋め込むことなく、AIエージェントからアクセスできるようになっています。
さらに、2025-06-18版の仕様ではMCPサーバーはリソースサーバーとして取り扱うことが明確に記述されたことで、認可サーバーとMCPサーバーは論理的に分離されました。

https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization

一方で、OAuth認可付きのリモートMCPサーバーを実装しようとする開発者にとっては何らかの形でOAuth 2.1に準拠した認可サーバーを用意する必要があることにかわりはありません。特にAWS環境では、Amazon Cognitoが動的クライアント登録や認可サーバーのメタデータ取得といった標準仕様のプロトコルやエンドポイントや対応していないことから、MCP InspectorやVSCodeのような一般のMCPクライアントと連携して動作する例がなかなかないのが現状です。

また、MCPサーバーを実装したい開発者にとって、認可サーバー機能部分をAWS Lambdaなどで実装して追加のコンピューティングリソースを管理運用するのは避けたいところです。そこで、今回はAmazon API Gatewayの機能をフル活用してMCP InspectorやVSCodeとも動くOAuth認可付きのリモートMCPサーバーを実装してみます。

認可付きMCPサーバーの概要

MCPサーバーの認可仕様は大きく以下の4つのRFC(ドラフト含む)に基づいています。

  1. [Draft] OAuth 2.1
  2. RFC 8414 Authorization Server Metadata
  3. RFC 7591 Dynamic Client Registration(DCR)
  4. RFC 9728 Protected Resource Server Metadata

OAuth 2.1については、今回は認可コードフローが動けばよいためCognitoの標準機能で対応可能であり割愛します。

Protected Resource Server Metadataは、リソースサーバーたるMCPサーバーが依拠する認可サーバーのURLを示すために利用されます。そのため、TFC 9728ではOPTIONALとなっているauthorization_serversフィールドも、MCPの仕様では1つ以上の認可サーバーの情報を含むことが必須となっています。また、MCPサーバーがHTTPステータス401を応答する場合は、WWW-AuthenticateヘッダーにBearer resource_metadata="https://resource.example.com/.well-known/oauth-protected-resource"のようにMCPサーバーのリソースメタデータを応答するURLを含めることが必須となっています。これにより、MCPクライアントはリソースメタデータから認可サーバーのURLを辿って認可を得ることができます。

Authorization Server MetadataとDynamic Client RegistrationはMCPクライアントが認可サーバーとやり取りするために必要な仕様です。Authorization Server Metadataは必須となっていますが、動的クライアント登録(DCR: Dynamic Client Registration)はオプショナルであり、DCRに対応しない場合には手動を含めて何らかの形でMCPクライアントを登録するための代替手段の実装が必要となります。しかしながら、MCP InspectorではDCRでクライアント登録することを前提として動くので、今回はMCP Inspectorと連携して標準動作を確認するためにDCRにも対応します。

これらの仕様を利用した全体のフローもMCPの仕様に記述されています。
https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-flow-steps

AWSマネージドサービスによる認可つきMCPサーバー実装

AWSマネージドサービスを利用した認可付きMCPサーバーの実装のために、まず認可サーバーとしてAmazon Cognitoを利用します。CognitoはPKCE付きの認可コードグラントと、クライアントクレデンシャルズグラントに対応しているためMCPサーバーの認可フローに準拠することができます。
一方で、Cognitoは認可サーバーメタデータエンドポイントやDCRには対応していません。そこで、API Gatewayを利用して足りない仕様を補完します。

概念的な構成図を以下に示します。まず、API Gatewayで各種エンドポイントへルーティングします。前段のHTTP APIのAPI Gatewayは下段で後述するホスト直下にwell-knownエンドポイント(例えば、https://example.com/.well-known/oauth-protected-resource)をおくためのワークアラウンドであり、実際にはREST APIのAPI Gatewayが今回の肝になるリソースです。認可サーバーとしてCognitoを利用して、DCRに対応するためにAWS統合でAPI Gatewayと接続します。また、MCPサーバーのツールを実装するためにLambdaを利用してプロキシ統合で接続します。メタデータエンドポイントはAPI GatewayのMock統合を利用して完結します。

MCPサーバーの概念構成図

以下のリポジトリに今回の実装を公開しています。
https://github.com/manaty226/remote-mcp-based-on-aws-managed-services

API GatewayのMock統合によるメタデータエンドポイント対応

認可サーバーやリソースサーバーのメタデータは、自身を示す識別子や関連エンドポイントを含む静的な情報です。特に、今回MCPサーバーを動かすための情報に限ればAWSインフラ構築時点で決定される情報です。
API GatewayにはMock統合とよばれる、固定値や簡単な計算結果を返すための機能があります。これを利用することで、認可サーバーやリソースサーバーのメタデータエンドポイントを実装することができます。
例えば、MCPサーバーとして最低限必要な認可サーバーメタデータは以下のようにマッピングテンプレートを記述することで実装できます。Issuerや各種エンドポイントのURLは、CognitoやAPI Gatewayのエンドポイント情報を渡せば静的に設定可能です。

{
  "issuer": "<IssuerのURL>",
  "authorization_endpoint": "<認可エンドポイントのURL>",
  "token_endpoint": "<トークンエンドポイントのURL>",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "registration_endpoint": "<DCRのエンドポイントURL>"
}

同様に、リソースサーバーメタデータも以下のようにマッピングテンプレートを記述します。

{
  "resource":"<リソースサーバーを実装するAPI GatewayのURL>",
  "authorization_servers":["<認可サーバーのURL>"]
}

API GatewayのAWS統合とマッピングテンプレートによる動的クライアント登録対応

Amazon Cognitoでは、CreateUserPoolClientという名前でクライアントを登録するためのAPIを提供しています。しかしながら、リクエストやレスポンスの形式が独自のものとなっているため、そのままではDCRの仕様に準拠したかたちでクライアント登録することはできません。

https://docs.aws.amazon.com/ja_jp/cognito-user-identity-pools/latest/APIReference/API_CreateUserPoolClient.html

そこで、API Gatewayのマッピングテンプレート機能を利用してリクエストやレスポンス情報を変換することで、DCRの仕様に即した形式でCognitoにクライアント登録できるようにします。
例えば、DCRのリクエストからCognitoのクライアント作成APIリクエストへの変換は以下のようなテンプレートを記述することで実装できます。

{
  "ClientName": $input.json('$.client_name'),
  "CallbackURLs": $input.json('$.redirect_uris'),
  "AllowedOAuthFlows": $input.json('$.response_types'),
  "AllowedOAuthFlowsUserPoolClient": true,
  "AllowedOAuthScopes": ["openid", "profile"],
  "SupportedIdentityProviders": ["COGNITO"],
  "UserPoolId": "<CognitoユーザープールID>"
}

API GatewayのマッピングテンプレートはVTL(Velocity Template Language)という言語を利用しており、例えば$input.json('$.client_name')のようにリクエストボディの値を参照することができます。これを前述したCogtnioのAPIリクエストボディ形式の値として設定することで、DCRのリクエストをCognitoのAPIリクエストに変換することができます。また、Cognito独自の必須設定値もあるため、それらはマッピングテンプレートに固定値として書き込んでおきます。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/models-mappings.html

レスポンスについても、Cognito独自のレスポンス形式からDCRのレスポンスに変換するマッピングテンプレートを記述することで、MCPクライアントへの応答として利用可能です。

{
  "client_id": $input.json('$.UserPoolClient.ClientId'),
  "client_name": $input.json('$.UserPoolClient.ClientName'),
  "redirect_uris": $input.json('$.UserPoolClient.CallbackURLs'),
  "response_types": $input.json('$.UserPoolClient.AllowedOAuthFlows')
}

MCPツール実装

認可に関する機能をAWSマネージドサービスで完結できたので、リソースサーバー機能部分であるMCPのツール実装については、公式SDK等で提供されているような認可機能も含めてMCPの仕様すべてに対応したフル実装でなく、薄いライブラリを利用したくなります。awslabsリポジトリにLambdaハンドラーに渡されるeventからリクエストを読み取ってでMCPのツール機能を動かすためのmcp-lambda-handlerというライブラリがあったので利用してみます。

https://github.com/awslabs/mcp/tree/8d90be5c403af4829a45d8f03093f830ffed6285/src/mcp-lambda-handler

UUID v4を返すだけのMCPサーバーを実装するなら、以下のように数行で完結します。

import uuid
from awslabs.mcp_lambda_handler import MCPLambdaHandler

mcp_server = MCPLambdaHandler(name="sample-uuid-mcp", version="1.0.0")

@mcp_server.tool()
def get_uuid() -> str:
    """Generate a new UUID v4"""
    return str(uuid.uuid4())

def handler(event, context):
    return mcp_server.handle_request(event, context)

また、API認可についてはAPI GatewayのCognitoオーソライザーを設定した上で、ゲートウェイレスポンスのUNAUTHORIZEDエラーに対してWWW-Authenticateヘッダーを付加すれば、リソースサーバーメタデータへのルーティングが完了します。

動作検証

MCP Inspectorを利用して、実装した認可付きリモートMCPサーバーが動作することを確認します。まず、MCP InspectorのTransport TypeをStreamable HTTPに設定して構築したAPI GatewayのMCPエンドポイント(https://<あなたの構築したAPI GWのホスト>/mcp)をURLフォームに設定します。その後、「Open Auth Setting」をクリックして「Quick OAuth Flow」または「Guided OAuth Flow」を選択すると、構築したリモートMCPサーバーとの認可フローが開始されます。

MCP Inspectorの設定

実行した結果、メタデータの取得、動的なクライアント登録、認可コードフローの実行が成功して、無事にアクセストークンが取得できました。

MCP Inspectorの認可検証結果

これだけでなく、MCP Inspectorの画面左メニューの「Connect」ボタンで接続してツールを呼び出すことも可能です。また、VSCodeなどに設定すればAI Agentからも呼び出すことができます。このように、API GatewayとCognitoの組み合わせだけで標準のMCP仕様に準拠した認可付きリモートMCPサーバーを実装することができました。

おわりに

今回はAWSのAPI GatewayとCognitoを使ってOAuth認可付きのリモートMCPサーバーを実装してみました。API GatewayのマッピングテンプレートをつかってプロトコルとAWSの仕様ギャップを埋めることで、認可機能に追加のコンピューティングリソースを必要とせず認可を実現することができます。
今回は実装を簡単化するためにリソースサーバーと認可サーバーを同一のAPI Gatewayで実装しましたが、認可サーバー機能部分だけをAPI GatewayとCognitoで公開することで、MCPサーバーの実装構成を分離してシンプルに実装することも可能です。

Discussion