👻

Bedrock AgentCore Gateway の使い方

に公開

Bedrock AgentCore Gateway ってなんなのさ?

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html

うーん、わからん。

という方向けに、brave/tavily の MCP を AgentCore Gateway 経由で使う方法を紹介するのでイメージを掴んでください。

とはいえ今回の用途をざっくり抑える

Gateway は MCP サーバーのマネージドサービスです。
もちろん自分で作った Lambda を MCP 化したり OpenAPI, Smithy などを MCP 化することもできますが、様々なサービスプロバイダーも MCP サーバーを用意しており、簡単に自分の Agent に取り込むことができます。
今回はその中の Brave および Tavily という Web 検索を提供する MCP サーバーを Gateway で使ってみます。

準備

Brave も Tavily も無料枠はあるものの有償サービスであり、その利用には API Key が必要です。Gateway から Brave や Tavily を使う場合も API Key が必要なのであらかじめ準備します。

https://brave.com/ja/search/api/

https://app.tavily.com/home

ここから API Key を発行して手元に残しておきます。

Identity 作成

API Key で Brave や Tavily の検索を行いますが、API Key は漏洩すると使われてしまうのでソースコード等に埋め込んではいけません。
AgentCore では Identity という機能でその Secret をマネージドに保管・利用することができます。

https://us-east-1.console.aws.amazon.com/bedrock-agentcore/identity?region=us-east-1

から(ue-east-1 の場合)、Add OAuth client / API Key からその API Key を保存します。

そしてそれぞれの API Key を Add していきます。

するとこんな感じ

Gateway 作成

次に Gateway を作成します。

https://us-east-1.console.aws.amazon.com/bedrock-agentcore/toolsAndGateways?region=us-east-1

から(us-east-1 の場合)、Create Gateway をクリックします。

Gateway Name に任意の名前を入れ、Enable semantic search - optional にチェックを入れる以外は KMS Key まではデフォルトでいきます。

Target のところで Integrations を選択して先ほどの Brave および Tavily を設定します。(Add another target をクリックすることで Target を増やせます)
また、Outbound は API key を選択します。

最後に Create gateway をクリックします。

すると Gateway ができあがります。

ここからコードを使いますが必要な情報を抜いておきます。

1 つ目は Gateway resource URL です。

2 つ目は Amazon Cognito に生えている User Pools の UserpoolIDです。

最後に Amazon Cognito の App Clients の Client ID です。

MCP サーバーを使う

さてここまでで必要な情報が揃いました。
ここからは Agent のコードの記述です。必ずしも AgentCore Runtime で動かす必要ないので、今回はローカルで動かすコードを前提に書いています。

定数の設定

先ほどまでの値を定数として設定します。

定数
user_pool_id = "us-east-1_xxxxxxxxx"
client_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
gateway_url = "https://gateway-quick-start-xxxxxx-xxxxxxxxxx.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp"

Agent のコード

そして、以下のコードを実行します。

Agent
import boto3
import requests
from strands.tools.mcp.mcp_client import MCPClient
from strands.agent import Agent
from mcp.client.streamable_http import streamablehttp_client

