[Amazon Bedrock AgentCore] Gateway経由でLambda関数をAIエージェントのツールにしてみた

[Amazon Bedrock AgentCore] Gateway経由でLambda関数をAIエージェントのツールにしてみた

2025.08.24

はじめに

こんにちは、コンサルティング部の神野です。

皆さんはAmazon Bedrock AgentCore(以下AgentCore)のGateway機能を使っていますか?
Gatewayを試そうと公式ハンズオンを確認していると、「LambdaサーバーをMCPサーバ化できる!」と記載があってどういうこと・・・?とわからなかったので、実際に手を動かして理解を深めてみることにしました!

公式が提供しているハンズオン

https://catalog.us-east-1.prod.workshops.aws/workshops/015a2de4-9522-4532-b2eb-639280dc31d8/en-US

Gatewayとは

Gatewayは既存のAPI、Lambda関数、各種サービスをMCP(Model Context Protocol)互換のツールに変換して、AIエージェントから簡単に呼び出せるようにしてくれるサービスです。
公式ハンズオンに記載があった図としては下記になります。

Gatewayの絵
なるほど・・・?図を見るとGatewayが仲介になって、AIエージェントとLambda関数の架け橋になって、エージェントがLambda関数をtoolとして呼び出したり、その実行結果を踏まえて判断できるみたいな機能なんですかね。より深掘りします。

Gatewayが行っていることを順序立てて説明すると、以下のようになります。

  1. ツールの統合: 既存のLambda関数やAPIをMCPツールとして登録
  2. プロトコル変換: AIエージェントからのMCPリクエストを、実際のLambda関数呼び出しに変換
  3. 認証・認可: Inbound認証(エージェント側)とOutbound認証(リソース側)を管理
    1. AWSリソースの場合はGatewayのIAMロールにLambda関数を実行できる権限を付与すれば実行可能となります。
  4. 結果の変換: Lambda関数の実行結果をMCPレスポンス形式に変換してエージェントに返却

上の流れをざっくりとした絵で描いてみました。

CleanShot 2025-08-24 at 22.47.34@2x

まさにGatewayはLambda関数とAIエージェントの架け橋としてMCPツール呼び出しを可能にしているんですね。
今回はLambda関数を対象にしていますが、OpenAPI仕様のAPIを使った場合なども試してみたいですね。

ツール統合

既存のエンタープライズリソースを、エージェントが使えるツールに簡単に変換できます。例えば、今回のように既存のLambda関数があれば、それをそのままMCPツール化可能です!具体的には下記対応しております。

  • OpenAPI形式のAPI
  • Lambda関数
  • Smithy モデル

認証機能

ここが今回の実装で最も重要なポイントになるのですが、GatewayはInbound認証(エージェントの身元確認)とOutbound認証(外部ツールへの接続)の両方をマネージドサービスIdentityで管理できます。

Runtimeの認証を実装する際もIdentityでもInbound/Outbound Auth出てきましたが、Gatewayでも必要になってくるんですね。
今回実装するLambda関数との統合についても公式ハンズオンで図があるので確認してみましょう。

MCPify your AWS Lambda

この図から、以下の認証フローが読み取れます。
今回はLambda関数をターゲットとする場合は、Outbound Authは使用せずGatewayのIAMロールがLambdaの実行権限さえあればOKといった形になります。

  1. エージェント→Gateway: OAuth tokenでの認証(Inbound Auth)
  2. Gateway→Identity: トークン検証(今回はCognitoを使用)
  3. Gateway→Lambda: IAMロールでの実行権限

M2M(Machine-to-Machine)認証とは?

今回設定するパラメータにM2Mとあったので先に言及します。

M2M認証は、人間の介入なしにサービス同士が安全に通信するための認証方法です。
今回使用するOAuth2のclient_credentialsフローがその代表例です。

簡単に言うと、以下のような流れになります。

  1. サービスA(今回はRuntime)が、自分のクライアントIDとシークレットを使って認証サーバー(Cognito)にアクセス

    1. Outbound Authに安全にシークレット情報は保管(Secrets Manager)されている
  2. 認証サーバーが一時的なアクセストークンを発行

  3. サービスAは、このトークンを使ってサービスB(Gateway)にアクセス

今回の実装全体をシーケンス図で記載するとこんなような感じです。
Identityの機能で、トークン取得が簡略化されるのは魅力の1つかと思います。

mermaid-diagram-2025-08-24-233203

Semantic Search機能

実は今回のGateway作成時にenable_semantic_search=Trueという設定を有効にしています。これは何でしょうか?

Semantic Search(セマンティック検索)は、Gatewayが提供するインテリジェントなツール検索機能です。
通常、エージェントが利用できるツールの数には制限があり(一般的に100個程度)、ツールが増えるとプロンプトサイズが大きくなり、適切なツールを見つけるのが困難になるという課題があります。

Semantic Searchを有効にすると、以下のようなメリットがあります。

  • コンテキストに基づくツール発見: エージェントがタスクの文脈に基づいて、最も関連性の高いツールを自動的に発見
  • トークン処理の削減: 必要なツールのみをフォーカスすることで、処理するトークン数を大幅に削減
  • 推論パフォーマンスの向上: 関連性の高いツールに絞り込むことで、全体的な応答時間を短縮
  • スケーラビリティ: 数千のツールがあっても効率的に管理・利用可能

公式が提供している絵としては下記のような感じです。

Semantic Search

Gateway作成時に以下のように設定します。

gateway_response = gateway_client.create_mcp_gateway(
    name=gateway_name,
    role_arn=gateway_role_arn,
    authorizer_config=authorizer_config,
    enable_semantic_search=True  # セマンティック検索を有効化!
)

ただし、Semantic Searchは作成時のみ有効化可能で既存のGatewayに後から追加することはできません。

下記のようにツールを検索して使用可能になると公式ドキュメントに記載がありました!

async def search_tools(gateway_url, access_token, query):
    headers = {"Authorization": f"Bearer {access_token}"}
    async with streamablehttp_client(gateway_url, headers=headers) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Use the built-in search tool
            response = await session.call_tool(
                "x_amz_bedrock_agentcore_search",
                arguments={"query": query}
            )

            # Parse the search results
            for content in response.content:
                results = json.loads(content.text)
                print(f"Found {len(results)} relevant tools for: {query}")
                for tool in results:
                    print(f"- {tool['name']}: {tool['description']}")

# Example usage
await search_tools(
    "https://gateway-id.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
    "your-access-token",
    "How do I process images?"
)

シンプルに使えそうでいいですね!また精度など検証してみたいです。

今回実装する構成

今回実装するアーキテクチャは以下のようになります。(ちょっと見づらい絵ですみません・・・)

CleanShot 2025-08-24 at 16.49.56@2x

図の番号に沿って、それぞれのコンポーネントの役割を見ていきましょう。

1. AgentCore Runtime(認証なしで起動)

開発環境想定で簡略化のためにInbound認証なしで起動します。クライアントからのリクエストを直接受け付けて処理を開始します。

