Bedrock AgentCore Gateway の使い方
Bedrock AgentCore Gateway ってなんなのさ?
うーん、わからん。
という方向けに、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 が必要なのであらかじめ準備します。
ここから API Key を発行して手元に残しておきます。
Identity 作成
API Key で Brave や Tavily の検索を行いますが、API Key は漏洩すると使われてしまうのでソースコード等に埋め込んではいけません。
AgentCore では Identity という機能でその Secret をマネージドに保管・利用することができます。
から(ue-east-1 の場合)、Add OAuth client / API Key からその API Key を保存します。

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


するとこんな感じ

Gateway 作成
次に Gateway を作成します。
から(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 のコード
そして、以下のコードを実行します。
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"}]}'}]}}
詳細はここにあるので確認をお願いします。
Discussion