client = boto3.client("cognito-idp")
client_secret = client.describe_user_pool_client(
    UserPoolId=user_pool_id,
    ClientId=client_id
)["UserPoolClient"].get("ClientSecret")
domain = client.describe_user_pool(UserPoolId=user_pool_id)['UserPool'].get('Domain')
token_url = f"https://{domain}.auth.{client._client_config.region_name}.amazoncognito.com/oauth2/token"
response = requests.post(
    token_url,
    data=f"grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}",
    headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
response.raise_for_status()
bearer_token = response.json()["access_token"]

mcp_client = MCPClient(lambda: streamablehttp_client(gateway_url, headers={"Authorization": f"Bearer {bearer_token}"}))
with mcp_client:
    tools = mcp_client.list_tools_sync()
    agent = Agent(
        tools = tools
    )
    agent("Bedrock AgentCore とは")

すると以下のような出力を得ます。このケースでは Tavily を使ったようです。

出力
「Bedrock AgentCore」について調べてみましょう。
Tool #1: tavily___TavilySearchPost
Amazon Bedrock AgentCoreについて詳しく説明いたします。

## Amazon Bedrock AgentCore とは

Amazon Bedrock AgentCoreは、AWSが提供する**AIエージェントの展開・運用に特化した完全マネージド型サービス**です。2025年にプレビュー版として発表されました。

### 主な特徴

#### 1. **柔軟性とスケーラビリティ**
- あらゆるフレームワーク(LangChain、CrewAI、LangGraph、Strands Agentsなど)をサポート
- Amazon Bedrockの内外を問わず、任意の基盤モデルに対応
- エンタープライズグレードのスケールで動作

#### 2. **インフラストラクチャの簡素化**
- カスタムインフラの構築負担を軽減
- セッション管理、アイデンティティ制御、メモリシステムなどを自動提供
- サーバーレス環境での高速コールドスタート

#### 3. **主要コンポーネント**

**AgentCore Runtime**
- 動的AIエージェントとツールのデプロイ・スケーリングに特化したセキュアなサーバーレスランタイム
- 真のセッション分離機能
- マルチモーダルペイロードサポート

**AgentCore Gateway**
- エージェントがツールを安全に発見・使用するためのゲートウェイ
- API、Lambda関数、既存サービスをエージェント対応ツールに変換
- カスタムコード開発の工数を大幅削減

**AgentCore Observability**
- エージェントの動作監視・管理機能

### 主な用途
- カスタマーサポートの自動化
- エンタープライズワークフローの効率化
- マルチエージェントシステムの構築
- リアルタイムまたは長時間実行エージェントの運用

Amazon Bedrock AgentCoreは、従来のAmazon Bedrock Agentsと異なり、**本格的なプロダクション環境でのエージェント運用**に焦点を当てた、より高度で柔軟なソリューションとして位置づけられています。

その他機能

使用できる MCP サーバーのツール一覧の取得

id に list-tool-request という値を指定すれば使用できるツール一覧を取得できる。

ツール一覧の取得
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {bearer_token}"
}
payload = {
    "jsonrpc": "2.0",
    "id": "list-tools-request",
    "method": "tools/list"
}
response = requests.post(gateway_url, headers=headers, json=payload)
response.raise_for_status()
response.json()

確かに brave と tavily が使えることがわかる。