2-4. Outbound Auth でのトークン取得フロー

RuntimeのOutbound Auth機能が自動的に以下の処理を実行します:

  • 2. トークン取得: RuntimeがGatewayアクセス用のトークンを取得開始
  • 3. ClientSecret を取得: AWS Secrets ManagerからCognitoのクライアントシークレットを安全に取得
  • 4. トークンを取得 with ClientSecret: Amazon CognitoのOAuth2エンドポイントにM2M認証(client_credentials)でアクセストークンを取得

この部分がAgentCore Identityの魅力で、@requires_access_tokenデコレーターを使うだけで自動化可能です!

5. MCP Serverとしてtool実行 with トークン

取得したアクセストークンをAuthorizationヘッダーに付与して、MCPでGatewayにツール実行リクエストを送信します。

6-7. Gateway側での認証とLambda関数実行

  • 6. 受信したトークンを検証: GatewayのInbound Auth(Cognito認証)が、受信したアクセストークンの正当性を検証
  • 7. Gateway経由でLambda関数を実行: 認証が成功したら、GatewayがLambda関数を呼び出し

8-9. Lambda関数の処理と結果返却

  • 8. Lambda関数を実行: Lambda関数内でcontext.client_contextからツール名(get_order_toolなど)を判別して処理
  • 9. 結果を返却: 注文情報のJSONデータを返却

10. LLMが解釈した結果を返却

最後に、LLMが自然言語に変換してユーザーに分かりやすく返答します。例えば下記のように返事をする想定です。

注文ID 123の情報は以下の通りです:
- 注文ID: 123
- 状態: 処理中 (processing)
- 商品:
  1. 商品A: 2点
  2. 商品B: 1点
- 合計金額: 5,000円

RuntimeのOutbound Auth → GatewayのInbound Auth → Lambda実行という流れで、
既存のLambda関数をMCPツール化可能にします!

前提条件

必要な環境

  • AWS CLI 2.28.8以上
  • Python 3.12以上
  • boto3とbotocore(最新版)
  • AWSアカウント(us-west-2リージョンを使用)
  • 適切なIAM権限(AgentCore、Lambda、Cognito、IAMの操作権限)
  • モデルの有効化
    • 今回はanthropic.claude-3-5-haiku-20241022-v1:0を使用します

必要なライブラリ

実装に必要なライブラリをrequirements.txtにまとめました。python-dotenvで.env管理、strands-agentsでエージェント実装、bedrock-agentcoreでRuntime/Gateway操作を行います。

requirements.txt
boto3
botocore
strands-agents
bedrock-agentcore
bedrock-agentcore-starter-toolkit
python-dotenv
requests

仮想環境を作成して必要なパッケージをインストールします。AgentCoreは最新版が必要なので、既存環境がある場合はアップグレードをおすすめします。

# 仮想環境の作成と有効化
python3 -m venv agentcore-env
source agentcore-env/bin/activate

# 依存関係のインストール
pip install -r requirements.txt

プロジェクト構成

今回作成するファイル構成は以下のとおりです。

プロジェクト構成
.
├── requirements.txt           # 依存関係
├── .env                       # 環境変数設定(自動生成)
├── .env.example              # 環境変数のテンプレート
├── setup_cognito.py          # Cognito設定スクリプト(.env自動更新)
├── create_iam_roles.py       # IAMロール作成スクリプト(.env自動更新)
├── create_lambda.py          # Lambda関数作成スクリプト(.env自動更新)
├── create_gateway.py         # Gateway作成スクリプト(.env自動更新)
├── setup_outbound_auth.py    # OutboundAuth設定スクリプト(必須)
├── runtime_agent.py          # Runtime実装(AgentCore Identity使用)
├── deploy_runtime.py         # Runtimeデプロイスクリプト
└── invoke_agentcore.py       # 動作確認用スクリプト

環境変数の管理

今回は.envファイルで環境変数を一元管理します。各スクリプトが実行されるたびに.envファイルが自動更新されるので、手動での環境変数設定が不要になります。

.env.example
# Cognito設定(setup_cognito.pyが自動更新)
COGNITO_DISCOVERY_URL=
M2M_CLIENT_ID=
M2M_CLIENT_SECRET=
COGNITO_POOL_ID=
RESOURCE_SERVER_ID=agentcore-gateway

# Gateway設定(create_gateway.pyが自動更新)
GATEWAY_URL=
GATEWAY_ID=

# IAMロール(create_iam_roles.pyが自動更新)
GATEWAY_ROLE_ARN=
RUNTIME_ROLE_ARN=
LAMBDA_ROLE_ARN=

# Lambda設定(create_lambda.pyが自動更新)
LAMBDA_ARN=

# AgentCore Identity設定(setup_outbound_auth.py実行後に手動追加)
IDENTITY_PROVIDER_NAME=agentcore-identity-for-gateway
GATEWAY_SCOPE=agentcore-gateway/read agentcore-gateway/write

サンプルのレポジトリ

実装を1通り行ったレポジトリは下記にあるので、必要に応じてご参照ください。

https://github.com/yuu551/bedrock-agentcore-gateway-lambda-sample

実装手順

1. Cognito User Poolの作成

まずはGatewayのInbound認証用にCognitoを設定します。M2M認証なので、Resource ServerとApp Clientの設定が必要です。

setup_cognito.pyでは、Cognito User Pool、Resource Server、User Pool Domain、App Clientを作成し、結果を.envファイルに自動保存します。

setup_cognito.py
import boto3
import json
import os
from botocore.exceptions import ClientError
from dotenv import load_dotenv, set_key

REGION = "us-west-2"
USER_POOL_NAME = "agentcore-gateway-pool"
RESOURCE_SERVER_ID = "agentcore-gateway"
RESOURCE_SERVER_NAME = "AgentCore Gateway Resource Server"
CLIENT_NAME = "agentcore-m2m-client"

# Scopeの定義
SCOPES = [
    {"ScopeName": "read", "ScopeDescription": "Read access to Gateway"},
    {"ScopeName": "write", "ScopeDescription": "Write access to Gateway"}
]

def create_cognito_m2m_setup():
    cognito = boto3.client("cognito-idp", region_name=REGION)

    try:
        # User Poolの作成
        pool_response = cognito.create_user_pool(
            PoolName=USER_POOL_NAME,
            Policies={
                'PasswordPolicy': {
                    'MinimumLength': 8,
                    'RequireUppercase': True,
                    'RequireLowercase': True,
                    'RequireNumbers': True,
                    'RequireSymbols': True
                }
            }
        )
        pool_id = pool_response['UserPool']['Id']
        print(f"✅ User Pool作成完了: {pool_id}")

        # Resource Serverの作成
        resource_response = cognito.create_resource_server(
            UserPoolId=pool_id,
            Identifier=RESOURCE_SERVER_ID,
            Name=RESOURCE_SERVER_NAME,
            Scopes=SCOPES
        )
        print(f"✅ Resource Server作成完了: {RESOURCE_SERVER_ID}")

        # User Pool Domain作成(OAuth2 token endpointに必須)
        domain_name = f"agentcore-gateway-auth-{int(time.time())}"
        try:
            domain_response = cognito.create_user_pool_domain(
                Domain=domain_name,
                UserPoolId=pool_id
            )
            print(f"✅ User Pool Domain作成完了: {domain_name}")
        except ClientError as e:
            if e.response['Error']['Code'] == 'InvalidParameterException' and 'already exists' in str(e):
                # ドメイン名重複時は別の名前で再試行
                domain_name = f"agentcore-gateway-auth-{int(time.time())}-retry"
                domain_response = cognito.create_user_pool_domain(
                    Domain=domain_name,
                    UserPoolId=pool_id
                )
                print(f"✅ User Pool Domain作成完了(リトライ): {domain_name}")
            else:
                raise

        # App Client作成(M2M用)
        client_response = cognito.create_user_pool_client(
            UserPoolId=pool_id,
            ClientName=CLIENT_NAME,
            GenerateSecret=True,  # M2M認証には必須
            AllowedOAuthFlows=['client_credentials'],  # M2M認証フロー
            AllowedOAuthScopes=[
                f"{RESOURCE_SERVER_ID}/read",
                f"{RESOURCE_SERVER_ID}/write"
            ],
            AllowedOAuthFlowsUserPoolClient=True
        )
        client_id = client_response['UserPoolClient']['ClientId']
        client_secret = client_response['UserPoolClient']['ClientSecret']

        # Discovery URLの生成
        discovery_url = f"https://cognito-idp.{REGION}.amazonaws.com/{pool_id}/.well-known/openid-configuration"

        print("\n🎉 Cognito M2M設定完了!")
        print(f"Pool ID: {pool_id}")
        print(f"Client ID: {client_id}")
        print(f"Client Secret: {client_secret}")
        print(f"Discovery URL: {discovery_url}")

        # .envファイルに設定を自動保存
        env_file = ".env"

        # .env.exampleからコピー(初回のみ)
        if not os.path.exists(env_file) and os.path.exists(".env.example"):
            with open(".env.example", "r") as src, open(env_file, "w") as dst:
                dst.write(src.read())

        # 環境変数を更新
        set_key(env_file, "COGNITO_POOL_ID", pool_id)
        set_key(env_file, "M2M_CLIENT_ID", client_id)
        set_key(env_file, "M2M_CLIENT_SECRET", client_secret)
        set_key(env_file, "COGNITO_DISCOVERY_URL", discovery_url)
        set_key(env_file, "RESOURCE_SERVER_ID", RESOURCE_SERVER_ID)

        print(f"\n✅ 設定を.envファイルに保存しました!")

        return {
            "pool_id": pool_id,
            "client_id": client_id,
            "client_secret": client_secret,
            "discovery_url": discovery_url
        }

    except ClientError as e:
        print(f"❌ エラーが発生しました: {e}")
        raise

if __name__ == "__main__":
    cognito_config = create_cognito_m2m_setup()

User Pool Domainも忘れずに作成しましょうね!最初作成を忘れていて下記エラーになりました。
エラー内容:An error occurred (ValidationException) when calling the GetResourceOauth2Token operation: Error parsing ClientCredentials response

CognitoでOAuth 2.0のclient_credentialsフロー(M2M認証)を使用するにはUser Pool Domainが必須です。

  • User Pool Domainを作成することで初めて/oauth2/tokenエンドポイントが有効になる
  • このエンドポイントがないと、M2M認証でアクセストークンを取得できない
  • AgentCore Identityは内部でこのエンドポイントを呼び出すため、Domainがないとエラーになる

2. IAMロールの作成

GatewayとRuntimeそれぞれに必要なIAMロールを作成します。今回はPythonスクリプトで自動化し、作成したロールARNを.envファイルに自動保存します。

create_iam_roles.pyでは、アカウントIDを自動取得し、Gateway/Runtime用の詳細な権限を設定して、結果を.envファイルに保存します。

create_iam_roles.py
"""
IAMロール作成スクリプト
Gateway用とRuntime用のIAMロールを作成し、.envファイルに自動保存します。
"""

import boto3
import json
import os
import time
from botocore.exceptions import ClientError
from dotenv import load_dotenv, set_key

# AWSクライアント
iam = boto3.client('iam')
sts = boto3.client('sts')

def get_account_id():
    """AWSアカウントIDを取得"""
    return sts.get_caller_identity()["Account"]

def create_trust_policy(account_id):
    """信頼ポリシーを生成(Gateway/Runtime共通)"""
    return {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AssumeRolePolicy",
                "Effect": "Allow",
                "Principal": {
                    "Service": "bedrock-agentcore.amazonaws.com"
                },
                "Action": "sts:AssumeRole",
                "Condition": {
                    "StringEquals": {
                        "aws:SourceAccount": account_id
                    },
                    "ArnLike": {
                        "aws:SourceArn": f"arn:aws:bedrock-agentcore:us-west-2:{account_id}:*"
                    }
                }
            }
        ]
    }

Runtime用の詳細なポリシー

Runtime用には、ECRアクセス、CloudWatch Logs、X-Ray、メトリクス、Bedrockモデル呼び出し、AgentCore Identity認証関連**、**Secrets Managerの権限を付与します。AgentCore Identity関連の権限は、OutboundAuth機能でM2M認証トークンを取得する際に必要になります。

コード全文
def create_runtime_policy(account_id):
    """Runtime用の詳細な実行ポリシーを生成"""
    return {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "ECRImageAccess",
                "Effect": "Allow",
                "Action": [
                    "ecr:BatchGetImage",
                    "ecr:GetDownloadUrlForLayer"
                ],
                "Resource": [
                    f"arn:aws:ecr:us-west-2:{account_id}:repository/*"
                ]
            },
            {
                "Sid": "ECRTokenAccess",
                "Effect": "Allow",
                "Action": [
                    "ecr:GetAuthorizationToken"
                ],
                "Resource": "*"
            },
            {
                "Sid": "LogsAccess",
                "Effect": "Allow",
                "Action": [
                    "logs:DescribeLogStreams",
                    "logs:CreateLogGroup",
                    "logs:DescribeLogGroups",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": [
                    f"arn:aws:logs:us-west-2:{account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
                ]
            },
            {
                "Sid": "XRayAccess",
                "Effect": "Allow",
                "Action": [
                    "xray:PutTraceSegments",
                    "xray:PutTelemetryRecords",
                    "xray:GetSamplingRules",
                    "xray:GetSamplingTargets"
                ],
                "Resource": ["*"]
            },
            {
                "Sid": "CloudWatchMetrics",
                "Effect": "Allow",
                "Resource": "*",
                "Action": "cloudwatch:PutMetricData",
                "Condition": {
                    "StringEquals": {
                        "cloudwatch:namespace": "bedrock-agentcore"
                    }
                }
            },
            {
                "Sid": "BedrockModelInvocation",
                "Effect": "Allow",
                "Action": [
                    "bedrock:InvokeModel",
                    "bedrock:InvokeModelWithResponseStream"
                ],
                "Resource": [
                    "arn:aws:bedrock:*::foundation-model/*",
                    f"arn:aws:bedrock:us-west-2:{account_id}:*"
                ]
            },
            {
                "Sid": "AgentCoreIdentityAccess",
                "Effect": "Allow",
                "Action": [
                    "bedrock-agentcore:GetResourceOauth2Token",
                    "bedrock-agentcore:GetWorkloadAccessToken",
                    "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
                ],
                "Resource": ["*"]
            },
            {
                "Sid": "SecretsManagerAccess",
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue"
                ],
                "Resource": ["*"]
            }
        ]
    }