出力
{'jsonrpc': '2.0',
 'id': 'list-tools-request',
 'result': {'tools': [{'inputSchema': {'type': 'object',
     'properties': {'country': {'description': '2-character country code for result origin',
       'type': 'string'},
      'text_decorations': {'description': 'Include decoration markers in results',
       'type': 'boolean'},
      'safesearch': {'description': 'Safe search level',
       'type': 'string',
       'enum': ['off', 'moderate', 'strict']},
      'result_filter': {'description': 'Comma-separated list of result types to include',
       'type': 'string'},
      'User-Agent': {'description': 'Client user agent string. Used to tailor search experience. Should follow standard formats (e.g. browser user agents).',
       'type': 'string'},
      'X-Loc-Timezone': {'examples': ['America/New_York'],
       'description': 'IANA timezone identifier for the client’s location',
       'type': 'string'},
      'X-Loc-Country': {'description': 'Two-letter ISO 3166-1 alpha-2 country code',
       'type': 'string'},
      'units': {'description': 'Measurement units',
       'type': 'string',
       'enum': ['metric', 'imperial']},
      'Accept-Encoding': {'description': 'The supported compression type is gzip',
       'type': 'string',
       'enum': ['gzip']},
      'freshness': {'description': 'Recency filter (e.g., pd, pw, pm, py)',
       'type': 'string'},
      'X-Loc-City': {'description': 'Name of the client’s city',
       'type': 'string'},
      'X-Loc-State-Name': {'description': 'Name of the client’s state or region',
       'type': 'string'},
      'X-Loc-State': {'description': 'Code representing the client’s state or region (ISO 3166-2)',
       'type': 'string'},
      'extra_snippets': {'description': 'Include additional excerpts',
       'type': 'boolean'},
      'summary': {'description': 'Enable summary key generation',
       'type': 'boolean'},
      'Api-Version': {'description': 'The Brave Web Search API version to use (format: YYYY-MM-DD). Defaults to the latest version.',
       'type': 'string'},
      'offset': {'description': 'Number of results to skip',
       'type': 'integer'},
      'Accept': {'description': 'The default supported media type is application/json',
       'type': 'string'},
      'count': {'description': 'Number of search results to return',
       'type': 'integer'},
      'ui_lang': {'description': 'Language code for UI in the response',
       'type': 'string'},
      'q': {'description': "The user's search query term.", 'type': 'string'},
      'Cache-Control': {'description': "To prevent caching, set to 'no-cache'. Otherwise, cached results may be returned.",
       'type': 'string',
       'enum': ['no-cache']},
      'spellcheck': {'description': 'Enable spellchecking on query',
       'type': 'boolean'},
      'goggles': {'description': 'List of goggles for custom re-ranking',
       'type': 'array',
       'items': {'type': 'string'}},
      'X-Loc-Postal-Code': {'description': 'Postal code of the client’s location',
       'type': 'string'},
      'X-Loc-Lat': {'format': 'float',
       'description': 'Latitude of the client’s location',
       'type': 'number'},
      'search_lang': {'description': 'Language code for search results',
       'type': 'string'},
      'X-Loc-Long': {'format': 'float',
       'description': 'Longitude of the client’s location',
       'type': 'number'}},
     'required': ['q']},
    'name': 'brave___BraveWebSearch',
    'description': 'Performs a web search query'},
   {'inputSchema': {'type': 'object',
     'properties': {'urls': {'type': 'string'},
      'extract_depth': {'type': 'string', 'enum': ['basic', 'advanced']},
      'include_images': {'type': 'boolean'}},
     'required': ['urls']},
    'name': 'tavily___TavilySearchExtract',
    'description': 'ExtractwebpagecontentfromURLsusingTavilyExtract'},
   {'inputSchema': {'type': 'object',
     'properties': {'query': {'examples': ['whoisLeoMessi?'],
       'type': 'string'},
      'chunks_per_source': {'type': 'integer'},
      'include_domains': {'type': 'array', 'items': {'type': 'string'}},
      'include_images': {'type': 'boolean'},
      'search_depth': {'type': 'string', 'enum': ['basic', 'advanced']},
      'time_range': {'type': 'string',
       'enum': ['day', 'week', 'month', 'year', 'd', 'w', 'm', 'y']},
      'topic': {'type': 'string', 'enum': ['general', 'news']},
      'max_results': {'type': 'integer'},
      'days': {'type': 'integer'},
      'include_answer': {'type': 'boolean'},
      'exclude_domains': {'type': 'array', 'items': {'type': 'string'}},
      'include_image_descriptions': {'type': 'boolean'},
      'include_raw_content': {'type': 'boolean'}},
     'required': ['query']},
    'name': 'tavily___TavilySearchPost',
    'description': 'ExecuteasearchqueryusingTavilySearch'}]}}

MCP サーバーが使えるツールの検索

こんなコードで検索もできます