Gateway用のポリシー

Gateway用には、Lambda実行権限、CloudWatch Logsの権限を付与します。

コード全文
def create_gateway_policy(account_id):
    """Gateway用の実行ポリシーを生成"""
    return {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "LambdaInvocation",
                "Effect": "Allow",
                "Action": [
                    "lambda:InvokeFunction"
                ],
                "Resource": f"arn:aws:lambda:us-west-2:{account_id}:function:*"
            },
            {
                "Sid": "LogsAccess",
                "Effect": "Allow",
                "Action": [
                    "logs:DescribeLogStreams",
                    "logs:CreateLogGroup",
                    "logs:DescribeLogGroups",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": [
                    f"arn:aws:logs:us-west-2:{account_id}:log-group:/aws/bedrock-agentcore/gateway/*"
                ]
            },
            {
                "Sid": "XRayAccess",
                "Effect": "Allow",
                "Action": [
                    "xray:PutTraceSegments",
                    "xray:PutTelemetryRecords"
                ],
                "Resource": ["*"]
            }
        ]
    }

Lambda用のポリシー

Lambda関数用のIAMロールも同時に作成されます。Lambda用には基本的なCloudWatch Logs権限を付与します。

コード全文
def create_lambda_policy(account_id):
    """Lambda関数用の実行ポリシーを生成"""
    return {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "LogsAccess",
                "Effect": "Allow",
                "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": [
                    f"arn:aws:logs:us-west-2:{account_id}:log-group:/aws/lambda/agentcore-order-tools:*"
                ]
            },
            {
                "Sid": "XRayAccess",
                "Effect": "Allow",
                "Action": [
                    "xray:PutTraceSegments",
                    "xray:PutTelemetryRecords"
                ],
                "Resource": ["*"]
            }
        ]
    }

実行すると、3つのIAMロール(Gateway、Runtime、Lambda)が作成され、ARNが.envファイルに自動保存されます。

# IAMロールを作成(.envファイルを自動更新)
python create_iam_roles.py

実行結果の例:

🚀 IAMロール作成スクリプトを開始します...
   アカウントID: 123456789012

📦 Gateway用IAMロールを作成中...
✅ IAMロール作成成功: AgentCoreGatewayRole-1234567890
   ポリシーをアタッチしました

📦 Runtime用IAMロールを作成中...
✅ IAMロール作成成功: AgentCoreRuntimeRole-1234567890
   ポリシーをアタッチしました

📦 Lambda関数用IAMロールを作成中...
✅ IAMロール作成成功: AgentCoreLambdaRole-1234567890
   ポリシーをアタッチしました

🎉 IAMロール作成完了!
   Gateway Role ARN: arn:aws:iam::123456789012:role/AgentCoreGatewayRole-1234567890
   Runtime Role ARN: arn:aws:iam::123456789012:role/AgentCoreRuntimeRole-1234567890
   Lambda Role ARN: arn:aws:iam::123456789012:role/AgentCoreLambdaRole-1234567890

✅ .envファイルに保存しました

3. Lambda関数の作成

次に、MCPツール化するLambda関数を作成します。
ツール名を判別する際にGateway経由で呼び出される際のcontext.client_contextを活用するのがポイントです!

最初、Lambda関数でどうやってツール名を判別すればいいのか分からず困りました。
調べてみると、Gateway経由で呼び出されるLambda関数では、context.client_context.custom['bedrockAgentCoreToolName']からツール名が取得できることがわかりました。
さらに、実際のツール名には Gateway Target のプレフィックス(例:order-lambda-target___get_order_tool)が付くため、___で分割して実際のツール名を抽出する処理が必要でした。

この実装はAWS公式のサンプルコードが大変参考になりました!

lambda関数の処理
import json

def lambda_handler(event, context):
    """
    AgentCore Gateway経由で呼び出されるLambda関数
    context.client_contextからツール名を判別して処理を分岐
    """

    # Gateway経由の場合、context.client_contextが設定される
    tool_name = None
    try:
        if hasattr(context, 'client_context') and context.client_context:
            # client_contextから直接ツール名を取得
            tool_name = context.client_context.custom['bedrockAgentCoreToolName']
            print(f"Original tool name from Gateway: {tool_name}")

            # Gateway Target プレフィックスを除去
            delimiter = "___"
            if delimiter in tool_name:
                tool_name = tool_name[tool_name.index(delimiter) + len(delimiter):]
            print(f"Processed tool name: {tool_name}")
            print(f"Client context structure: {str(context.client_context)}")
        else:
            print("No client_context available - direct Lambda invocation")
    except (AttributeError, KeyError, TypeError) as e:
        print(f"Error accessing client_context: {e}")
        tool_name = None

    # ツール名に基づいて処理を分岐
    if tool_name == 'get_order_tool':
        order_id = event.get('orderId', 'unknown')
        # 実際のビジネスロジックをここに実装
        result = {
            "orderId": order_id,
            "status": "processing",
            "items": [
                {"name": "商品A", "quantity": 2},
                {"name": "商品B", "quantity": 1}
            ],
            "total": 5000
        }
        return {
            "statusCode": 200,
            "body": json.dumps(result)
        }

    elif tool_name == 'update_order_tool':
        order_id = event.get('orderId', 'unknown')
        # 実際の更新処理をここに実装
        result = {
            "orderId": order_id,
            "status": "updated",
            "message": f"Order {order_id} has been updated successfully"
        }
        return {
            "statusCode": 200,
            "body": json.dumps(result)
        }

    else:
        # ツール名が不明な場合
        return {
            "statusCode": 400,
            "body": json.dumps({
                "error": f"Unknown tool: {tool_name}"
            })
        }

Lambda関数もPythonスクリプトで自動化します。.envファイルからLambdaロールARNを読み込み、作成したLambda関数のARNを.envファイルに保存します。

create_lambda.pyでは、Lambda関数のコード生成、ZIPパッケージ化、デプロイ、.env更新を自動で行います。

コード全文
create_lambda.py
"""
Lambda関数作成スクリプト
注文管理Lambda関数を作成し、.envファイルに自動保存します。
"""

import boto3
import zipfile
import os
from botocore.exceptions import ClientError
from dotenv import load_dotenv, set_key

# AWSクライアント
lambda_client = boto3.client('lambda', region_name='us-west-2')

LAMBDA_FUNCTION_NAME = "agentcore-order-tools"
LAMBDA_RUNTIME = "python3.12"
LAMBDA_HANDLER = "lambda_function.lambda_handler"

def create_lambda_function(role_arn):
    """Lambda関数を作成"""
    try:
        # デプロイメントパッケージを読み込み
        with open("lambda_function_code.zip", "rb") as f:
            zip_content = f.read()

        # Lambda関数を作成
        response = lambda_client.create_function(
            FunctionName=LAMBDA_FUNCTION_NAME,
            Runtime=LAMBDA_RUNTIME,
            Role=role_arn,
            Handler=LAMBDA_HANDLER,
            Code={'ZipFile': zip_content},
            Description='Order management tools for AgentCore Gateway'
        )

        function_arn = response['FunctionArn']
        print(f"✅ Lambda関数作成成功: {LAMBDA_FUNCTION_NAME}")

        return function_arn

    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceConflictException':
            print(f"ℹ️  Lambda関数は既に存在します")
            response = lambda_client.get_function(FunctionName=LAMBDA_FUNCTION_NAME)
            return response['Configuration']['FunctionArn']

実行すると、Lambda関数が作成され、ARNが.envファイルに自動保存されます。

# Lambda関数を作成(.envファイルを自動更新)
python create_lambda.py

実行結果の例:

🚀 Lambda関数作成スクリプトを開始します...
✅ Lambda関数のコードを作成しました
✅ デプロイメントパッケージを作成しました: lambda_function_code.zip
✅ Lambda関数作成成功: agentcore-order-tools

🎉 Lambda関数作成完了!
   Function ARN: arn:aws:lambda:us-west-2:123456789012:function:agentcore-order-tools

✅ .envファイルに保存しました
🧹 作業ファイルを削除しました

4. AgentCore Identity OutboundAuth設定

AgentCore IdentityのOutboundAuth機能を使ってM2M認証を行うため、Credential Providerの設定が必要です。この設定により、RuntimeからGatewayへのM2M認証が自動化されます。

setup_outbound_auth.pyでは、.envファイルから既存のGateway設定を自動読み取りし、Credential Providerを作成して、結果を.envファイルに自動保存します。

setup_outbound_auth.py
"""
AgentCore Identity OutboundAuth自動セットアップスクリプト
Gateway用のCredential Providerを自動作成します。
"""

import boto3
import requests
import json
import os
from typing import Dict, Any, Optional
from botocore.exceptions import ClientError

class OutboundAuthSetup:
    def __init__(self, region: str = "us-west-2"):
        self.region = region
        self.cognito_client = boto3.client("cognito-idp", region_name=region)
        self.gateway_client = boto3.client("bedrock-agentcore-control", region_name=region)
        self.identity_client = boto3.client("bedrock-agentcore-control", region_name=region)

    def get_gateway_info(self, gateway_name: str) -> Dict[str, Any]:
        """
        既存のGatewayから必要な情報を取得
        """
        try:
            # Gatewayを検索
            response = self.gateway_client.list_gateways()
            gateway_info = None

            for gateway in response.get("gateways", []):
                if gateway.get("name") == gateway_name:
                    gateway_info = gateway
                    break

            if not gateway_info:
                raise Exception(f"Gateway '{gateway_name}' not found")

            # 詳細情報を取得
            gateway_detail = self.gateway_client.get_gateway(
                gatewayIdentifier=gateway_info["gatewayId"]
            )

            print(f"✅ Gateway情報取得成功: {gateway_name}")
            print(f"   Gateway ID: {gateway_detail['gatewayId']}")
            print(f"   Gateway URL: {gateway_detail['gatewayUrl']}")

            return gateway_detail

        except ClientError as e:
            print(f"❌ Gateway情報取得エラー: {e}")
            raise

    def get_cognito_discovery_url(self, gateway_detail: Dict[str, Any]) -> str:
        """
        GatewayのCognito設定からDiscovery URLを取得
        """
        try:
            auth_config = gateway_detail.get("authorizerConfiguration", {})
            custom_jwt = auth_config.get("customJWTAuthorizer", {})
            discovery_url = custom_jwt.get("discoveryUrl")

            if not discovery_url:
                raise Exception("Gateway authorizerConfiguration にdiscoveryUrlが見つかりません")

            print(f"✅ Discovery URL取得成功: {discovery_url}")
            return discovery_url

        except Exception as e:
            print(f"❌ Discovery URL取得エラー: {e}")
            raise

    def get_cognito_client_info(self, discovery_url: str) -> Dict[str, str]:
        """
        Discovery URLからUser Pool IDを抽出し、クライアント情報を取得
        """
        try:
            # Discovery URLからUser Pool IDを抽出
            # 例: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXXXXX/.well-known/openid-configuration
            import re
            match = re.search(r'/([^/]+)/.well-known', discovery_url)
            if not match:
                raise Exception("Discovery URLからUser Pool IDを抽出できません")

            user_pool_id = match.group(1)
            print(f"✅ User Pool ID抽出成功: {user_pool_id}")

            # User Pool Clientsを取得
            clients_response = self.cognito_client.list_user_pool_clients(
                UserPoolId=user_pool_id
            )

            if not clients_response.get("UserPoolClients"):
                raise Exception("User Pool Clientが見つかりません")

            # 最初のクライアント(通常M2M用)を使用
            client_info = clients_response["UserPoolClients"][0]
            client_id = client_info["ClientId"]

            # クライアント詳細を取得
            client_detail = self.cognito_client.describe_user_pool_client(
                UserPoolId=user_pool_id,
                ClientId=client_id
            )

            client_secret = client_detail["UserPoolClient"].get("ClientSecret")

            print(f"✅ Cognito Client情報取得成功:")
            print(f"   Client ID: {client_id}")
            print(f"   Client Secret: {'*' * 8}...{client_secret[-4:] if client_secret else 'なし'}")

            return {
                "client_id": client_id,
                "client_secret": client_secret,
                "user_pool_id": user_pool_id,
                "discovery_url": discovery_url
            }

        except ClientError as e:
            print(f"❌ Cognito Client情報取得エラー: {e}")
            raise

    def create_oauth2_credential_provider(self, 
                                        provider_name: str,
                                        cognito_info: Dict[str, str]) -> Dict[str, Any]:
        """
        OAuth2 Credential Providerを作成
        """
        try:
            create_request = {
                "name": provider_name,
                "description": f"Gateway M2M認証用Credential Provider - {provider_name}",
                "configurationOverrides": {
                    "clientId": cognito_info["client_id"],
                    "clientSecret": cognito_info["client_secret"],
                    "discoveryUrl": cognito_info["discovery_url"],
                    "grantType": "client_credentials"
                }
            }

            print(f"🔐 OAuth2 Credential Provider作成中: {provider_name}")

            response = self.identity_client.create_oauth2_credential_provider(**create_request)

            print(f"✅ OAuth2 Credential Provider作成成功!")
            print(f"   Provider Name: {response['name']}")
            print(f"   Provider ID: {response.get('credentialProviderId', 'N/A')}")

            return response

        except ClientError as e:
            if e.response['Error']['Code'] == 'ConflictException':
                print(f"⚠️ Credential Provider '{provider_name}' は既に存在します")
                # 既存のプロバイダ情報を取得
                try:
                    list_response = self.identity_client.list_oauth2_credential_providers()
                    for provider in list_response.get("oauth2CredentialProviders", []):
                        if provider["name"] == provider_name:
                            print(f"✅ 既存のCredential Provider使用: {provider_name}")
                            return provider
                except Exception as list_error:
                    print(f"❌ 既存プロバイダ情報取得エラー: {list_error}")
                    raise
            else:
                print(f"❌ OAuth2 Credential Provider作成エラー: {e}")
                raise