ツールの検索
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {bearer_token}"
}
payload = {
    "jsonrpc": "2.0",
    "id": "search-tools-request",
    "method": "tools/call",
    "params": {
        "name": "x_amz_bedrock_agentcore_search",
        "arguments": {"query": "Web Search"}
    }
}
response = requests.post(gateway_url, headers=headers, json=payload)
response.raise_for_status()
response.json()
結果
{'jsonrpc': '2.0',
 'id': 'search-tools-request',
 'result': {'structuredContent': {'tools': [{'inputSchema': {'type': 'object',
      'properties': {'country': {'description': '2-character country code for result origin',
        'type': 'string'},
       'text_decorations': {'description': 'Include decoration markers in results',
        'type': 'boolean'},
       'safesearch': {'description': 'Safe search level',
        'type': 'string',
        'enum': ['off', 'moderate', 'strict']},
       'result_filter': {'description': 'Comma-separated list of result types to include',
        'type': 'string'},
       'User-Agent': {'description': 'Client user agent string. Used to tailor search experience. Should follow standard formats (e.g. browser user agents).',
        'type': 'string'},
       'X-Loc-Timezone': {'examples': ['America/New_York'],
        'description': 'IANA timezone identifier for the client’s location',
        'type': 'string'},
       'X-Loc-Country': {'description': 'Two-letter ISO 3166-1 alpha-2 country code',
        'type': 'string'},
       'units': {'description': 'Measurement units',
        'type': 'string',
        'enum': ['metric', 'imperial']},
       'Accept-Encoding': {'description': 'The supported compression type is gzip',
        'type': 'string',
        'enum': ['gzip']},
       'freshness': {'description': 'Recency filter (e.g., pd, pw, pm, py)',
        'type': 'string'},
       'X-Loc-City': {'description': 'Name of the client’s city',
        'type': 'string'},
       'X-Loc-State-Name': {'description': 'Name of the client’s state or region',
        'type': 'string'},
       'X-Loc-State': {'description': 'Code representing the client’s state or region (ISO 3166-2)',
        'type': 'string'},
       'extra_snippets': {'description': 'Include additional excerpts',
        'type': 'boolean'},
       'summary': {'description': 'Enable summary key generation',
        'type': 'boolean'},
       'Api-Version': {'description': 'The Brave Web Search API version to use (format: YYYY-MM-DD). Defaults to the latest version.',
        'type': 'string'},
       'offset': {'description': 'Number of results to skip',
        'type': 'integer'},
       'Accept': {'description': 'The default supported media type is application/json',
        'type': 'string'},
       'count': {'description': 'Number of search results to return',
        'type': 'integer'},
       'ui_lang': {'description': 'Language code for UI in the response',
        'type': 'string'},
       'q': {'description': "The user's search query term.", 'type': 'string'},
       'Cache-Control': {'description': "To prevent caching, set to 'no-cache'. Otherwise, cached results may be returned.",
        'type': 'string',
        'enum': ['no-cache']},
       'spellcheck': {'description': 'Enable spellchecking on query',
        'type': 'boolean'},
       'goggles': {'description': 'List of goggles for custom re-ranking',
        'type': 'array',
        'items': {'type': 'string'}},
       'X-Loc-Postal-Code': {'description': 'Postal code of the client’s location',
        'type': 'string'},
       'X-Loc-Lat': {'format': 'float',
        'description': 'Latitude of the client’s location',
        'type': 'number'},
       'search_lang': {'description': 'Language code for search results',
        'type': 'string'},
       'X-Loc-Long': {'format': 'float',
        'description': 'Longitude of the client’s location',
        'type': 'number'}},
      'required': ['q']},
     'name': 'brave___BraveWebSearch',
     'description': 'Performs a web search query'},
    {'inputSchema': {'type': 'object',
      'properties': {'query': {'examples': ['whoisLeoMessi?'],
        'type': 'string'},
       'chunks_per_source': {'type': 'integer'},
       'include_domains': {'type': 'array', 'items': {'type': 'string'}},
       'include_images': {'type': 'boolean'},
       'search_depth': {'type': 'string', 'enum': ['basic', 'advanced']},
       'time_range': {'type': 'string',
        'enum': ['day', 'week', 'month', 'year', 'd', 'w', 'm', 'y']},
       'topic': {'type': 'string', 'enum': ['general', 'news']},
       'max_results': {'type': 'integer'},
       'days': {'type': 'integer'},
       'include_answer': {'type': 'boolean'},
       'exclude_domains': {'type': 'array', 'items': {'type': 'string'}},
       'include_image_descriptions': {'type': 'boolean'},
       'include_raw_content': {'type': 'boolean'}},
      'required': ['query']},
     'name': 'tavily___TavilySearchPost',
     'description': 'ExecuteasearchqueryusingTavilySearch'},
    {'inputSchema': {'type': 'object',
      'properties': {'urls': {'type': 'string'},
       'extract_depth': {'type': 'string', 'enum': ['basic', 'advanced']},
       'include_images': {'type': 'boolean'}},
      'required': ['urls']},
     'name': 'tavily___TavilySearchExtract',
     'description': 'ExtractwebpagecontentfromURLsusingTavilyExtract'}]},
  'isError': False,
  'content': [{'type': 'text',
    'text': '{"tools":[{"inputSchema":{"type":"object","properties":{"country":{"description":"2-character country code for result origin","type":"string"},"text_decorations":{"description":"Include decoration markers in results","type":"boolean"},"safesearch":{"description":"Safe search level","type":"string","enum":["off","moderate","strict"]},"result_filter":{"description":"Comma-separated list of result types to include","type":"string"},"User-Agent":{"description":"Client user agent string. Used to tailor search experience. Should follow standard formats (e.g. browser user agents).","type":"string"},"X-Loc-Timezone":{"examples":["America/New_York"],"description":"IANA timezone identifier for the client’s location","type":"string"},"X-Loc-Country":{"description":"Two-letter ISO 3166-1 alpha-2 country code","type":"string"},"units":{"description":"Measurement units","type":"string","enum":["metric","imperial"]},"Accept-Encoding":{"description":"The supported compression type is gzip","type":"string","enum":["gzip"]},"freshness":{"description":"Recency filter (e.g., pd, pw, pm, py)","type":"string"},"X-Loc-City":{"description":"Name of the client’s city","type":"string"},"X-Loc-State-Name":{"description":"Name of the client’s state or region","type":"string"},"X-Loc-State":{"description":"Code representing the client’s state or region (ISO 3166-2)","type":"string"},"extra_snippets":{"description":"Include additional excerpts","type":"boolean"},"summary":{"description":"Enable summary key generation","type":"boolean"},"Api-Version":{"description":"The Brave Web Search API version to use (format: YYYY-MM-DD). Defaults to the latest version.","type":"string"},"offset":{"description":"Number of results to skip","type":"integer"},"Accept":{"description":"The default supported media type is application/json","type":"string"},"count":{"description":"Number of search results to return","type":"integer"},"ui_lang":{"description":"Language code for UI in the response","type":"string"},"q":{"description":"The user\'s search query term.","type":"string"},"Cache-Control":{"description":"To prevent caching, set to \'no-cache\'. Otherwise, cached results may be returned.","type":"string","enum":["no-cache"]},"spellcheck":{"description":"Enable spellchecking on query","type":"boolean"},"goggles":{"description":"List of goggles for custom re-ranking","type":"array","items":{"type":"string"}},"X-Loc-Postal-Code":{"description":"Postal code of the client’s location","type":"string"},"X-Loc-Lat":{"format":"float","description":"Latitude of the client’s location","type":"number"},"search_lang":{"description":"Language code for search results","type":"string"},"X-Loc-Long":{"format":"float","description":"Longitude of the client’s location","type":"number"}},"required":["q"]},"name":"brave___BraveWebSearch","description":"Performs a web search query"},{"inputSchema":{"type":"object","properties":{"query":{"examples":["whoisLeoMessi?"],"type":"string"},"chunks_per_source":{"type":"integer"},"include_domains":{"type":"array","items":{"type":"string"}},"include_images":{"type":"boolean"},"search_depth":{"type":"string","enum":["basic","advanced"]},"time_range":{"type":"string","enum":["day","week","month","year","d","w","m","y"]},"topic":{"type":"string","enum":["general","news"]},"max_results":{"type":"integer"},"days":{"type":"integer"},"include_answer":{"type":"boolean"},"exclude_domains":{"type":"array","items":{"type":"string"}},"include_image_descriptions":{"type":"boolean"},"include_raw_content":{"type":"boolean"}},"required":["query"]},"name":"tavily___TavilySearchPost","description":"ExecuteasearchqueryusingTavilySearch"},{"inputSchema":{"type":"object","properties":{"urls":{"type":"string"},"extract_depth":{"type":"string","enum":["basic","advanced"]},"include_images":{"type":"boolean"}},"required":["urls"]},"name":"tavily___TavilySearchExtract","description":"ExtractwebpagecontentfromURLsusingTavilyExtract"}]}'}]}}

詳細はここにあるので確認をお願いします。

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html

アマゾン ウェブ サービス ジャパン (有志)

Discussion