実行方法:

# OutboundAuth設定(必須ステップ)- .envファイルから自動読み取り
python setup_outbound_auth.py

改善ポイント: コマンドライン引数が不要になり、.envファイルからGATEWAY_IDを自動読み取りして、IDENTITY_PROVIDER_NAMEGATEWAY_SCOPEも自動で.envファイルに保存されるようになりました!

実行結果の例:

🚀 OutboundAuth自動設定開始
   .envファイルから設定を読み込み中...
   Gateway ID: 1234567890abcdef
   Provider Name: agentcore-identity-for-gateway
   Region: us-west-2
--------------------------------------------------
✅ Gateway情報取得成功: order-management-gateway
   Gateway URL: https://gateway-1234567890abcdef.mcp.us-west-2.amazonaws.com
✅ Discovery URL取得成功: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXXXXX/.well-known/openid-configuration
✅ User Pool ID抽出成功: us-west-2_XXXXXXX
✅ Cognito Client情報取得成功:
   Client ID: 1234567890abcdef
   Client Secret: ********...xyz123
🔐 OAuth2 Credential Provider作成中: agentcore-identity-for-gateway
✅ OAuth2 Credential Provider作成成功!
   Provider Name: agentcore-identity-for-gateway
   Provider ID: resource-provider-oauth-client
--------------------------------------------------
🎉 OutboundAuth設定完了!

✅ 以下の環境変数を.envファイルに自動保存しました:
   IDENTITY_PROVIDER_NAME=agentcore-identity-for-gateway
   GATEWAY_SCOPE=agentcore-gateway/read agentcore-gateway/write

5. AgentCore Gatewayの作成

いよいよGatewayを作成します!Cognito認証を設定し、Lambda関数をMCPツールとして登録します。

create_gateway.pyでは、.envファイルから設定を読み込み、Gateway作成後に結果を.envファイルに自動保存します。

create_gateway.py
import boto3
import json
import os
from bedrock_agentcore_starter_toolkit.operations.gateway.client import GatewayClient
from dotenv import load_dotenv, set_key

def create_gateway_with_lambda(cognito_config, gateway_role_arn, lambda_arn):
    """
    Cognito認証付きGatewayを作成し、Lambda関数をMCPツール化
    """

    # Gateway clientの初期化
    gateway_client = GatewayClient(region_name="us-west-2")

    # 1. Gatewayの作成(Cognito認証設定)
    gateway_name = "order-management-gateway"
    print(f"🚀 Gateway '{gateway_name}' を作成中...")

    try:
        authorizer_config = {
            "customJWTAuthorizer": {
                "discoveryUrl": cognito_config["discovery_url"],
                "allowedClients": [cognito_config["client_id"]]
            }
        }

        gateway_response = gateway_client.create_mcp_gateway(
            name=gateway_name,
            role_arn=gateway_role_arn,
            authorizer_config=authorizer_config,
            enable_semantic_search=True  # セマンティック検索も有効化!
        )

        # gateway_responseは辞書なので、キーでアクセス
        gateway_id = gateway_response['gateway_id'] if 'gateway_id' in gateway_response else gateway_response['gatewayId']
        gateway_url = gateway_response['mcp_url'] if 'mcp_url' in gateway_response else gateway_response['mcpUrl']

    except Exception as e:
        if "already exists" in str(e) or "ConflictException" in str(e):
            print(f"ℹ️  Gateway '{gateway_name}' は既に存在します。既存のGatewayを取得中...")

            # 既存のGatewayを取得
            gateways = gateway_client.list_gateways()
            existing_gateway = None

            # 名前で検索
            for gateway in gateways:
                if gateway.get('name') == gateway_name:
                    existing_gateway = gateway
                    break

            if existing_gateway:
                gateway_id = existing_gateway['gateway_id'] if 'gateway_id' in existing_gateway else existing_gateway['gatewayId']
                gateway_url = existing_gateway['mcp_url'] if 'mcp_url' in existing_gateway else existing_gateway['mcpUrl']
            else:
                print("❌ 既存のGatewayが見つかりませんでした")
                raise e
        else:
            raise e
    print(f"✅ Gateway作成完了!")
    print(f"   Gateway ID: {gateway_id}")
    print(f"   MCP URL: {gateway_url}")

    # 2. Lambda関数をターゲットとして登録
    print("\n🔧 Lambda関数をMCPツールとして登録中...")

    # ツールスキーマの定義
    tool_schemas = [
        {
            "name": "get_order_tool",
            "description": "注文情報を取得します",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "orderId": {
                        "type": "string",
                        "description": "注文ID"
                    }
                },
                "required": ["orderId"]
            }
        },
        {
            "name": "update_order_tool",
            "description": "注文情報を更新します",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "orderId": {
                        "type": "string",
                        "description": "注文ID"
                    }
                },
                "required": ["orderId"]
            }
        }
    ]

    # ターゲット設定
    target_config = {
        "mcp": {
            "lambda": {
                "lambdaArn": lambda_arn,
                "toolSchema": {
                    "inlinePayload": tool_schemas
                }
            }
        }
    }

    # 認証情報プロバイダー(Lambda呼び出しにはGatewayのIAMロールを使用)
    credential_config = [
        {
            "credentialProviderType": "GATEWAY_IAM_ROLE"
        }
    ]

    bedrock_client = boto3.client('bedrock-agentcore-control', region_name='us-west-2')
    target_response = bedrock_client.create_gateway_target(
        gatewayIdentifier=gateway_id,
        name="order-lambda-target",
        description="注文管理Lambda関数",
        targetConfiguration=target_config,
        credentialProviderConfigurations=credential_config
    )

    print(f"✅ Lambda MCPツール登録完了!")
    print(f"   Target ID: {target_response['targetId']}")

    # .envファイルに結果を保存
    set_key(".env", "GATEWAY_ID", gateway_id)
    set_key(".env", "GATEWAY_URL", gateway_url)
    print(f"\n✅ Gateway情報を.envファイルに保存しました!")

    return {
        "gateway_id": gateway_id,
        "gateway_url": gateway_url,
        "target_id": target_response['targetId']
    }

if __name__ == "__main__":
    # .envファイルから設定を読み込み
    load_dotenv()

    # Cognito設定を.envから取得
    cognito_config = {
        "discovery_url": os.environ.get("COGNITO_DISCOVERY_URL"),
        "client_id": os.environ.get("M2M_CLIENT_ID"),
        "client_secret": os.environ.get("M2M_CLIENT_SECRET")
    }

    # IAMロールとLambda ARNも.envから取得
    gateway_role_arn = os.environ.get("GATEWAY_ROLE_ARN")
    lambda_arn = os.environ.get("LAMBDA_ARN")

    # 必須項目のチェック
    if not all([cognito_config["discovery_url"], cognito_config["client_id"], 
                cognito_config["client_secret"], gateway_role_arn, lambda_arn]):
        print("❌ .envファイルに必要な設定が不足しています")
        print("   以下の項目を確認してください:")
        print("   - COGNITO_DISCOVERY_URL, M2M_CLIENT_ID, M2M_CLIENT_SECRET")
        print("   - GATEWAY_ROLE_ARN, LAMBDA_ARN")
        exit(1)

    gateway_info = create_gateway_with_lambda(cognito_config, gateway_role_arn, lambda_arn)

6. AgentCore Runtimeの実装

Runtime エージェントの実装

次に、AgentCore Identityを使ってGatewayを呼び出すエージェントを実装します。

runtime_agent.pyでは、AgentCore Identityの@requires_access_tokenデコレーターを使用してM2M認証を自動化します。これにより、手動でのトークン取得処理が不要になります。便利ですね。

処理自体はシンプルで注文の照会や更新を担当するエージェントとします。

runtime_agent.py
import os
from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from bedrock_agentcore.identity.auth import requires_access_token
from typing import Dict, Any

# エージェントアプリケーションの初期化
app = BedrockAgentCoreApp()

@app.entrypoint
async def order_management_agent(payload: Dict[str, Any]):
    """
    注文管理エージェント(AgentCore Identity使用)
    """
    print("📋 エージェント起動")
    print(f"受信したペイロード: {payload}")

    # AgentCore Identityを使用してGatewayにアクセス
    gateway_url = os.environ.get("GATEWAY_URL")
    provider_name = os.environ.get("IDENTITY_PROVIDER_NAME", "agentcore-identity-for-gateway")
    gateway_scope = os.environ.get("GATEWAY_SCOPE")  # 例: "agentcore-gateway/read agentcore-gateway/write"

    @requires_access_token(
        provider_name="resource-provider-oauth-client-zckzu",
        scopes=gateway_scope.split() if gateway_scope else [],
        auth_flow="M2M",
        force_authentication=False,
    )
    async def process_with_gateway(*, access_token: str) -> str:
        """
        Gatewayへのアクセストークンを取得し、MCPクライアントで処理
        """
        print(f"✅ アクセストークン取得成功")

        # MCPクライアントの作成(AgentCore Identity認証トークン付き)
        def create_streamable_http_transport():
            return streamablehttp_client(
                gateway_url, 
                headers={"Authorization": f"Bearer {access_token}"}
            )

        client = MCPClient(create_streamable_http_transport)
        print(f"✅ MCP Client初期化完了(AgentCore Identity認証)")

        try:
            with client:
                # ツールリストを取得
                tools = client.list_tools_sync()
                print(f"🛠️ 利用可能なツール: {[tool.tool_name for tool in tools]}")

                # Bedrockモデルとエージェントの初期化
                model = BedrockModel(
                    model_id="anthropic.claude-3-5-haiku-20241022-v1:0",
                    params={"max_tokens": 4096, "temperature": 0.7},
                    region="us-west-2"
                )

                agent = Agent(
                    model=model,
                    tools=tools,
                    system_prompt="あなたは注文管理システムのアシスタントです。注文の照会や更新を手伝います。"
                )
                print("✅ エージェント初期化完了!")

                # ユーザー入力を処理
                user_input = payload.get("prompt", "注文IDの123の情報を教えて")
                print(f"💬 ユーザー入力: {user_input}")

                # エージェントで処理(内部でGatewayのツールを呼び出す)
                response = agent(user_input)
                result = response.message['content'][0]['text']
                print(f"🤖 エージェント応答: {result}")
                return result

        except Exception as e:
            print(f"❌ エージェント処理エラー: {e}")
            return f"エラーが発生しました: {str(e)}"

    try:
        # AgentCore Identityを使用してアクセストークンを取得し、処理を実行
        return await process_with_gateway()
    except Exception as e:
        print(f"❌ 認証エラー: {e}")
        return f"認証に失敗しました: {str(e)}"

if __name__ == "__main__":
    app.run()

Runtimeのデプロイ

最後に、RuntimeをデプロイするスクリプトPython版です。

deploy_runtime.pyでは、認証なしのRuntimeをデプロイし、.envファイルから読み込んだ設定を環境変数として渡します。

コード全量
deploy_runtime.py
from bedrock_agentcore_starter_toolkit import Runtime
import os
from dotenv import load_dotenv, set_key

def deploy_runtime():
    """
    認証なしのRuntimeをデプロイ(M2M認証はGateway呼び出し時に実行)
    """
    # .envファイルを読み込み
    load_dotenv()

    print("🚀 AgentCore Runtimeのデプロイを開始...")

    # .envファイルから環境変数を取得
    env_vars = {
        # Gateway接続情報
        "GATEWAY_URL": os.environ.get("GATEWAY_URL"),

        # Cognito M2M認証情報
        "COGNITO_DISCOVERY_URL": os.environ.get("COGNITO_DISCOVERY_URL"),
        "M2M_CLIENT_ID": os.environ.get("M2M_CLIENT_ID"),
        "M2M_CLIENT_SECRET": os.environ.get("M2M_CLIENT_SECRET"),
        "RESOURCE_SERVER_ID": os.environ.get("RESOURCE_SERVER_ID", "agentcore-gateway")
    }

    # Runtimeの設定(認証なし)
    runtime = Runtime()

    response = runtime.configure(
        entrypoint="runtime_agent.py",
        execution_role=os.environ.get("RUNTIME_ROLE_ARN"),
        auto_create_ecr=True,
        requirements_file="requirements.txt",
        region="us-west-2",
        agent_name="order_management_runtime",
        # 認証設定なし!(Inbound Authなし)
    )

    print("✅ Runtime設定完了!デプロイ中...")

    # デプロイ実行
    launch_result = runtime.launch(env_vars=env_vars)

    print(f"✅ Runtimeデプロイ完了!")
    print(f"   Agent ARN: {launch_result.agent_arn}")
    print(f"   注意: このRuntimeは認証なしで動作します(開発環境用)")

    # Runtime ARNを.envファイルに保存
    set_key(".env", "RUNTIME_ARN", launch_result.agent_arn)
    print(f"✅ Runtime ARNを.envファイルに保存しました!")

    return launch_result

if __name__ == "__main__":
    deploy_runtime()

動作確認

実行手順のまとめ

全体の流れを整理すると以下のようになります。

# 初期設定
cp .env.example .env

# Cognito設定(.envファイルを自動更新)
python setup_cognito.py
# → .envファイルにCognito設定が自動保存される

# IAMロール作成(.envファイルを自動更新)
python create_iam_roles.py
# → Gateway用とRuntime用のロールARNが.envファイルに自動保存される

# Lambda関数作成(.envファイルを自動更新)
python create_lambda.py
# → Lambda関数が作成され、ARNが.envファイルに自動保存される

# Gateway作成(.envファイルを自動更新)
python create_gateway.py
# → .envファイルにGateway URLが自動保存される

# OutboundAuth設定(必須ステップ)
python setup_outbound_auth.py
# → .envから自動読み取り、AgentCore IdentityのCredential Provider作成、結果を.envに自動保存

# Runtimeデプロイ(.envファイルから設定を読み込み、Runtime ARNも.envに自動保存)
python deploy_runtime.py

すべての設定は.envファイルで管理して、自動化しています!

動作テスト

動作確認には、Python版のテストスクリプトを使用します。invoke_agentcore.pyでは、.envファイルからRuntimeARNを自動読み取りし、boto3を使って実際のRuntimeを呼び出し、エラーハンドリングやストリーミングレスポンスも適切に処理します。

Runtime呼び出し時に「Workload access token has not been set」エラーが発生して困りました。
調べてみると、runtimeUserIdパラメータの設定が必要だったことがわかりました。
これはAgentCore Identityでユーザーコンテキストを管理するために必要な設定でした。Inbound AuthでRuntimeにも同一CognitoでJWT認証を使っていれば自動で設定されるのですが、今回は認証なしRuntimeのためパラメータ指定が必要でした。

今回はRuntimeのInbound AuthにCognitoを設定しなかったので、同一Cognitoを設定するケースも試してみたいですね!

この問題については下記Qiitaで詳しく解説されており、大変参考になりました!

https://qiita.com/har1101/items/aae967fa157b01e414a9

公式ドキュメントにも一部記載があるので、気になる方はご参照ください。

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/get-workload-access-token.html

コード全量
invoke_agentcore.py
"""
Bedrock AgentCore Runtime を呼び出すサンプルスクリプト
"""

import boto3
import json
import os
from dotenv import load_dotenv

def invoke_agent_runtime(prompt, runtime_arn=None, qualifier="DEFAULT", region="us-west-2"):
    """
    Bedrock AgentCore Runtime を呼び出す

    Args:
        prompt: エージェントに送信するプロンプト
        runtime_arn: エージェントランタイムのARN(.envから自動読み取り)
        qualifier: エンドポイント名(デフォルトは "DEFAULT")
        region: AWSリージョン(デフォルトは "us-west-2")

    Returns:
        エージェントからのレスポンス
    """

    # .envファイルからランタイムARNを取得
    if runtime_arn is None:
        load_dotenv()
        runtime_arn = os.environ.get("RUNTIME_ARN")
        if not runtime_arn:
            raise ValueError("RUNTIME_ARN が.envファイルに設定されていません。deploy_runtime.pyを実行してください。")

    # Bedrock AgentCore クライアントを初期化
    client = boto3.client('bedrock-agentcore', region_name=region)

    # ペイロードを準備(JSON形式でエンコード)
    payload = json.dumps({
        "prompt": prompt
    }).encode('utf-8')

    try:
        # エージェントランタイムを呼び出し
        response = client.invoke_agent_runtime(
            agentRuntimeArn=runtime_arn,
            qualifier=qualifier,
            payload=payload,
            contentType='application/json',
            accept='application/json',
            runtimeUserId="test-user-123"
        )

        # レスポンスを処理
        if response.get('contentType') == 'text/event-stream':
            # ストリーミングレスポンスの処理
            content = []
            for line in response['response'].iter_lines(chunk_size=10):
                if line:
                    line = line.decode('utf-8')
                    if line.startswith('data: '):
                        line = line[6:]
                        print(f"ストリーミング: {line}")
                        content.append(line)
            return '\n'.join(content)

        elif response.get('contentType') == 'application/json':
            # JSON レスポンスの処理
            content = []
            for chunk in response.get('response', []):
                content.append(chunk.decode('utf-8'))
            return json.loads(''.join(content))

        else:
            # その他のレスポンス形式
            return response

    except Exception as e:
        print(f"エラーが発生しました: {type(e).__name__}: {str(e)}")

        # エラーの詳細情報を表示
        if hasattr(e, 'response'):
            error_code = e.response.get('Error', {}).get('Code', 'Unknown')
            error_message = e.response.get('Error', {}).get('Message', 'No message')
            print(f"エラーコード: {error_code}")
            print(f"エラーメッセージ: {error_message}")

        return None

def main():
    """メイン関数"""

    # 使用例
    print("=== Bedrock AgentCore Runtime 呼び出しサンプル ===\n")

    # 1. 基本的な呼び出し
    print("1. 基本的な呼び出し:")
    response = invoke_agent_runtime("注文ID 123の情報を教えてください エラーが起きた場合はエラーについても丁寧に教えてください。エラーメッセージの原文も添えてね。")
    if response:
        print(f"レスポンス: {response}\n")

if __name__ == "__main__":
    main()

実行方法

# 動作確認スクリプトを実行
python invoke_agentcore.py

成功すると以下のような応答が返ってきます。

注文ID 123の情報は以下の通りです:
- 注文ID: 123
- 状態: 処理中 (processing)
- 商品:
  1. 商品A: 2点
  2. 商品B: 1点
- 合計金額: 5,000円

回答を見るに問題なくLambda関数をMCPサーバー化してToolとして利用できました!
レスポンスは下記が返ってくるので、LLMがJSONを理解して回答してくれましたね!!

{
  "orderId": order_id,
  "status": "processing",
  "items": [
      {"name": "商品A", "quantity": 2},
      {"name": "商品B", "quantity": 1}
  ],
  "total": 5000
}

今回のようにLambda関数をMCP Serverにするのは面白いですね。固定文言を今回は返却しましたが、Lambda関数でS3の情報やDynamoDBを参照してLLMに情報として渡すことも可能です!

権限などは要検討ですが、Lambda関数自体は特殊な加工は不要だったので簡単にMCPツール化できて面白いですね。

おわりに

今回はAmazon Bedrock AgentCore RuntimeからGatewayを呼び出し、Lambda関数をMCPツール化する方法を実装してみました!
Lambda関数をMCP Serverにする面白い機能ですよね。

今後はOpenAPI仕様のAPIをMCP Server化したり、Semantic Search Toolについても検証していきたいと思います!
本記事が少しでも参考になりましたら幸いです!最後までご覧いただきありがとうございましたー!!

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.