diff --git a/.gitignore b/.gitignore index 1600d676d..0cc4eaeea 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,7 @@ celerybeat.pid # Environments .env +.env.* .venv env/ venv/ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/README.md b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/README.md new file mode 100644 index 000000000..c67b60124 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/README.md @@ -0,0 +1,125 @@ +# Raw MCP Tool Discovery and Invocation + +## Overview + +This example calls the AgentCore gateway directly over the MCP protocol — no agent framework involved. It's the simplest way to verify your Gateway and Web Search Tool target are working correctly. + +> šŸ”’ **Search Privacy**: The Web Search Tool queries an AWS-maintained search index. Queries do not route to any third-party search engines or external providers. + +![Raw MCP Client web search Architecture](images/raw-mcp-call-architecture.png) + +## Prerequisites + +- Python 3.10+ +- AWS account with Amazon Bedrock enabled in **us-east-1** +- AWS credentials with IAM, Cognito, and AgentCore gateway permissions + +## How It Works + +The script performs three operations against the Gateway: + +### Step 1: Authenticate + +An OAuth token is obtained from Cognito using the `client_credentials` flow. The token is attached as a `Bearer` header on the MCP Streamable HTTP transport. This is handled internally by `create_streamable_http_transport()`. + +```python +from utils.gateway_auth import create_streamable_http_transport + +transport = create_streamable_http_transport() +``` + +### Step 2: Discover Tools (`tools/list`) + +The MCP `tools/list` call returns all tools available on the Gateway. For a Gateway with the Web Search connector target, you'll see: + +```json +{ + "name": "WebSearch", + "description": "Search the web for current information", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query (max 200 characters)" + } + }, + "required": ["query"] + } +} +``` + +### Step 3: Invoke the Tool (`tools/call`) + +Calling the tool with a query returns structured results: + +```json +{ + "results": [ + { + "text": "Snippet from the web page...", + "url": "https://example.com/article", + "title": "Article Title", + "publishedDate": "2026-05-28" + } + ] +} +``` + +## Files + +| File | Description | +|:-----|:------------| +| `raw_mcp_call.py` | Main example script — tool discovery and invocation | + +## Quick Start + +```bash +# Install dependencies +pip install -r ../requirements.txt +#python3 -m pip install --upgrade --force-reinstall boto3 + +# Set up the Gateway (creates IAM role, Cognito, Gateway, Web Search target) +python setup_gateway.py +#python setup_gateway.py --gateway-name my-web-search-gw + +# Load credentials into your shell +source .env.web-search + +# Run the example — discovers WebSearch tool and invokes it +python raw_mcp_call.py + +# Try a custom query +python raw_mcp_call.py --query "Latest Python release" +python raw_mcp_call.py --query "AWS re:Invent 2026 announcements" +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--query` | No | Search query (default: built-in example query) | + +## Cleanup (Optional) + +When you're done remove all provisioned AWS resources: + +**1. Retrieve resource IDs** from the setup output (printed when you ran `setup_gateway.py`): + +``` +Gateway ID: +IAM Role: agentcore-web-search-gateway-role +Cognito Pool: +``` + +> **Tip:** If you no longer have the terminal output, the gateway ID is the subdomain prefix in your `AGENTCORE_GATEWAY_URL` (e.g., `gw-abc123` from `https://gw-abc123.gateway.bedrock-agentcore...`). The IAM role follows the pattern `agentcore--role`. + +**2. Run cleanup:** + +```bash +python cleanup.py --gateway-id --user-pool-id --role-name +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--gateway-id` | Yes | Gateway ID | +| `--user-pool-id` | Yes | Cognito User Pool ID | +| `--role-name` | Yes | IAM role name | diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/cleanup.py b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/cleanup.py new file mode 100644 index 000000000..4d231f659 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/cleanup.py @@ -0,0 +1,111 @@ +""" +Clean up all resources created by the Web Search Tool setup. + +Deletes in order: + 1. Gateway targets and Gateway + 2. Cognito User Pool (domain, clients, pool) + 3. IAM Role and inline policies + +Prerequisites: + pip install -r ../requirements.txt + AWS credentials with permissions to delete the resources. + +Usage: + python cleanup.py --gateway-id --user-pool-id --role-name + python cleanup.py --gateway-id gw-abc123 --user-pool-id us-east-1_AbCdEf --role-name agentcore-web-search-gateway-role +""" + +import argparse +import os +import time + +import boto3 + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + + +def delete_gateway(gateway_client, gateway_id): + """Delete all targets and the gateway itself.""" + print("\n[1/3] Deleting Gateway resources...") + try: + targets = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100) + for item in targets["items"]: + gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=item["targetId"]) + print(f" Deleted target: {item['name']}") + + time.sleep(10) + gateway_client.delete_gateway(gatewayIdentifier=gateway_id) + print(f" Deleted gateway: {gateway_id}") + except Exception as e: + print(f" Error deleting gateway: {e}") + + +def delete_cognito(cognito_client, user_pool_id): + """Delete the Cognito User Pool and its domain.""" + print("\n[2/3] Deleting Cognito resources...") + try: + domain = user_pool_id.replace("_", "").lower() + cognito_client.delete_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + cognito_client.delete_user_pool(UserPoolId=user_pool_id) + print(f" Deleted user pool: {user_pool_id}") + except Exception as e: + print(f" Error deleting Cognito: {e}") + + +def delete_iam_role(iam_client, role_name): + """Delete the IAM role and its inline policies.""" + print("\n[3/3] Deleting IAM resources...") + try: + policies = iam_client.list_role_policies(RoleName=role_name) + for policy_name in policies["PolicyNames"]: + iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + print(f" Deleted policy: {policy_name}") + + iam_client.delete_role(RoleName=role_name) + print(f" Deleted role: {role_name}") + except Exception as e: + print(f" Error deleting IAM role: {e}") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Clean up Web Search Tool Gateway resources") + parser.add_argument("--gateway-id", required=True, help="Gateway ID to delete") + parser.add_argument("--user-pool-id", required=True, help="Cognito User Pool ID to delete") + parser.add_argument("--role-name", required=True, help="IAM role name to delete") + parser.add_argument("--region", default=REGION, help="AWS region (default: us-east-1)") + return parser.parse_args() + + +def main(): + args = parse_args() + region = args.region + + print("=" * 60) + print("AgentCore web search tool — Resource Cleanup") + print("=" * 60) + print(f"\nRegion: {region}") + print(f"Gateway ID: {args.gateway_id}") + print(f"User Pool ID: {args.user_pool_id}") + print(f"Role Name: {args.role_name}") + + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + cognito_client = boto3.client("cognito-idp", region_name=region) + iam_client = boto3.client("iam") + + delete_gateway(gateway_client, args.gateway_id) + delete_cognito(cognito_client, args.user_pool_id) + delete_iam_role(iam_client, args.role_name) + + # Remove local credentials file + env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env.web-search") + if os.path.exists(env_file): + os.remove(env_file) + print(f"\n Deleted local credentials file: {env_file}") + + print("\n" + "=" * 60) + print("āœ… All resources cleaned up successfully!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/images/raw-mcp-call-architecture.png b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/images/raw-mcp-call-architecture.png new file mode 100644 index 000000000..0868daf23 Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/images/raw-mcp-call-architecture.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/raw_mcp_call.py b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/raw_mcp_call.py new file mode 100644 index 000000000..1b0a6d924 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/raw_mcp_call.py @@ -0,0 +1,120 @@ +""" +Raw MCP Tool Discovery and Invocation against AgentCore gateway. + +Demonstrates direct MCP protocol calls without an agent framework: + 1. Connect to the Gateway using MCP Streamable HTTP transport + 2. Call tools/list to discover the WebSearch tool and its schema + 3. Call tools/call to invoke WebSearch with a test query + 4. Display the structured results + +This is useful for verifying your Gateway setup before integrating +with an agent framework. + +Prerequisites: + pip install -r ../requirements.txt + Export environment variables from 01-setup-gateway/setup_gateway.py + +Environment variables required: + AGENTCORE_GATEWAY_URL — Gateway MCP endpoint + COGNITO_DOMAIN — Cognito domain prefix + COGNITO_CLIENT_ID — Cognito app client ID + COGNITO_CLIENT_SECRET — Cognito app client secret + COGNITO_SCOPE — OAuth scope string + +IAM permissions required: + bedrock-agentcore:InvokeGateway (via OAuth token) + +Usage: + python raw_mcp_call.py + python raw_mcp_call.py --query "Latest Python release" +""" + +import argparse +import json +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.gateway_auth import create_streamable_http_transport + +from strands.tools.mcp.mcp_client import MCPClient + +# ── Configuration ───────────────────────────────────────────────────────────── + +DEFAULT_QUERY = "Tesla stock price right now?" + + +# ── Main ────────────────────────────────────────────────────────────────────── + + +def parse_args(): + parser = argparse.ArgumentParser(description="Raw MCP tool discovery and invocation against AgentCore gateway") + parser.add_argument( + "--query", + default=DEFAULT_QUERY, + help=f"Search query (default: '{DEFAULT_QUERY}')", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + print("=" * 60) + print("AgentCore web search tool — Raw MCP Calls") + print("=" * 60) + + # Create MCP client + transport_factory = create_streamable_http_transport() + mcp_client = MCPClient(transport_factory) + + with mcp_client: + # Step 1: Discover tools + print("\n[1] Discovering tools (tools/list)...\n") + tools = mcp_client.list_tools_sync() + print(f" Found {len(tools)} tool(s):\n") + + for tool in tools: + spec = tool.tool_spec + print(f" Name: {spec['name']}") + print(f" Description: {spec.get('description', 'N/A')}") + schema_str = json.dumps(spec.get("inputSchema", {}), indent=2) + # Truncate long schemas for readability + if len(schema_str) > 200: + schema_str = schema_str[:200] + "..." + print(f" Input: {schema_str}") + print() + + # Step 2: Invoke WebSearch + print("=" * 60) + print(f"[2] Calling WebSearch: '{args.query}'") + print("=" * 60 + "\n") + + # Find the WebSearch tool + ws_tools = [t for t in tools if "WebSearch" in t.tool_name] + if not ws_tools: + print(" ERROR: WebSearch tool not found. Check your Gateway target.") + return + + ws_tool_name = ws_tools[0].tool_name + result = mcp_client.call_tool_sync("raw-mcp-demo", ws_tool_name, {"query": args.query}) + + # Step 3: Display results + print("[3] Results:\n") + for content in result.get("content", result if isinstance(result, list) else [result]): + if isinstance(content, dict) and "text" in content: + try: + parsed = json.loads(content["text"]) + print(json.dumps(parsed, indent=2)) + except (ValueError, TypeError): + print(content["text"][:2000]) + else: + print(content) + + print("\n" + "=" * 60) + print("Demo complete!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/setup_gateway.py b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/setup_gateway.py new file mode 100644 index 000000000..8824d5c10 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/01-raw-mcp/setup_gateway.py @@ -0,0 +1,18 @@ +"""Set up AgentCore gateway with Web Search Tool — delegates to shared utility. +After running this script, load credentials with `source .env.web-search` to use +with the other web search examples. + +Usage: + python setup_gateway.py + python setup_gateway.py --gateway-name my-gateway + python setup_gateway.py --region us-east-1 +""" + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.gateway_setup import main + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/README.md b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/README.md new file mode 100644 index 000000000..f8a3e63ea --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/README.md @@ -0,0 +1,118 @@ +# Web Search with a Strands AI Agent + +## Overview + +This example shows the complete agent integration: a Strands agent automatically discovers and invokes the Web Search Tool to answer real-time questions with cited sources. + +![Strands Agent web search Architecture](images/strands-web-search-architecture.png) + +## Prerequisites + +- Python 3.10+ +- AWS account with Amazon Bedrock enabled in **us-east-1** +- AWS credentials with IAM, Cognito, and AgentCore gateway permissions +- Claude Sonnet 4 model access enabled in Bedrock + +```bash +pip install -r ../requirements.txt +``` + +## Quick Start + +```bash +# 1. Set up the Gateway (creates IAM role, Cognito, Gateway, Web Search target) +#python setup_gateway.py +python setup_gateway.py --gateway-name strands-web-search-gw + +# 2. Load credentials into your shell +source .env.web-search + +# 3. (Optional) Override the default model +export BEDROCK_MODEL_ID="us.anthropic.claude-sonnet-4-20250514-v1:0" + +# 4. Run the agent +python web_search_strands.py + +# Try a custom query +python web_search_strands.py --query "What are the latest AI announcements?" +python web_search_strands.py --query "Current price of Bitcoin" +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--query` | No | Research question (default: built-in demo queries) | + +## How It Works + +The script connects to the Gateway, creates an agent, and runs a research query: + +### Step 1: Connect to the Gateway + +The agent connects via MCP Streamable HTTP. Authentication is handled internally — an OAuth token is obtained from Cognito and attached to the transport. The Gateway returns the `WebSearch` tool with its input schema, and Strands registers it as an available tool for the LLM. + +```python +from utils.web_search_agent import create_agent, create_mcp_client + +mcp_client = create_mcp_client() +``` + +### Step 2: Create the Agent + +The `create_agent()` function discovers tools via `tools/list` and configures a Strands agent with a system prompt that instructs it to search and cite sources: + +```python +with mcp_client: + agent = create_agent(mcp_client) +``` + +### Step 3: Run the Agent Loop + +When you ask a question, the agent loop executes: + +1. Strands sends the query + tool schema to Claude Sonnet 4 +2. Claude decides to call `WebSearch` with a concise search query +3. Strands invokes the tool via MCP `tools/call` on the Gateway +4. The Gateway routes to the Web Search connector and returns results +5. Results are fed back to Claude as a tool result +6. Claude synthesizes a final answer with cited sources + +```python +result = agent("What is the latest version of Python?") +print(result.message) +``` + +## Files + +| File | Description | +|:-----|:------------| +| `setup_gateway.py` | Creates Gateway + Web Search target infrastructure | +| `web_search_strands.py` | Main demo script — Strands agent with web search | +| `cleanup.py` | Deletes all provisioned AWS resources | +| `../utils/web_search_agent.py` | Shared agent factory with MCP client setup | +| `../utils/gateway_auth.py` | OAuth token retrieval and transport creation | + +## Cleanup (Optional) + +When you're done remove all provisioned AWS resources: + +**1. Retrieve resource IDs** from the setup output (printed when you ran `setup_gateway.py`): + +``` +Gateway ID: +IAM Role: agentcore-web-search-gateway-role +Cognito Pool: +``` + +> **Tip:** If you no longer have the terminal output, the gateway ID is the subdomain prefix in your `AGENTCORE_GATEWAY_URL` (e.g., `gw-abc123` from `https://gw-abc123.gateway.bedrock-agentcore...`). The IAM role follows the pattern `agentcore--role`. + +**2. Run cleanup:** + +```bash +python cleanup.py --gateway-id --user-pool-id --role-name +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--gateway-id` | Yes | Gateway ID | +| `--user-pool-id` | Yes | Cognito User Pool ID | +| `--role-name` | Yes | IAM role name | diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/cleanup.py b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/cleanup.py new file mode 100644 index 000000000..4d231f659 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/cleanup.py @@ -0,0 +1,111 @@ +""" +Clean up all resources created by the Web Search Tool setup. + +Deletes in order: + 1. Gateway targets and Gateway + 2. Cognito User Pool (domain, clients, pool) + 3. IAM Role and inline policies + +Prerequisites: + pip install -r ../requirements.txt + AWS credentials with permissions to delete the resources. + +Usage: + python cleanup.py --gateway-id --user-pool-id --role-name + python cleanup.py --gateway-id gw-abc123 --user-pool-id us-east-1_AbCdEf --role-name agentcore-web-search-gateway-role +""" + +import argparse +import os +import time + +import boto3 + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + + +def delete_gateway(gateway_client, gateway_id): + """Delete all targets and the gateway itself.""" + print("\n[1/3] Deleting Gateway resources...") + try: + targets = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100) + for item in targets["items"]: + gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=item["targetId"]) + print(f" Deleted target: {item['name']}") + + time.sleep(10) + gateway_client.delete_gateway(gatewayIdentifier=gateway_id) + print(f" Deleted gateway: {gateway_id}") + except Exception as e: + print(f" Error deleting gateway: {e}") + + +def delete_cognito(cognito_client, user_pool_id): + """Delete the Cognito User Pool and its domain.""" + print("\n[2/3] Deleting Cognito resources...") + try: + domain = user_pool_id.replace("_", "").lower() + cognito_client.delete_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + cognito_client.delete_user_pool(UserPoolId=user_pool_id) + print(f" Deleted user pool: {user_pool_id}") + except Exception as e: + print(f" Error deleting Cognito: {e}") + + +def delete_iam_role(iam_client, role_name): + """Delete the IAM role and its inline policies.""" + print("\n[3/3] Deleting IAM resources...") + try: + policies = iam_client.list_role_policies(RoleName=role_name) + for policy_name in policies["PolicyNames"]: + iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + print(f" Deleted policy: {policy_name}") + + iam_client.delete_role(RoleName=role_name) + print(f" Deleted role: {role_name}") + except Exception as e: + print(f" Error deleting IAM role: {e}") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Clean up Web Search Tool Gateway resources") + parser.add_argument("--gateway-id", required=True, help="Gateway ID to delete") + parser.add_argument("--user-pool-id", required=True, help="Cognito User Pool ID to delete") + parser.add_argument("--role-name", required=True, help="IAM role name to delete") + parser.add_argument("--region", default=REGION, help="AWS region (default: us-east-1)") + return parser.parse_args() + + +def main(): + args = parse_args() + region = args.region + + print("=" * 60) + print("AgentCore web search tool — Resource Cleanup") + print("=" * 60) + print(f"\nRegion: {region}") + print(f"Gateway ID: {args.gateway_id}") + print(f"User Pool ID: {args.user_pool_id}") + print(f"Role Name: {args.role_name}") + + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + cognito_client = boto3.client("cognito-idp", region_name=region) + iam_client = boto3.client("iam") + + delete_gateway(gateway_client, args.gateway_id) + delete_cognito(cognito_client, args.user_pool_id) + delete_iam_role(iam_client, args.role_name) + + # Remove local credentials file + env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env.web-search") + if os.path.exists(env_file): + os.remove(env_file) + print(f"\n Deleted local credentials file: {env_file}") + + print("\n" + "=" * 60) + print("āœ… All resources cleaned up successfully!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/images/strands-web-search-architecture.png b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/images/strands-web-search-architecture.png new file mode 100644 index 000000000..5fc769924 Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/images/strands-web-search-architecture.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/setup_gateway.py b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/setup_gateway.py new file mode 100644 index 000000000..8824d5c10 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/setup_gateway.py @@ -0,0 +1,18 @@ +"""Set up AgentCore gateway with Web Search Tool — delegates to shared utility. +After running this script, load credentials with `source .env.web-search` to use +with the other web search examples. + +Usage: + python setup_gateway.py + python setup_gateway.py --gateway-name my-gateway + python setup_gateway.py --region us-east-1 +""" + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.gateway_setup import main + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/web_search_strands.py b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/web_search_strands.py new file mode 100644 index 000000000..9b39f33fb --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/02-strands-agent/web_search_strands.py @@ -0,0 +1,107 @@ +""" +Web Search with a Strands AI Agent. + +Demonstrates a Strands agent that uses the Web Search Tool via AgentCore +Gateway to answer real-time questions: + 1. Connect to the Gateway using MCP Streamable HTTP transport + 2. Discover the WebSearch tool via tools/list + 3. Create a Strands agent with the discovered tools + 4. Ask a question — the agent invokes WebSearch automatically + 5. Get a grounded response with cited sources + +Prerequisites: + pip install -r ../requirements.txt + Export environment variables from 01-setup-gateway/setup_gateway.py + Access to Claude Sonnet 4 in us-east-1 + +Environment variables required: + AGENTCORE_GATEWAY_URL — Gateway MCP endpoint + COGNITO_DOMAIN — Cognito domain prefix + COGNITO_CLIENT_ID — Cognito app client ID + COGNITO_CLIENT_SECRET — Cognito app client secret + COGNITO_SCOPE — OAuth scope string + +IAM permissions required: + bedrock:InvokeModel (for Claude Sonnet 4) + +Usage: + python web_search_strands.py + python web_search_strands.py --query "What are the latest AI announcements?" +""" + +import argparse +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.web_search_agent import create_agent, create_mcp_client + +# ── Configuration ───────────────────────────────────────────────────────────── + +DEFAULT_QUERIES = [ + "What is today's news around the world?", + "What are the latest developments in quantum computing?", +] + + +# ── Main ────────────────────────────────────────────────────────────────────── + + +def run_query(agent, query: str): + """Run a single query through the agent and print the response.""" + print(f"\n{'=' * 60}") + print(f"Query: {query}") + print("=" * 60) + + response = agent(query) + + print("\n[Agent Response]") + print("-" * 60) + if hasattr(response, "message"): + content = response.message.get("content", []) + for block in content: + if block.get("text"): + print(block["text"]) + else: + print(str(response)) + + +def parse_args(): + parser = argparse.ArgumentParser(description="Strands agent with Web Search Tool via AgentCore gateway") + parser.add_argument( + "--query", + default=None, + help="Custom query (runs default queries if omitted)", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + print("=" * 60) + print("AgentCore web search tool — Strands Agent") + print("=" * 60) + + mcp_client = create_mcp_client() + + with mcp_client: + # Discover tools + tools = mcp_client.list_tools_sync() + print(f"\nDiscovered {len(tools)} tool(s) from Gateway") + + # Create agent + agent = create_agent(mcp_client) + + # Run queries + queries = [args.query] if args.query else DEFAULT_QUERIES + for q in queries: + run_query(agent, q) + + print("\n" + "=" * 60) + print("Web Search with a Strands AI Agent Demo complete!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/README.md b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/README.md new file mode 100644 index 000000000..5a85c0dfb --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/README.md @@ -0,0 +1,122 @@ +# Web Search with a LangChain Agent + +## Overview + +This demo shows the same Web Search Tool integration using LangChain and LangGraph instead of Strands. It uses `langchain-mcp-adapters` to connect to the AgentCore gateway and `create_react_agent` from LangGraph for the agent loop. + +![Langchain web search Architecture](images/langchain-web-search-architecture.png) + +## Prerequisites + +- Python 3.10+ +- AWS account with Amazon Bedrock enabled in **us-east-1** +- AWS credentials with IAM, Cognito, and AgentCore gateway permissions +- Claude Sonnet 4 model access enabled in Bedrock + +```bash +pip install -r ../requirements.txt +``` + +## Quick Start + +```bash +# 1. Set up the Gateway (creates IAM role, Cognito, Gateway, Web Search target) +#python setup_gateway.py +python setup_gateway.py --gateway-name langchain-web-search-gw + +# 2. Load credentials into your shell +source .env.web-search + +# 3. (Optional) Override the default model +export BEDROCK_MODEL_ID="us.anthropic.claude-sonnet-4-20250514-v1:0" + +# 4. Run the agent +python web_search_langchain.py + +# Try a custom query +python web_search_langchain.py --query "Latest AWS announcements" +python web_search_langchain.py --query "Python 3.13 new features" +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--query` | No | Search query (default: built-in demo query) | + +## How It Works + +The script authenticates, discovers tools from the Gateway, and runs an agent loop: + +### Step 1: Connect to the Gateway via MCP + +An OAuth token is obtained from Cognito and passed to `MultiServerMCPClient`, which connects to the Gateway and converts MCP tools into LangChain-compatible objects: + +```python +from langchain_mcp_adapters.client import MultiServerMCPClient + +async with MultiServerMCPClient({ + "web-search": { + "transport": "streamable_http", + "url": gateway_url, + "headers": {"Authorization": f"Bearer {token}"}, + } +}) as client: + tools = client.get_tools() +``` + +### Step 2: Create the Agent + +The agent uses LangGraph's `create_react_agent` with `ChatBedrockConverse` as the LLM: + +```python +from langchain_aws import ChatBedrockConverse +from langgraph.prebuilt import create_react_agent + +model = ChatBedrockConverse( + model="us.anthropic.claude-sonnet-4-6", + region_name="us-east-1", +) +agent = create_react_agent(model, tools=tools) +``` + +### Step 3: Run the Agent Loop + +LangChain's MCP adapter uses async I/O. The agent invokes `WebSearch` automatically when it determines a search is needed, then synthesizes a cited response: + +```python +result = await agent.ainvoke({"messages": [{"role": "user", "content": query}]}) +``` + +## Files + +| File | Description | +|:-----|:------------| +| `setup_gateway.py` | Creates Gateway + Web Search target infrastructure | +| `web_search_langchain.py` | Main demo script — LangChain agent with web search | +| `cleanup.py` | Deletes all provisioned AWS resources | +| `../utils/gateway_auth.py` | OAuth token retrieval (shared with other demos) | + +## Cleanup (Optional) + +When you're done remove all provisioned AWS resources: + +**1. Retrieve resource IDs** from the setup output (printed when you ran `setup_gateway.py`): + +``` +Gateway ID: +IAM Role: agentcore-web-search-gateway-role +Cognito Pool: +``` + +> **Tip:** If you no longer have the terminal output, the gateway ID is the subdomain prefix in your `AGENTCORE_GATEWAY_URL` (e.g., `gw-abc123` from `https://gw-abc123.gateway.bedrock-agentcore...`). The IAM role follows the pattern `agentcore--role`. + +**2. Run cleanup:** + +```bash +python cleanup.py --gateway-id --user-pool-id --role-name +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--gateway-id` | Yes | Gateway ID | +| `--user-pool-id` | Yes | Cognito User Pool ID | +| `--role-name` | Yes | IAM role name | diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/cleanup.py b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/cleanup.py new file mode 100644 index 000000000..4d231f659 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/cleanup.py @@ -0,0 +1,111 @@ +""" +Clean up all resources created by the Web Search Tool setup. + +Deletes in order: + 1. Gateway targets and Gateway + 2. Cognito User Pool (domain, clients, pool) + 3. IAM Role and inline policies + +Prerequisites: + pip install -r ../requirements.txt + AWS credentials with permissions to delete the resources. + +Usage: + python cleanup.py --gateway-id --user-pool-id --role-name + python cleanup.py --gateway-id gw-abc123 --user-pool-id us-east-1_AbCdEf --role-name agentcore-web-search-gateway-role +""" + +import argparse +import os +import time + +import boto3 + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + + +def delete_gateway(gateway_client, gateway_id): + """Delete all targets and the gateway itself.""" + print("\n[1/3] Deleting Gateway resources...") + try: + targets = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100) + for item in targets["items"]: + gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=item["targetId"]) + print(f" Deleted target: {item['name']}") + + time.sleep(10) + gateway_client.delete_gateway(gatewayIdentifier=gateway_id) + print(f" Deleted gateway: {gateway_id}") + except Exception as e: + print(f" Error deleting gateway: {e}") + + +def delete_cognito(cognito_client, user_pool_id): + """Delete the Cognito User Pool and its domain.""" + print("\n[2/3] Deleting Cognito resources...") + try: + domain = user_pool_id.replace("_", "").lower() + cognito_client.delete_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + cognito_client.delete_user_pool(UserPoolId=user_pool_id) + print(f" Deleted user pool: {user_pool_id}") + except Exception as e: + print(f" Error deleting Cognito: {e}") + + +def delete_iam_role(iam_client, role_name): + """Delete the IAM role and its inline policies.""" + print("\n[3/3] Deleting IAM resources...") + try: + policies = iam_client.list_role_policies(RoleName=role_name) + for policy_name in policies["PolicyNames"]: + iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + print(f" Deleted policy: {policy_name}") + + iam_client.delete_role(RoleName=role_name) + print(f" Deleted role: {role_name}") + except Exception as e: + print(f" Error deleting IAM role: {e}") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Clean up Web Search Tool Gateway resources") + parser.add_argument("--gateway-id", required=True, help="Gateway ID to delete") + parser.add_argument("--user-pool-id", required=True, help="Cognito User Pool ID to delete") + parser.add_argument("--role-name", required=True, help="IAM role name to delete") + parser.add_argument("--region", default=REGION, help="AWS region (default: us-east-1)") + return parser.parse_args() + + +def main(): + args = parse_args() + region = args.region + + print("=" * 60) + print("AgentCore web search tool — Resource Cleanup") + print("=" * 60) + print(f"\nRegion: {region}") + print(f"Gateway ID: {args.gateway_id}") + print(f"User Pool ID: {args.user_pool_id}") + print(f"Role Name: {args.role_name}") + + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + cognito_client = boto3.client("cognito-idp", region_name=region) + iam_client = boto3.client("iam") + + delete_gateway(gateway_client, args.gateway_id) + delete_cognito(cognito_client, args.user_pool_id) + delete_iam_role(iam_client, args.role_name) + + # Remove local credentials file + env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env.web-search") + if os.path.exists(env_file): + os.remove(env_file) + print(f"\n Deleted local credentials file: {env_file}") + + print("\n" + "=" * 60) + print("āœ… All resources cleaned up successfully!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/images/langchain-web-search-architecture.png b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/images/langchain-web-search-architecture.png new file mode 100644 index 000000000..4946d4360 Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/images/langchain-web-search-architecture.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/setup_gateway.py b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/setup_gateway.py new file mode 100644 index 000000000..2b66af2f7 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/setup_gateway.py @@ -0,0 +1,16 @@ +"""Set up AgentCore gateway with Web Search Tool — delegates to shared utility. + +Usage: +python setup_gateway.py +python setup_gateway.py --gateway-name my-gateway +python setup_gateway.py --region us-east-1 +""" + +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.gateway_setup import main + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/web_search_langchain.py b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/web_search_langchain.py new file mode 100644 index 000000000..9876778fe --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/03-langchain-agent/web_search_langchain.py @@ -0,0 +1,128 @@ +""" +Web Search with a LangChain Agent. + +Demonstrates a LangChain agent that uses the Web Search Tool via AgentCore +Gateway to answer real-time questions: + 1. Connect to the Gateway using langchain-mcp-adapters MultiServerMCPClient + 2. Discover tools from the Gateway + 3. Create a LangChain agent with ChatBedrockConverse + 4. Ask a question — the agent invokes WebSearch automatically + 5. Get a grounded response with cited sources + +Prerequisites: + pip install -r ../requirements.txt + Export environment variables from 01-setup-gateway/setup_gateway.py + Access to Claude Sonnet 4 in us-east-1 + +Environment variables required: + AGENTCORE_GATEWAY_URL — Gateway MCP endpoint + COGNITO_DOMAIN — Cognito domain prefix + COGNITO_CLIENT_ID — Cognito app client ID + COGNITO_CLIENT_SECRET — Cognito app client secret + COGNITO_SCOPE — OAuth scope string + BEDROCK_MODEL_ID — (optional) Bedrock inference profile ID or ARN; + defaults to us.anthropic.claude-sonnet-4-5-20250514-v1:0 + +IAM permissions required: + bedrock:InvokeModel (for Claude Sonnet 4) + +Usage: + python web_search_langchain.py + python web_search_langchain.py --query "Latest AWS announcements" +""" + +import argparse +import asyncio +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from utils.gateway_auth import get_oauth_token + +from langchain_aws import ChatBedrockConverse +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain.agents import create_agent + +# ── Configuration ───────────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") +MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "global.anthropic.claude-sonnet-4-6") +GATEWAY_URL = os.getenv("AGENTCORE_GATEWAY_URL", "") + +DEFAULT_QUERY = "What is today's news around the world?" + + +# ── Agent ───────────────────────────────────────────────────────────────────── + + +async def run_agent(query: str): + """Create and run a LangChain agent with Web Search tools.""" + # Get OAuth token for Gateway authentication + token = get_oauth_token() + + # Configure the LLM + model = ChatBedrockConverse( + model=MODEL_ID, + region_name=REGION, + temperature=0.7, + max_tokens=1024, + ) + + # Connect to the Gateway as an MCP client + client = MultiServerMCPClient( + { + "web-search": { + "transport": "streamable_http", + "url": GATEWAY_URL, + "headers": {"Authorization": f"Bearer {token}"}, + } + } + ) + tools = await client.get_tools() + print(f" Discovered {len(tools)} tool(s)") + + # Create and run the agent + # agent = create_react_agent(model, tools=tools) + agent = create_agent(model, tools=tools) + result = await agent.ainvoke({"messages": [{"role": "user", "content": query}]}) + + # Print the final response + print("\n[Agent Response]") + print("-" * 60) + final_message = result["messages"][-1] + if hasattr(final_message, "content"): + print(final_message.content) + else: + print(str(final_message)) + + +# ── Main ────────────────────────────────────────────────────────────────────── + + +def parse_args(): + parser = argparse.ArgumentParser(description="LangChain agent with Web Search Tool via AgentCore gateway") + parser.add_argument( + "--query", + default=DEFAULT_QUERY, + help=f"Search query (default: '{DEFAULT_QUERY}')", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + print("=" * 60) + print("AgentCore web search tool — LangChain Agent") + print("=" * 60) + print(f"\nQuery: {args.query}\n") + + asyncio.run(run_agent(args.query)) + + print("\n" + "=" * 60) + print("Web Search with a LangChain Agent Demo Complete.!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/README.md b/01-features/03-connect-your-agent-to-anything/03-web-search/README.md new file mode 100644 index 000000000..ba817c7df --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/README.md @@ -0,0 +1,111 @@ +# AgentCore web search tool + +![Web Search Tool Architecture](images/tutorial-architecture.png) + +[Amazon Bedrock AgentCore web search tool](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html) exposes web search as a fully managed, MCP-compliant tool through Amazon Bedrock AgentCore gateway. Your agents discover and invoke it using the standard Model Context Protocol — no custom integrations, no infrastructure to manage. + +> šŸ”’ **Search Privacy**: The Web Search Tool queries an AWS-maintained search index. Queries do not route to any third-party search engines or external providers. + +## Key Capabilities + +- **Real-time information access** — Retrieve current web results with titles, URLs, snippets, and publication dates +- **Zero infrastructure management** — No search APIs to provision or scaling to configure +- **Framework agnostic** — Works with Strands Agents, LangChain, LangGraph, CrewAI, or any MCP-compatible client +- **Structured results** — Results returned in both MCP `content` (text) and `structuredContent` (typed JSON) formats +- **MCP-native** — Standard `tools/list` and `tools/call` protocol; no custom SDK required + +## Tutorials + +| Section | Description | +|:--------|:------------| +| [01-raw-mcp/](01-raw-mcp/) | Direct MCP tool discovery and invocation without an agent framework | +| [02-strands-agent/](02-strands-agent/) | Full agent loop with Strands Agents — automatic tool selection and cited responses | +| [03-langchain-agent/](03-langchain-agent/) | Full agent loop using LangChain + LangGraph with MCP adapter | + +## Prerequisites + +- AWS account with Amazon Bedrock enabled in **us-east-1** +- Python 3.10+ +- AWS credentials with IAM, Cognito, and AgentCore gateway permissions +- Model access enabled for Claude Sonnet 4 (cross-region inference profile) + +```bash +pip install -r requirements.txt +``` + +> **Note:** The Web Search Tool connector is available in [supported regions](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html#gateway-target-connector-web-search-tool-availability). Set your region accordingly when running the setup script. + +## Core Concepts + +### Gateway + Connector Architecture + +The Web Search Tool uses the **connector** target type — a fully AWS-managed integration that requires no schema, no endpoint configuration, and no outbound credential setup. You specify `connectorId: "web-search"` and the Gateway handles everything else. + +![Web search with Amazon Bedrock AgentCore gateway](images/agentcore-web-search-architecture.png) + +### How It Works + +1. **Gateway setup** — Create an AgentCore gateway and add a Web Search Tool target using `connectorId: "web-search"` +2. **Tool discovery** — Your agent calls `tools/list` on the Gateway endpoint and discovers `WebSearch` with its input schema +3. **Search invocation** — Your agent calls `tools/call` with a natural language query (up to 200 characters) +4. **Structured results** — The tool returns results with text snippets, URLs, titles, and publication dates +5. **Grounded response** — Your agent uses the results to compose a response with cited sources + +### Response Format + +```json +{ + "results": [ + { + "text": "Snippet from the web page...", + "url": "https://example.com/article", + "title": "Article Title", + "publishedDate": "2026-05-28" + } + ] +} +``` + +| Field | Type | Required | Description | +|:------|:-----|:---------|:------------| +| `text` | string | Yes | Text content or snippet of the search result | +| `url` | string | No | URL of the source webpage | +| `title` | string | No | Title of the source webpage | +| `publishedDate` | string | No | Publication date of the result | + +> **Note:** Queries longer than 200 characters may not return results. Keep queries concise. + +### Authentication + +- **Inbound**: Amazon Cognito with `client_credentials` OAuth flow (can use other OAuth providers) +- **Outbound**: Automatic — the Gateway uses its own IAM role to authenticate to the Web Search backend + +## End-to-End Example + +```bash +pip install -r requirements.txt + +# Create Gateway and Web Search target (run from any sample folder) +python 01-raw-mcp/setup_gateway.py + +# Load credentials +source .env.web-search + +# Verify with raw MCP calls +python 01-raw-mcp/raw_mcp_call.py + +# Run with Strands agent +python 02-strands-agent/web_search_strands.py + +# Run with LangChain agent +python 03-langchain-agent/web_search_langchain.py + +# Cleanup when done (run from any sample folder) +python 01-raw-mcp/cleanup.py --gateway-id --user-pool-id --role-name +``` + +## Documentation + +- [Web Search Tool connector](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html) +- [AgentCore gateway](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html) +- [Supported models for cross-region inference](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html) diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/images/agentcore-web-search-architecture.png b/01-features/03-connect-your-agent-to-anything/03-web-search/images/agentcore-web-search-architecture.png new file mode 100644 index 000000000..75e53cb8f Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/images/agentcore-web-search-architecture.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/images/inbound-and-outbound-auth.png b/01-features/03-connect-your-agent-to-anything/03-web-search/images/inbound-and-outbound-auth.png new file mode 100644 index 000000000..c833f09a5 Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/images/inbound-and-outbound-auth.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/images/tutorial-architecture.png b/01-features/03-connect-your-agent-to-anything/03-web-search/images/tutorial-architecture.png new file mode 100644 index 000000000..36eb0484d Binary files /dev/null and b/01-features/03-connect-your-agent-to-anything/03-web-search/images/tutorial-architecture.png differ diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/requirements.txt b/01-features/03-connect-your-agent-to-anything/03-web-search/requirements.txt new file mode 100644 index 000000000..d8c552bb4 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/requirements.txt @@ -0,0 +1,10 @@ +boto3 +botocore +strands-agents>=0.1.8 +mcp +requests +langchain>=1.3.4 +langchain-core>=1.4.0 +langchain-mcp-adapters>=0.2.2 +langchain-aws>=1.5.0 +langgraph>=1.2.4 diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/utils/__init__.py b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_auth.py b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_auth.py new file mode 100644 index 000000000..184648023 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_auth.py @@ -0,0 +1,103 @@ +""" +Shared Gateway authentication utilities for Web Search Tool demos. + +Provides helpers for: + - Obtaining OAuth tokens from Cognito (client_credentials flow) + - Creating MCP Streamable HTTP transports authenticated against the Gateway + +All demos in this folder share these utilities to avoid duplicating +auth boilerplate. +""" + +import os + +import requests +from mcp.client.streamable_http import streamablehttp_client + + +# ── Configuration ───────────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") +GATEWAY_URL = os.getenv("AGENTCORE_GATEWAY_URL", "") +COGNITO_DOMAIN = os.getenv("COGNITO_DOMAIN", "") +COGNITO_CLIENT_ID = os.getenv("COGNITO_CLIENT_ID", "") +COGNITO_CLIENT_SECRET = os.getenv("COGNITO_CLIENT_SECRET", "") +COGNITO_SCOPE = os.getenv("COGNITO_SCOPE", "") + + +# ── Token retrieval ─────────────────────────────────────────────────────────── + + +def get_oauth_token( + domain: str = "", + client_id: str = "", + client_secret: str = "", + scope: str = "", + region: str = "", +) -> str: + """Retrieve a fresh OAuth token from Cognito using client_credentials flow. + + Args: + domain: Cognito domain prefix (e.g., "us-east-1abcdef12"). + client_id: Cognito app client ID. + client_secret: Cognito app client secret. + scope: OAuth scope string (e.g., "agentcore-websearch/invoke"). + region: AWS region for the Cognito endpoint. + + Returns: + Access token string. + """ + domain = domain or COGNITO_DOMAIN + client_id = client_id or COGNITO_CLIENT_ID + client_secret = client_secret or COGNITO_CLIENT_SECRET + scope = scope or COGNITO_SCOPE + region = region or REGION + + if not all([domain, client_id, client_secret]): + raise ValueError( + "Cognito credentials not configured. Set COGNITO_DOMAIN, " + "COGNITO_CLIENT_ID, and COGNITO_CLIENT_SECRET environment variables." + ) + + url = f"https://{domain}.auth.{region}.amazoncognito.com/oauth2/token" + response = requests.post( + url, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "scope": scope, + }, + timeout=10, + ) + response.raise_for_status() + return response.json()["access_token"] + + +# ── MCP transport factory ───────────────────────────────────────────────────── + + +def create_streamable_http_transport(gateway_url: str = "", **token_kwargs): + """Create an MCP Streamable HTTP transport authenticated with a Bearer token. + + Args: + gateway_url: The AgentCore gateway MCP endpoint URL. + **token_kwargs: Passed to get_oauth_token() for credential overrides. + + Returns: + A callable suitable for MCPClient initialization. + """ + gateway_url = gateway_url or GATEWAY_URL + if not gateway_url: + raise ValueError("Gateway URL not configured. Set AGENTCORE_GATEWAY_URL environment variable.") + + token = get_oauth_token(**token_kwargs) + + def _transport(): + return streamablehttp_client( + gateway_url, + headers={"Authorization": f"Bearer {token}"}, + ) + + return _transport diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_setup.py b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_setup.py new file mode 100644 index 000000000..da67202a3 --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/gateway_setup.py @@ -0,0 +1,346 @@ +""" +Set up AgentCore gateway with Web Search Tool Target. + +Creates all required infrastructure for the Web Search Tool: + 1. IAM service role for the Gateway (with InvokeWebSearch permission) + 2. Cognito User Pool with client_credentials OAuth flow + 3. AgentCore gateway with MCP protocol and JWT authorization + 4. Web Search Tool connector target + +After running this script, load credentials with `source .env.web-search` to use +with the other demos in this folder. + +Prerequisites: + pip install -r ../requirements.txt + AWS credentials with permissions to create IAM roles, Cognito pools, + and AgentCore gateway. + +IAM permissions required: + iam:CreateRole, iam:PutRolePolicy, iam:GetRole + cognito-idp:CreateUserPool, cognito-idp:CreateUserPoolDomain + cognito-idp:CreateResourceServer, cognito-idp:CreateUserPoolClient + cognito-idp:ListUserPools, cognito-idp:ListUserPoolClients + cognito-idp:DescribeUserPoolClient, cognito-idp:DescribeResourceServer + bedrock-agentcore:CreateGateway, bedrock-agentcore:GetGateway + bedrock-agentcore:CreateGatewayTarget, bedrock-agentcore:ListGatewayTargets + +Usage: + python setup_gateway.py + python setup_gateway.py --gateway-name my-gateway + python setup_gateway.py --region us-east-1 +""" + +import argparse +import json +import os +import sys +import time + + +import boto3 + +# ── Configuration ───────────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + + +def wait_for_status(client, gateway_id, target_status="READY", max_wait=150): + """Poll gateway status until it reaches target_status.""" + for _ in range(max_wait // 5): + status = client.get_gateway(gatewayIdentifier=gateway_id)["status"] + if status == target_status: + return status + time.sleep(5) + return status + + +def wait_for_targets(client, gateway_id, max_wait=150): + """Poll until all gateway targets are READY.""" + for _ in range(max_wait // 5): + targets = client.list_gateway_targets(gatewayIdentifier=gateway_id) + if all(item["status"] == "READY" for item in targets["items"]): + return True + time.sleep(5) + return False + + +# ── Setup Steps ─────────────────────────────────────────────────────────────── + + +def create_gateway_role(iam_client, role_name, account_id, region): + """Create the IAM service role for the Gateway.""" + assume_role_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "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:{region}:{account_id}:*"}, + }, + } + ], + } + + try: + role_response = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=json.dumps(assume_role_policy), + ) + print(f" Created role: {role_name}") + time.sleep(10) # Wait for IAM propagation + except iam_client.exceptions.EntityAlreadyExistsException: + role_response = iam_client.get_role(RoleName=role_name) + print(f" Role already exists: {role_name}") + + role_arn = role_response["Role"]["Arn"] + + # Attach permissions + iam_client.put_role_policy( + RoleName=role_name, + PolicyName="WebSearchGatewayPolicy", + PolicyDocument=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "InvokeGateway", + "Effect": "Allow", + "Action": "bedrock-agentcore:InvokeGateway", + "Resource": f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*", + }, + { + "Sid": "InvokeWebSearch", + "Effect": "Allow", + "Action": "bedrock-agentcore:InvokeWebSearch", + "Resource": f"arn:aws:bedrock-agentcore:{region}:aws:tool/web-search.v1", + }, + ], + } + ), + ) + print(" Permissions attached āœ“") + return role_arn + + +def create_cognito_resources(cognito_client, region): + """Create Cognito User Pool, resource server, and M2M client.""" + pool_name = "agentcore-websearch-pool" + resource_server_id = "agentcore-websearch" + scopes = [{"ScopeName": "invoke", "ScopeDescription": "Invoke gateway"}] + scope_names = [f"{resource_server_id}/{s['ScopeName']}" for s in scopes] + + # Find or create user pool + user_pool_id = None + for pool in cognito_client.list_user_pools(MaxResults=60)["UserPools"]: + if pool["Name"] == pool_name: + user_pool_id = pool["Id"] + break + + if user_pool_id is None: + create_resp = cognito_client.create_user_pool(PoolName=pool_name) + user_pool_id = create_resp["UserPool"]["Id"] + domain = user_pool_id.replace("_", "").lower() + cognito_client.create_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + print(f" Created user pool: {user_pool_id}") + else: + print(f" User pool exists: {user_pool_id}") + + # Create resource server + try: + cognito_client.describe_resource_server(UserPoolId=user_pool_id, Identifier=resource_server_id) + except cognito_client.exceptions.ResourceNotFoundException: + cognito_client.create_resource_server( + UserPoolId=user_pool_id, + Identifier=resource_server_id, + Name="WebSearch Gateway Resource Server", + Scopes=scopes, + ) + print(" Resource server ensured āœ“") + + # Find or create M2M client + client_id, client_secret = None, None + for client in cognito_client.list_user_pool_clients(UserPoolId=user_pool_id, MaxResults=60)["UserPoolClients"]: + if client["ClientName"] == "agentcore-websearch-client": + desc = cognito_client.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"]) + client_id = client["ClientId"] + client_secret = desc["UserPoolClient"]["ClientSecret"] + break + + if client_id is None: + created = cognito_client.create_user_pool_client( + UserPoolId=user_pool_id, + ClientName="agentcore-websearch-client", + GenerateSecret=True, + AllowedOAuthFlows=["client_credentials"], + AllowedOAuthScopes=scope_names, + AllowedOAuthFlowsUserPoolClient=True, + SupportedIdentityProviders=["COGNITO"], + ExplicitAuthFlows=["ALLOW_REFRESH_TOKEN_AUTH"], + ) + client_id = created["UserPoolClient"]["ClientId"] + client_secret = created["UserPoolClient"]["ClientSecret"] + print(f" Created client: {client_id}") + else: + print(f" Client exists: {client_id}") + + domain = user_pool_id.replace("_", "").lower() + discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration" + scope_string = " ".join(scope_names) + + return { + "user_pool_id": user_pool_id, + "client_id": client_id, + "client_secret": client_secret, + "discovery_url": discovery_url, + "domain": domain, + "scope": scope_string, + } + + +def create_gateway(gateway_client, name, role_arn, cognito_config): + """Create the AgentCore gateway with MCP protocol.""" + create_response = gateway_client.create_gateway( + name=name, + roleArn=role_arn, + protocolType="MCP", + protocolConfiguration={"mcp": {"supportedVersions": ["2025-03-26"], "searchType": "SEMANTIC"}}, + authorizerType="CUSTOM_JWT", + authorizerConfiguration={ + "customJWTAuthorizer": { + "allowedClients": [cognito_config["client_id"]], + "discoveryUrl": cognito_config["discovery_url"], + } + }, + description="AgentCore gateway with Web Search Tool", + ) + + gateway_id = create_response["gatewayId"] + gateway_url = create_response["gatewayUrl"] + print(f" Gateway ID: {gateway_id}") + print(f" Gateway URL: {gateway_url}") + + status = wait_for_status(gateway_client, gateway_id) + print(f" Gateway status: {status}") + + return gateway_id, gateway_url + + +def create_web_search_target(gateway_client, gateway_id): + """Create the Web Search Tool connector target.""" + target_response = gateway_client.create_gateway_target( + name="web-search-tool", + gatewayIdentifier=gateway_id, + targetConfiguration={ + "mcp": { + "connector": { + "source": {"connectorId": "web-search"}, + "configurations": [{"name": "WebSearch", "parameterValues": {}}], + } + } + }, + credentialProviderConfigurations=[{"credentialProviderType": "GATEWAY_IAM_ROLE"}], + ) + + target_id = target_response["targetId"] + print(f" Target ID: {target_id}") + + if wait_for_targets(gateway_client, gateway_id): + print(" Target status: READY āœ“") + else: + print(" WARNING: Target did not reach READY state within timeout") + + return target_id + + +# ── Main ────────────────────────────────────────────────────────────────────── + + +def parse_args(): + parser = argparse.ArgumentParser(description="Set up AgentCore gateway with Web Search Tool target") + parser.add_argument( + "--gateway-name", + default="web-search-gateway", + help="Name for the Gateway (default: web-search-gateway)", + ) + parser.add_argument( + "--region", + default=REGION, + help="AWS region (default: us-east-1)", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + region = args.region + + print("=" * 60) + print("AgentCore web search tool — Gateway Setup") + print("=" * 60) + + # Get account ID + sts_client = boto3.client("sts", region_name=region) + account_id = sts_client.get_caller_identity()["Account"] + print(f"\nAccount: {account_id}") + print(f"Region: {region}") + + # Step 1: IAM Role + print("\n[1/4] Creating Gateway service role...") + iam_client = boto3.client("iam") + role_name = f"agentcore-{args.gateway_name}-role" + role_arn = create_gateway_role(iam_client, role_name, account_id, region) + + # Step 2: Cognito + print("\n[2/4] Setting up Cognito authentication...") + cognito_client = boto3.client("cognito-idp", region_name=region) + cognito_config = create_cognito_resources(cognito_client, region) + + # Step 3: Gateway + print("\n[3/4] Creating AgentCore gateway...") + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + gateway_id, gateway_url = create_gateway(gateway_client, args.gateway_name, role_arn, cognito_config) + + # Step 4: Web Search Target + print("\n[4/4] Creating Web Search Tool target...") + create_web_search_target(gateway_client, gateway_id) + + # Print environment variables for other demos + print("\n" + "=" * 60) + print("SETUP COMPLETE") + print("=" * 60) + + # Write credentials to a local .env file next to the script that invoked setup + caller_dir = os.path.dirname(os.path.abspath(sys.argv[0])) if sys.argv[0] else os.getcwd() + env_file = os.path.join(caller_dir, ".env.web-search") + # START nosec - intentional for local development workflow + with open(env_file, "w") as f: + f.write(f'export AGENTCORE_GATEWAY_URL="{gateway_url}"\n') + f.write(f'export COGNITO_DOMAIN="{cognito_config["domain"]}"\n') + f.write(f'export COGNITO_CLIENT_ID="{cognito_config["client_id"]}"\n') + f.write(f'export COGNITO_CLIENT_SECRET="{cognito_config["client_secret"]}"\n') # noqa: E501 + f.write(f'export COGNITO_SCOPE="{cognito_config["scope"]}"\n') + f.write(f'export AWS_DEFAULT_REGION="{region}"\n') + f.write(f'export GATEWAY_ID="{gateway_id}"\n') + f.write(f'export USER_POOL_ID="{cognito_config["user_pool_id"]}"\n') + f.write(f'export ROLE_NAME="{role_name}"\n') + f.write("# Cleanup resource IDs\n") + # END nosec - intentional for local development workflow + + print(f"\nāœ… Credentials written to: {env_file}") + print(" Load them with: source .env.web-search\n") + print(f" Gateway URL: {gateway_url}") + print(f" Gateway ID: {gateway_id} (for cleanup)") + print(f" IAM Role: {role_name}") + print(f" Cognito Pool: {cognito_config['user_pool_id']}") + print(f"\nāš ļø Keep {env_file} secure — it contains your client secret.") + print(" Add it to .gitignore to avoid committing it.") + + +if __name__ == "__main__": + main() diff --git a/01-features/03-connect-your-agent-to-anything/03-web-search/utils/web_search_agent.py b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/web_search_agent.py new file mode 100644 index 000000000..377967e9a --- /dev/null +++ b/01-features/03-connect-your-agent-to-anything/03-web-search/utils/web_search_agent.py @@ -0,0 +1,83 @@ +""" +Shared Strands agent using AgentCore web search tool via Gateway. + +Used as the common demo agent across web search sub-demos: + - 03-strands-agent/web_search_strands.py + +The agent connects to an AgentCore gateway that exposes the Web Search Tool +as an MCP-compliant connector target. Tools are discovered dynamically via +the MCP tools/list endpoint. + +Environment variables: + BEDROCK_MODEL_ID — (optional) Bedrock inference profile ID or ARN; + must be a cross-region inference profile (e.g. us.*, eu.*, ap.*) + or an inference profile ARN — on-demand model IDs are not supported. + Defaults to us.anthropic.claude-sonnet-4-5-20250514-v1:0 + AWS_DEFAULT_REGION — AWS region (default: us-east-1) +""" + +import os + +from strands import Agent +from strands.models import BedrockModel +from strands.tools.mcp.mcp_client import MCPClient + +from utils.gateway_auth import create_streamable_http_transport + +# ── Configuration ───────────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") +MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "global.anthropic.claude-sonnet-4-6") + +SYSTEM_PROMPT = """You are a helpful research assistant with access to real-time web search. + +PRINCIPLES: +- Use the WebSearch tool to find current information when answering questions +- Always cite your sources with URLs when providing information from search results +- If search results are insufficient, say so rather than guessing +- Keep queries concise (under 200 characters) for best results +- Synthesize information from multiple results when possible + +RESPONSE FORMAT: +- Provide clear, well-structured answers +- Include source URLs for verification +- Note the publication date of sources when available +""" + + +# ── Factory ──────────────────────────────────────────────────────────────────── + + +def create_mcp_client(gateway_url: str = "", **token_kwargs) -> MCPClient: + """Create an MCPClient connected to the AgentCore gateway. + + Args: + gateway_url: The Gateway MCP endpoint URL. + **token_kwargs: Passed to gateway_auth for credential overrides. + + Returns: + MCPClient instance (must be used as a context manager). + """ + transport_factory = create_streamable_http_transport(gateway_url=gateway_url, **token_kwargs) + return MCPClient(transport_factory) + + +def create_agent(mcp_client: MCPClient) -> Agent: + """Create a Strands agent with Web Search tools from the Gateway. + + The mcp_client must already be entered as a context manager (i.e., + call this inside a `with mcp_client:` block). + + Args: + mcp_client: An active MCPClient connected to the Gateway. + + Returns: + Strands Agent configured with discovered tools. + """ + tools = mcp_client.list_tools_sync() + model = BedrockModel(model_id=MODEL_ID, region_name=REGION) + return Agent( + model=model, + tools=tools, + system_prompt=SYSTEM_PROMPT, + ) diff --git a/01-features/03-connect-your-agent-to-anything/README.md b/01-features/03-connect-your-agent-to-anything/README.md index ac9a9f94f..a9a6e4ee7 100644 --- a/01-features/03-connect-your-agent-to-anything/README.md +++ b/01-features/03-connect-your-agent-to-anything/README.md @@ -1,6 +1,6 @@ # Connect Your Agent to Anything -Give your agents access to powerful built-in tool environments — sandboxed code execution and headless browser automation — managed and scaled by Amazon Bedrock AgentCore. +Give your agents access to powerful built-in tool environments — sandboxed code execution, headless browser automation, and real-time web search — managed and scaled by Amazon Bedrock AgentCore. ## Top-level layout @@ -8,10 +8,11 @@ Give your agents access to powerful built-in tool environments — sandboxed cod |:-------|:--------------| | [`01-code-interpreter/`](./01-code-interpreter/) | Sandboxed Python execution environment — run code, execute shell commands, upload and read files, use the AWS CLI, all in an isolated per-session sandbox | | [`02-browser/`](./02-browser/) | Fully managed headless Chromium browser — drive it with Nova Act, Browser-Use, Strands, or raw Playwright via the Chrome DevTools Protocol | +| [`03-web-search/`](./03-web-search/) | Real-time web search as an MCP-compliant tool — ground your agents in current information via AgentCore gateway with zero infrastructure to manage | ## How these tools work -Both tools follow the same pattern: AgentCore provisions an isolated sandbox session on demand, your agent calls tool APIs within that session, and the session terminates when you stop it. No infrastructure to manage. +Code Interpreter and Browser follow the same pattern: AgentCore provisions an isolated sandbox session on demand, your agent calls tool APIs within that session, and the session terminates when you stop it. Web Search uses a different pattern — it's exposed as an MCP-compliant connector through AgentCore gateway, so your agent discovers and invokes it via standard MCP protocol calls. All three require no infrastructure to manage. ### Code Interpreter @@ -44,6 +45,24 @@ with browser_session("us-west-2") as client: # Pass ws_url + headers to Nova Act, Browser-Use, Playwright, or Strands ``` +### Web Search Tool + +- **What it is**: A fully managed web search connector exposed through AgentCore gateway via MCP +- **Use it for**: Agents that need real-time information — current events, latest releases, fact-checking, competitive intelligence +- **Entry point**: Create a Gateway with `connectorId: "web-search"`, then connect any MCP client + +```python +from strands.tools.mcp.mcp_client import MCPClient +from mcp.client.streamable_http import streamablehttp_client + +transport = lambda: streamablehttp_client(gateway_url, headers={"Authorization": f"Bearer {token}"}) +mcp_client = MCPClient(transport) + +with mcp_client: + tools = mcp_client.list_tools_sync() # Discovers WebSearch + result = mcp_client.call_tool_sync("demo", "WebSearch", {"query": "latest AI news"}) +``` + ## Quick Start ```bash @@ -57,10 +76,18 @@ playwright install chromium python 02-browser/01-nova-act/getting_started.py \ --nova-act-key $NOVA_ACT_API_KEY \ --prompt "Search Amazon for MacBooks" + +# Web Search Tool +pip install -r 03-web-search/requirements.txt +python 03-web-search/01-raw-mcp/setup_gateway.py +# Load the credentials written by setup: +source .env.web-search +python 03-web-search/03-strands-agent/web_search_strands.py ``` ## Resources - [Code Interpreter — Developer Guide](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-overview.html) - [Browser Tool — Developer Guide](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool-overview.html) +- [AgentCore gateway — Developer Guide](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html) - [boto3 Data Plane Reference (`bedrock-agentcore`)](https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-agentcore.html) diff --git a/01-features/README.md b/01-features/README.md index b19e3290d..2ab63944f 100644 --- a/01-features/README.md +++ b/01-features/README.md @@ -21,6 +21,7 @@ AgentCore services work together or independently with any open-source framework | **payments** | Microtransaction payments for agents via the x402 protocol — wallet integration, configurable spending limits, and end-to-end observability. | | **code interpreter** | Isolated sandbox for agents to execute Python, JavaScript, and TypeScript code. | | **browser** | Managed cloud browser for agents to interact with web applications, navigate sites, fill forms, and extract information. | +| **web search** | Real-time web search as a managed MCP connector through AgentCore gateway. Agents discover and invoke it via standard MCP protocol — no search APIs to provision. | | **harness** | Serverless agent orchestration layer — model, tools, system prompt, and context management in a single API call, without managing a runtime. | ![AgentCore map](../00-getting-started/images/agentcore-map.png) @@ -78,7 +79,7 @@ data = boto3.client('bedrock-agentcore', region_name='us-west-2') |:--|:-------|:--------------| | 01 | [harness](01-harness/) | Serverless agent orchestration environment — model, tools, sandbox, and session management in a single API call | | 02 | [host your agent](02-host-your-agent/) | Deploy agents and MCP tool servers on AgentCore runtime; multi-protocol (HTTP, MCP, A2A, AG-UI), streaming, sessions, async, VPC, and coding agents | -| 03 | [connect your agent to anything](03-connect-your-agent-to-anything/) | Built-in managed tools: sandboxed Python code execution (Code Interpreter) and headless browser automation (Browser Tool) | +| 03 | [connect your agent to anything](03-connect-your-agent-to-anything/) | Built-in managed tools: sandboxed Python code execution (Code Interpreter), headless browser automation (Browser Tool), and real-time web search (Web Search Tool) | | 04 | [manage context of your agent](04-manage-context-of-your-agent/) | Short-term session memory and long-term persistent memory for context-aware agents | | 05 | [authenticate and authorize](05-authenticate-and-authorize/) | Inbound auth (Cognito, Entra ID, Okta, PingFederate) and outbound auth (OAuth2, API keys, 3LO, M2M, OBO) | | 06 | [observe, evaluate, and optimize](06-observe-evaluate-optimize-your-agent/) | Trace and debug with OpenTelemetry, evaluate with LLM-as-a-judge and ground-truth evaluators, optimize prompts and tool descriptions | diff --git a/02-use-cases/01-conversational-agents/README.md b/02-use-cases/01-conversational-agents/README.md index 861a265e7..573907b70 100644 --- a/02-use-cases/01-conversational-agents/README.md +++ b/02-use-cases/01-conversational-agents/README.md @@ -29,6 +29,7 @@ Agents that interact with users in real time through a chat or query interface. | [A2A-multi-agent-incident-response](./A2A-multi-agent-incident-response/) | IT / DevOps | Advanced | Runtime, Gateway, Memory, A2A using Strands + OpenAI Agents + Google ADK | | [AWS-operations-agent](./AWS-operations-agent/) | Cloud Operations | Advanced | Runtime, Gateway, Memory, Policy, Observability; built with Strands, ADK, and OpenAI Agents SDK | | [customer-support-assistant-vpc](./customer-support-assistant-vpc/) | Retail / E-commerce | Intermediate | Runtime, Gateway deployed inside a VPC with private endpoints | +| [deep-research-agent](./deep-research-agent/) | Research / Q&A | Intermediate | Runtime, Gateway (Web Search); iterative Plan → Search → Reflect → Synthesize loop with auto-provisioning | | [device-management-agent](./device-management-agent/) | IoT / Smart Home | Intermediate | Runtime, Gateway, Policy, Identity (Cognito); React frontend | | [finance-personal-assistant](./finance-personal-assistant/) | Personal Finance | Beginner | Gateway, Policy; notebook-based | | [healthcare-appointment-agent](./healthcare-appointment-agent/) | Healthcare | Intermediate | Runtime, Gateway, Policy, Observability; FHIR R4 via HealthLake | diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/README.md b/02-use-cases/01-conversational-agents/deep-research-agent/README.md new file mode 100644 index 000000000..2eb4f1167 --- /dev/null +++ b/02-use-cases/01-conversational-agents/deep-research-agent/README.md @@ -0,0 +1,235 @@ +# Deep Research Agent + +An intelligent research agent powered by Amazon Bedrock AgentCore and Claude Sonnet 4 that answers complex, multi-faceted questions through an iterative Plan → Search → Reflect → Synthesize loop. + +## Overview + +Single-shot web search works for simple factual queries. For questions that require comparing multiple sources, reconciling conflicting information, or drilling into details revealed by earlier results, you need a reflect-and-refine loop. The Deep Research Agent makes that loop explicit and configurable. + +![Deep Research Agent-Iterative Research Loop](images/deep-research-loop-diagram.png) + +| Information | Details | +|:------------|:--------| +| Use case type | Research / Question answering | +| Agent type | Single agent | +| AgentCore components | AgentCore gateway, AgentCore runtime | +| Agentic framework | Strands Agents | +| LLM model | Anthropic Claude Sonnet 4 | +| Use case vertical | Cross-vertical | +| Example complexity | Intermediate | +| SDK used | bedrock-agentcore, strands-agents, mcp | + +## When to Use This Pattern + +| Scenario | Single-shot search | Deep Research Agent | +|:---------|:-------------------|:--------------------| +| Simple factual lookup | āœ… Sufficient | Overkill | +| Multi-source comparison | āŒ Incomplete | āœ… Ideal | +| Conflicting information | āŒ Misses nuance | āœ… Reconciles | +| Follow-up questions emerge | āŒ Single pass | āœ… Iterates | +| Complex regulatory / technical topics | āŒ Shallow | āœ… Deep | + +## Example Questions + +- "What are the key differences between Claude Sonnet 4 and GPT-4.5 for enterprise code generation, and which has stronger developer community adoption?" +- "What regulatory changes in the EU in 2026 affect AI startup compliance, and what are the key deadlines?" +- "How has the adoption of Model Context Protocol changed agent development practices in 2025–2026?" +- "What are the current best practices for deploying LLM agents in production, based on recent industry reports?" +- "What are the trade-offs between RAG and fine-tuning for enterprise LLM applications?" + +## Use Case Architecture + +![Deep Research Agent with Amazon Bedrock AgentCore](images/deep-research-agent-architecture.png) + +## Features + +- **Configurable depth** — set `--max-iter` (or `DEEP_RESEARCH_MAX_ITER`) to tune depth vs. cost +- **Transparent reasoning** — plan, search queries, and reflections are visible in the output +- **Cited answers** — every factual claim is backed by a source URL +- **AgentCore runtime** — production-ready hosting via `BedrockAgentCoreApp` +- **CLI + runtime modes** — run locally with `--query` or deploy to AgentCore runtime + +## Tuning the Loop + +| `--max-iter` | Effect | Use when | +|:-------------|:-------|:---------| +| 2 | Fast, shallow | Simple factual questions | +| 4 (default) | Balanced | Most research tasks | +| 6+ | Deep, thorough | Complex comparative analysis | + +Each additional iteration costs one WebSearch call and one LLM call. + +## Prerequisites + +- AWS account with Amazon Bedrock enabled in **us-east-1** +- **Claude Sonnet 4** model access enabled in Bedrock (Bedrock Console → Model Access) +- AWS credentials with `bedrock:InvokeModel` permission +- **No Gateway pre-setup required** — the agent auto-detects or provisions one for you + +### IAM permissions for auto-provisioning (first run only) + +If no existing Gateway is found, the agent will offer to create one. This requires: + +``` +iam:CreateRole, iam:PutRolePolicy, iam:GetRole +cognito-idp:CreateUserPool, cognito-idp:CreateUserPoolDomain +cognito-idp:CreateResourceServer, cognito-idp:CreateUserPoolClient +cognito-idp:ListUserPools, cognito-idp:ListUserPoolClients +cognito-idp:DescribeUserPoolClient, cognito-idp:DescribeResourceServer +bedrock-agentcore:CreateGateway, bedrock-agentcore:GetGateway +bedrock-agentcore:CreateGatewayTarget, bedrock-agentcore:ListGatewayTargets +bedrock-agentcore:ListGateways +``` + +On subsequent runs, if you export the printed variables, only `bedrock:InvokeModel` is needed. + +## Quick Start + +### 1. Install dependencies + +```bash +cd 02-use-cases/01-conversational-agents/deep-research-agent +pip install -r requirements.txt +``` + +### 2. Run locally + +```bash +# Interactive mode — auto-detects or provisions Gateway, then prompts for a question +python deep_research_agent.py + +# Direct question +python deep_research_agent.py --query "What are the trade-offs between RAG and fine-tuning for enterprise LLMs?" + +# Deep mode — 6 iterations for complex comparative questions +python deep_research_agent.py --query "Compare Claude Sonnet 4 and GPT-4.5 for code generation" --max-iter 6 +``` + +On first run without environment variables, the agent will: +1. Scan your account for an existing Gateway with a Web Search target +2. If found, reuse it automatically +3. If not found, prompt you to create one (~60 seconds) +4. Write credentials to `.env.web-search` for future runs + +### 3. (Optional) Pre-configure environment variables + +If you already have a Gateway from a previous setup, source the credentials file: + +```bash +source .env.web-search +``` + +Or set them manually: + +```bash +export AGENTCORE_GATEWAY_URL="https://..." +export COGNITO_DOMAIN="us-east-1xxxxxxxx" +export COGNITO_CLIENT_ID="..." +export COGNITO_CLIENT_SECRET="..." +export COGNITO_SCOPE="agentcore-websearch/invoke" +export AWS_DEFAULT_REGION="us-east-1" +``` + +### 4. (Optional) Deploy to AgentCore runtime + +For runtime deployments, environment variables **must** be pre-configured in the +container environment (no interactive provisioning in runtime mode): + +```bash +# The BedrockAgentCoreApp entrypoint makes this deployment-ready +python deep_research_agent.py # starts the runtime server when deployed +``` + +When deployed, invoke via the AgentCore runtime API: + +```json +{ + "prompt": "What are the current best practices for deploying LLM agents in production?", + "max_iter": 4 +} +``` + +## Cleanup + +When you're done, remove all provisioned resources: + +```bash +python cleanup.py --gateway-id --user-pool-id --role-name +``` + +| Parameter | Required | Description | +|:----------|:---------|:------------| +| `--gateway-id` | Yes | Gateway ID (printed during provisioning) | +| `--user-pool-id` | Yes | Cognito User Pool ID (printed during provisioning) | +| `--role-name` | Yes | IAM role name (printed during provisioning) | + +The cleanup script will: +- Delete the Gateway and all its targets +- Delete the Cognito User Pool and domain +- Delete the IAM service role and inline policies +- Remove the local `.env.web-search` credentials file + +After cleanup, unset environment variables: + +```bash +unset AGENTCORE_GATEWAY_URL COGNITO_DOMAIN COGNITO_CLIENT_ID COGNITO_CLIENT_SECRET COGNITO_SCOPE +``` + +## IAM Permissions + +### Caller (agent runtime) + +```json +{ + "Effect": "Allow", + "Action": "bedrock:InvokeModel", + "Resource": "arn:aws:bedrock:us-east-1::foundation-model/us.anthropic.claude-sonnet-4-20250514-v1:0" +} +``` + +### Gateway authentication + +Gateway invocation is authorised via the Cognito OAuth token — no additional IAM permissions needed for the caller beyond `bedrock:InvokeModel`. + +### Auto-provisioning (first run only) + +See the Prerequisites section above for the full list of permissions needed to create Gateway infrastructure. These are only required once — after provisioning, the agent only needs `bedrock:InvokeModel`. + +## Files + +| File | Description | +|:-----|:------------| +| `deep_research_agent.py` | Main agent — Plan/Search/Reflect loop, AgentCore runtime entrypoint, and CLI | +| `gateway_setup.py` | Auto-detection and provisioning of Gateway + Web Search infrastructure | +| `cleanup.py` | Deletes all provisioned AWS resources and local credentials | +| `requirements.txt` | Python dependencies | +| `README.md` | This file | + +## How It Works + +### The Research Loop + +The system prompt encodes the loop directly. Claude: + +1. **Plans** — decomposes the question into ordered sub-questions and identifies what needs searching +2. **Searches** — calls the `WebSearch` tool with the highest-priority unanswered sub-question (queries kept under 200 characters for best results) +3. **Reflects** — after each result, explicitly notes what was learned and what gaps remain; decides whether to search again or synthesize +4. **Synthesizes** — once confident (or when `max_iter` is reached), writes a comprehensive answer with cited URLs and noted uncertainties + +### Gateway Integration + +The agent connects to AgentCore gateway via MCP Streamable HTTP. The Gateway exposes the Web Search connector as a standard MCP `WebSearch` tool. Tool discovery (`tools/list`) and invocation (`tools/call`) happen automatically through the Strands `MCPClient`. + +### Auth Flow + +``` +deep_research_agent.py + └── get_oauth_token() + └── POST /oauth2/token → Cognito (client_credentials) + └── Bearer token attached to every MCP request → Gateway +``` + +## Related Resources + +- [`01-features/03-connect-your-agent-to-anything/03-web-search/`](../../../01-features/03-connect-your-agent-to-anything/03-web-search/) — Gateway setup, raw MCP, and basic agent demos +- [AgentCore gateway documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html) diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/cleanup.py b/02-use-cases/01-conversational-agents/deep-research-agent/cleanup.py new file mode 100644 index 000000000..aff660cc0 --- /dev/null +++ b/02-use-cases/01-conversational-agents/deep-research-agent/cleanup.py @@ -0,0 +1,117 @@ +""" +Clean up all resources provisioned by the Deep Research Agent. + +Deletes in order: + 1. Gateway targets and Gateway + 2. Cognito User Pool (domain, clients, pool) + 3. IAM Role and inline policies + 4. Local .env.web-search credentials file + +Prerequisites: + pip install -r requirements.txt + AWS credentials with permissions to delete the resources. + +Usage: + python cleanup.py --gateway-id --user-pool-id --role-name + python cleanup.py --gateway-id gw-abc123 --user-pool-id us-east-1_AbCdEf --role-name agentcore-web-search-gateway-role +""" + +import argparse +import os +import time + +import boto3 + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + + +def delete_gateway(gateway_client, gateway_id): + """Delete all targets and the gateway itself.""" + print("\n[1/3] Deleting Gateway resources...") + try: + targets = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id, maxResults=100) + for item in targets["items"]: + gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=item["targetId"]) + print(f" Deleted target: {item['name']}") + + time.sleep(10) + gateway_client.delete_gateway(gatewayIdentifier=gateway_id) + print(f" Deleted gateway: {gateway_id}") + except Exception as e: + print(f" Error deleting gateway: {e}") + + +def delete_cognito(cognito_client, user_pool_id): + """Delete the Cognito User Pool and its domain.""" + print("\n[2/3] Deleting Cognito resources...") + try: + domain = user_pool_id.replace("_", "").lower() + cognito_client.delete_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + cognito_client.delete_user_pool(UserPoolId=user_pool_id) + print(f" Deleted user pool: {user_pool_id}") + except Exception as e: + print(f" Error deleting Cognito: {e}") + + +def delete_iam_role(iam_client, role_name): + """Delete the IAM role and its inline policies.""" + print("\n[3/3] Deleting IAM resources...") + try: + policies = iam_client.list_role_policies(RoleName=role_name) + for policy_name in policies["PolicyNames"]: + iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + print(f" Deleted policy: {policy_name}") + + iam_client.delete_role(RoleName=role_name) + print(f" Deleted role: {role_name}") + except Exception as e: + print(f" Error deleting IAM role: {e}") + + +def delete_env_file(): + """Remove local .env.web-search credentials file.""" + env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env.web-search") + if os.path.exists(env_file): + os.remove(env_file) + print(f"\n Deleted local credentials file: {env_file}") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Clean up Deep Research Agent Gateway resources") + parser.add_argument("--gateway-id", required=True, help="Gateway ID to delete") + parser.add_argument("--user-pool-id", required=True, help="Cognito User Pool ID to delete") + parser.add_argument("--role-name", required=True, help="IAM role name to delete") + parser.add_argument("--region", default=REGION, help="AWS region (default: us-east-1)") + return parser.parse_args() + + +def main(): + args = parse_args() + region = args.region + + print("=" * 60) + print("Deep Research Agent — Resource Cleanup") + print("=" * 60) + print(f"\nRegion: {region}") + print(f"Gateway ID: {args.gateway_id}") + print(f"User Pool ID: {args.user_pool_id}") + print(f"Role Name: {args.role_name}") + + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + cognito_client = boto3.client("cognito-idp", region_name=region) + iam_client = boto3.client("iam") + + delete_gateway(gateway_client, args.gateway_id) + delete_cognito(cognito_client, args.user_pool_id) + delete_iam_role(iam_client, args.role_name) + delete_env_file() + + print("\n" + "=" * 60) + print("āœ… All resources cleaned up successfully!") + print("=" * 60) + print("\nšŸ’” Remember to unset environment variables:") + print(" unset AGENTCORE_GATEWAY_URL COGNITO_DOMAIN COGNITO_CLIENT_ID COGNITO_CLIENT_SECRET COGNITO_SCOPE") + + +if __name__ == "__main__": + main() diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/deep_research_agent.py b/02-use-cases/01-conversational-agents/deep-research-agent/deep_research_agent.py new file mode 100644 index 000000000..7494a8299 --- /dev/null +++ b/02-use-cases/01-conversational-agents/deep-research-agent/deep_research_agent.py @@ -0,0 +1,379 @@ +""" +Deep Research Agent — Plan → Search → Reflect → Synthesize Loop. + +An intelligent research agent powered by Amazon Bedrock AgentCore and Claude Sonnet 4 +that answers complex, multi-faceted questions through an iterative research loop: + + 1. PLAN — break the question into prioritised sub-questions + 2. SEARCH — execute the highest-priority unanswered sub-question via WebSearch + 3. REFLECT — assess gaps; if unresolved and iterations remain, go back to SEARCH + 4. SYNTHESIZE — write a comprehensive, cited answer once confident + +Single-shot search fails for questions that require comparing multiple sources, +reconciling conflicting information, or drilling into details revealed by earlier +results. This agent makes the reflect-and-refine loop explicit and configurable. + +Prerequisites: + pip install -r requirements.txt + + Gateway + Web Search configuration is handled automatically: + - If environment variables are set, they are used directly + - If not, the agent scans for an existing Gateway in your account + - If none found, it offers to create one interactively + +Optional environment variables: + AGENTCORE_GATEWAY_URL — Gateway MCP endpoint (auto-detected if missing) + COGNITO_DOMAIN — Cognito domain prefix (auto-detected if missing) + COGNITO_CLIENT_ID — Cognito app client ID (auto-detected if missing) + COGNITO_CLIENT_SECRET — Cognito app client secret (auto-detected if missing) + COGNITO_SCOPE — OAuth scope string (auto-detected if missing) + BEDROCK_MODEL_ID — Bedrock inference profile ID or ARN + (defaults to us.anthropic.claude-sonnet-4-6) + DEEP_RESEARCH_MAX_ITER — Maximum search iterations per question (default: 4) + AWS_DEFAULT_REGION — AWS region (default: us-east-1) + +IAM permissions required: + bedrock:InvokeModel (for Claude Sonnet 4) + + provisioning permissions if auto-creating Gateway (see gateway_setup.py) + +Usage: + # Run interactively (auto-detects or provisions gateway, prompts for question) + python deep_research_agent.py + + # Pass a question directly + python deep_research_agent.py --query "What are the trade-offs between RAG and fine-tuning for enterprise LLMs?" + + # Increase depth for complex comparative questions + python deep_research_agent.py --query "..." --max-iter 6 +""" + +import argparse +import logging +import os + +import requests +from bedrock_agentcore.runtime import BedrockAgentCoreApp +from mcp.client.streamable_http import streamablehttp_client +from strands import Agent +from strands.models import BedrockModel +from strands.tools.mcp.mcp_client import MCPClient + +from gateway_setup import ensure_gateway + +# ── Configuration ────────────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") +MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "us.anthropic.claude-sonnet-4-6") +MAX_SEARCH_ITERATIONS = int(os.getenv("DEEP_RESEARCH_MAX_ITER", "4")) + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Initialize the AgentCore Runtime app +app = BedrockAgentCoreApp() + + +# ── Auth helpers ─────────────────────────────────────────────────────────────── + + +def get_oauth_token() -> str: + """Retrieve a fresh OAuth token from Cognito using client_credentials flow.""" + cognito_domain = os.environ.get("COGNITO_DOMAIN", "") + cognito_client_id = os.environ.get("COGNITO_CLIENT_ID", "") + cognito_client_secret = os.environ.get("COGNITO_CLIENT_SECRET", "") + cognito_scope = os.environ.get("COGNITO_SCOPE", "") + region = os.environ.get("AWS_DEFAULT_REGION", REGION) + + if not all([cognito_domain, cognito_client_id, cognito_client_secret]): + raise ValueError( + "Cognito credentials not configured. Run the agent interactively " + "to auto-provision, or set COGNITO_DOMAIN, COGNITO_CLIENT_ID, " + "and COGNITO_CLIENT_SECRET environment variables." + ) + url = f"https://{cognito_domain}.auth.{region}.amazoncognito.com/oauth2/token" + resp = requests.post( + url, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data={ + "grant_type": "client_credentials", + "client_id": cognito_client_id, + "client_secret": cognito_client_secret, + "scope": cognito_scope, + }, + timeout=10, + ) + if resp.status_code != 200: + error_detail = resp.text + raise RuntimeError( + f"Authentication failed (HTTP {resp.status_code}). " + f"Cognito returned: {error_detail}\n" + f" Check your COGNITO_CLIENT_ID and COGNITO_CLIENT_SECRET are correct.\n" + f" Token URL: {url}" + ) + return resp.json()["access_token"] + + +def create_mcp_transport(): + """Create an authenticated MCP Streamable HTTP transport for the Gateway.""" + gateway_url = os.environ.get("AGENTCORE_GATEWAY_URL", "") + if not gateway_url: + raise ValueError( + "Gateway URL not configured. Run the agent interactively to " + "auto-provision, or set AGENTCORE_GATEWAY_URL environment variable." + ) + token = get_oauth_token() + return streamablehttp_client( + gateway_url, + headers={"Authorization": f"Bearer {token}"}, + ) + + +# ── System prompt ────────────────────────────────────────────────────────────── + + +def build_system_prompt(max_iterations: int) -> str: + """Build the deep-research system prompt with the configured iteration cap. + + Args: + max_iterations: Maximum number of search-reflect cycles before forcing synthesis. + + Returns: + System prompt string for the Strands agent. + """ + return f"""You are a thorough research analyst with access to real-time web search. + +RESEARCH LOOP (repeat up to {max_iterations} iterations): + +STEP 1 — PLAN: + Break the question into sub-questions. List them in priority order. + Identify what you can answer confidently vs. what requires search. + +STEP 2 — SEARCH: + Execute the highest-priority unanswered sub-question as a web search. + Keep queries under 200 characters. + After each search, note: "Learned: . Remaining gaps: ." + +STEP 3 — REFLECT: + Ask yourself: "Do I have enough to answer the original question confidently?" + - If NO and iterations remain: formulate the next search based on gaps, go to STEP 2 + - If YES or iterations exhausted: go to STEP 4 + +STEP 4 — SYNTHESIZE: + Write a comprehensive answer that: + - Directly addresses the original question + - Integrates findings from all search rounds + - Cites URLs for all factual claims + - Notes any remaining uncertainties + +SHOW YOUR WORK: Make the plan, search queries, and reflections visible in your +response. This helps users understand the research process and verify the results. +""" + + +# ── Agent factory ────────────────────────────────────────────────────────────── + + +def create_deep_research_agent(max_iterations: int = MAX_SEARCH_ITERATIONS) -> tuple: + """Create an MCPClient and a Strands deep-research agent. + + Returns the (mcp_client, agent) pair. The caller is responsible for using + mcp_client as a context manager. + + Args: + max_iterations: Maximum search-reflect cycles before forcing synthesis. + + Returns: + Tuple of (MCPClient, Agent). + """ + mcp_client = MCPClient(create_mcp_transport) + + with mcp_client: + tools = mcp_client.list_tools_sync() + logger.info("Discovered %d tool(s) from Gateway", len(tools)) + + model = BedrockModel( + model_id=MODEL_ID, + region_name=REGION, + temperature=0.5, + max_tokens=4096, + ) + agent = Agent( + model=model, + tools=tools, + system_prompt=build_system_prompt(max_iterations), + ) + + return mcp_client, agent + + +# ── Response extraction ──────────────────────────────────────────────────────── + + +def extract_text(response) -> str: + """Extract the text content from a Strands agent response.""" + if hasattr(response, "message"): + parts = [] + for block in response.message.get("content", []): + if block.get("text"): + parts.append(block["text"]) + return "\n".join(parts) + return str(response) + + +# ── AgentCore Runtime entrypoint ─────────────────────────────────────────────── + + +@app.entrypoint +def deep_research_runtime(payload): + """AgentCore Runtime handler — receives a payload and returns the research report. + + In runtime mode, environment variables MUST be pre-configured (no interactive + provisioning). The runtime expects AGENTCORE_GATEWAY_URL and Cognito vars to + be set in the container environment. + + Args: + payload: Dict containing the research question under any of: + 'prompt', 'query', 'message', or 'inputText'. + + Returns: + str: The full research report with citations. + """ + query = ( + payload.get("prompt") + or payload.get("query") + or payload.get("message") + or payload.get("inputText") + or payload.get("input") + ) + + if not query: + return "No research question provided. Include your question under the 'prompt' key." + + max_iter = int(payload.get("max_iter", MAX_SEARCH_ITERATIONS)) + logger.info("Deep research request: %s (max_iter=%d)", query, max_iter) + + # In runtime mode, ensure gateway config is available (non-interactive) + ensure_gateway(interactive=False) + + mcp_client = MCPClient(create_mcp_transport) + + with mcp_client: + tools = mcp_client.list_tools_sync() + model = BedrockModel( + model_id=MODEL_ID, + region_name=REGION, + temperature=0.5, + max_tokens=4096, + ) + agent = Agent( + model=model, + tools=tools, + system_prompt=build_system_prompt(max_iter), + ) + response = agent(query) + + return extract_text(response) + + +# ── CLI entrypoint ───────────────────────────────────────────────────────────── + + +def parse_args(): + parser = argparse.ArgumentParser( + description=( + "Deep Research Agent — iterative Plan/Search/Reflect/Synthesize loop via AgentCore Gateway Web Search Tool" + ) + ) + parser.add_argument( + "--query", + default=None, + help="Research question. If omitted, the agent prompts interactively.", + ) + parser.add_argument( + "--max-iter", + type=int, + default=MAX_SEARCH_ITERATIONS, + help=( + f"Maximum search iterations per question (default: {MAX_SEARCH_ITERATIONS}). " + "Higher values give deeper results at the cost of more WebSearch + LLM calls. " + "Recommended: 2 (fast), 4 (balanced), 6+ (deep comparative analysis)." + ), + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + print("=" * 60) + print("AgentCore Web Search Tool — Deep Research Agent") + print("=" * 60) + print(f"Max search iterations : {args.max_iter}") + print(f"Model : {MODEL_ID}") + + # ── Ensure Gateway is configured (detect / reuse / provision) ────────── + ensure_gateway(interactive=True) + + print() + query = args.query + if not query: + query = input("Enter your research question: ").strip() + if not query: + print("No question provided. Exiting.") + return + + print(f"\nResearch question: {query}") + print("-" * 60) + print("Running iterative research loop...\n") + + mcp_client = MCPClient(create_mcp_transport) + + try: + with mcp_client: + tools = mcp_client.list_tools_sync() + print(f"Discovered {len(tools)} tool(s) from Gateway\n") + + model = BedrockModel( + model_id=MODEL_ID, + region_name=REGION, + temperature=0.5, + max_tokens=4096, + ) + agent = Agent( + model=model, + tools=tools, + system_prompt=build_system_prompt(args.max_iter), + ) + response = agent(query) + except Exception as e: + error_msg = str(e) + if "authentication" in error_msg.lower() or "401" in error_msg or "400" in error_msg: + print(f"\nāŒ Authentication error: {error_msg}") + print("\n Possible causes:") + print(" • COGNITO_CLIENT_SECRET is incorrect or expired") + print(" • COGNITO_CLIENT_ID does not match the User Pool client") + print(" • COGNITO_DOMAIN is wrong") + print("\n To fix: re-run the Gateway setup or correct your environment variables.") + elif "initialization" in error_msg.lower(): + print(f"\nāŒ Failed to connect to the Gateway: {error_msg}") + print("\n Possible causes:") + print(" • Invalid Cognito credentials (check COGNITO_CLIENT_SECRET)") + print(" • Gateway URL is unreachable (check AGENTCORE_GATEWAY_URL)") + print(" • Gateway is not in READY state") + print("\n To fix: verify your environment variables and Gateway status.") + else: + print(f"\nāŒ Error: {error_msg}") + raise SystemExit(1) + + print("\n" + "=" * 60) + print("RESEARCH REPORT") + print("=" * 60) + print(extract_text(response)) + print("\n" + "=" * 60) + print("Deep Research Agent demo complete.") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/gateway_setup.py b/02-use-cases/01-conversational-agents/deep-research-agent/gateway_setup.py new file mode 100644 index 000000000..f96f64732 --- /dev/null +++ b/02-use-cases/01-conversational-agents/deep-research-agent/gateway_setup.py @@ -0,0 +1,565 @@ +""" +Gateway auto-detection and provisioning for the Deep Research Agent. + +Implements a Detect → Prompt → Auto-provision flow: + 1. Check if AGENTCORE_GATEWAY_URL is already set (use it directly) + 2. If not, scan the account for an existing Gateway with a web-search target + 3. If found, auto-configure from the existing Gateway + 4. If not found, prompt the user and create one on the fly + +This removes the hard prerequisite of running a separate setup script before +using the Deep Research Agent. + +Environment variables (all optional — auto-provisioned if missing): + AGENTCORE_GATEWAY_URL — Gateway MCP endpoint + COGNITO_DOMAIN — Cognito domain prefix + COGNITO_CLIENT_ID — Cognito app client ID + COGNITO_CLIENT_SECRET — Cognito app client secret + COGNITO_SCOPE — OAuth scope string + AWS_DEFAULT_REGION — AWS region (default: us-east-1) + +IAM permissions required for auto-provisioning: + iam:CreateRole, iam:PutRolePolicy, iam:GetRole + cognito-idp:CreateUserPool, cognito-idp:CreateUserPoolDomain + cognito-idp:CreateResourceServer, cognito-idp:CreateUserPoolClient + cognito-idp:ListUserPools, cognito-idp:ListUserPoolClients + cognito-idp:DescribeUserPoolClient, cognito-idp:DescribeResourceServer + bedrock-agentcore:CreateGateway, bedrock-agentcore:GetGateway + bedrock-agentcore:CreateGatewayTarget, bedrock-agentcore:ListGatewayTargets + bedrock-agentcore:ListGateways +""" + +import json +import logging +import os +import time + +import boto3 + +logger = logging.getLogger(__name__) + +REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1") +GATEWAY_NAME_PREFIX = "deep-research" + + +# ── Data class for gateway config ────────────────────────────────────────────── + + +class GatewayConfig: + """Holds all connection details for an AgentCore Gateway with Web Search.""" + + def __init__( + self, + gateway_url: str, + cognito_domain: str, + cognito_client_id: str, + cognito_client_secret: str, + cognito_scope: str, + region: str = REGION, + gateway_id: str = "", + role_name: str = "", + user_pool_id: str = "", + ): + self.gateway_url = gateway_url + self.cognito_domain = cognito_domain + self.cognito_client_id = cognito_client_id + self.cognito_client_secret = cognito_client_secret + self.cognito_scope = cognito_scope + self.region = region + self.gateway_id = gateway_id + self.role_name = role_name + self.user_pool_id = user_pool_id + + def export_to_env(self): + """Set environment variables so downstream code can use them.""" + os.environ["AGENTCORE_GATEWAY_URL"] = self.gateway_url + os.environ["COGNITO_DOMAIN"] = self.cognito_domain + os.environ["COGNITO_CLIENT_ID"] = self.cognito_client_id + os.environ["COGNITO_CLIENT_SECRET"] = self.cognito_client_secret + os.environ["COGNITO_SCOPE"] = self.cognito_scope + os.environ["AWS_DEFAULT_REGION"] = self.region + + def print_env_vars(self): + """Write credentials to a local .env file and print non-sensitive info.""" + env_file = ".env.web-search" + # START nosec - intentional for local development workflow + with open(env_file, "w") as f: + f.write(f'export AGENTCORE_GATEWAY_URL="{self.gateway_url}"\n') + f.write(f'export COGNITO_DOMAIN="{self.cognito_domain}"\n') + f.write(f'export COGNITO_CLIENT_ID="{self.cognito_client_id}"\n') + f.write(f'export COGNITO_CLIENT_SECRET="{self.cognito_client_secret}"\n') + f.write(f'export COGNITO_SCOPE="{self.cognito_scope}"\n') + f.write(f'export AWS_DEFAULT_REGION="{self.region}"\n') + # END nosec - intentional for local development workflow + print(f"\n āœ… Credentials written to: {env_file}") + print(f" Load them with: source {env_file}\n") + print(f" Gateway URL: {self.gateway_url}") + if self.gateway_id: + print(f" Gateway ID: {self.gateway_id} (for cleanup)") + if self.role_name: + print(f" IAM Role: {self.role_name}") + if self.user_pool_id: + print(f" Cognito Pool: {self.user_pool_id}") + print(f"\n āš ļø Keep {env_file} secure — it contains your client secret.") + + +# ── Detection: check env vars ───────────────────────────────────────────────── + + +def _config_from_env() -> GatewayConfig | None: + """Return a GatewayConfig from environment variables, or None if incomplete.""" + gateway_url = os.getenv("AGENTCORE_GATEWAY_URL", "") + cognito_domain = os.getenv("COGNITO_DOMAIN", "") + cognito_client_id = os.getenv("COGNITO_CLIENT_ID", "") + cognito_client_secret = os.getenv("COGNITO_CLIENT_SECRET", "") + cognito_scope = os.getenv("COGNITO_SCOPE", "") + + if all([gateway_url, cognito_domain, cognito_client_id, cognito_client_secret]): + return GatewayConfig( + gateway_url=gateway_url, + cognito_domain=cognito_domain, + cognito_client_id=cognito_client_id, + cognito_client_secret=cognito_client_secret, + cognito_scope=cognito_scope or "agentcore-websearch/invoke", + region=REGION, + ) + return None + + +# ── Detection: scan account for existing gateway ────────────────────────────── + + +def _find_existing_gateway() -> dict | None: + """Scan the account for an existing Gateway with a web-search connector target. + + Returns a dict with gateway_id and gateway_url if found, else None. + """ + try: + client = boto3.client("bedrock-agentcore-control", region_name=REGION) + + # List all gateways + paginator_kwargs = {} + while True: + response = client.list_gateways(maxResults=50, **paginator_kwargs) + for gw in response.get("items", []): + gw_id = gw["gatewayId"] + # Check if this gateway has a web-search target + try: + targets = client.list_gateway_targets(gatewayIdentifier=gw_id) + for target in targets.get("items", []): + target_name = target.get("name", "").lower() + if "web-search" in target_name or "websearch" in target_name: + # Get the full gateway details for the URL + gw_detail = client.get_gateway(gatewayIdentifier=gw_id) + return { + "gateway_id": gw_id, + "gateway_url": gw_detail["gatewayUrl"], + "gateway_name": gw.get("name", ""), + } + except Exception: + continue + + next_token = response.get("nextToken") + if not next_token: + break + paginator_kwargs = {"nextToken": next_token} + + except Exception as e: + logger.debug("Could not scan for existing gateways: %s", e) + + return None + + +def _find_cognito_for_gateway(gateway_id: str) -> dict | None: + """Try to find Cognito credentials associated with a gateway. + + Looks for the standard Cognito pool/client created by our setup pattern. + Returns dict with domain, client_id, client_secret, scope or None. + """ + try: + cognito_client = boto3.client("cognito-idp", region_name=REGION) + pools = cognito_client.list_user_pools(MaxResults=60)["UserPools"] + + for pool in pools: + pool_name = pool["Name"] + # Look for pools matching our naming conventions + if "agentcore" in pool_name.lower() and "websearch" in pool_name.lower(): + pool_id = pool["Id"] + clients = cognito_client.list_user_pool_clients(UserPoolId=pool_id, MaxResults=60)["UserPoolClients"] + + for client_info in clients: + if "websearch" in client_info["ClientName"].lower(): + desc = cognito_client.describe_user_pool_client( + UserPoolId=pool_id, + ClientId=client_info["ClientId"], + ) + client_detail = desc["UserPoolClient"] + domain = pool_id.replace("_", "").lower() + return { + "domain": domain, + "client_id": client_detail["ClientId"], + "client_secret": client_detail.get("ClientSecret", ""), + "scope": "agentcore-websearch/invoke", + "user_pool_id": pool_id, + } + except Exception as e: + logger.debug("Could not find Cognito credentials: %s", e) + + return None + + +# ── Provisioning ────────────────────────────────────────────────────────────── + + +def _wait_for_gateway_status(client, gateway_id, target_status="READY", max_wait=150): + """Poll gateway status until it reaches target_status.""" + for _ in range(max_wait // 5): + status = client.get_gateway(gatewayIdentifier=gateway_id)["status"] + if status == target_status: + return status + time.sleep(5) + return status + + +def _wait_for_targets_ready(client, gateway_id, max_wait=150): + """Poll until all gateway targets are READY.""" + for _ in range(max_wait // 5): + targets = client.list_gateway_targets(gatewayIdentifier=gateway_id) + if all(item["status"] == "READY" for item in targets["items"]): + return True + time.sleep(5) + return False + + +def _create_gateway_role(iam_client, role_name, account_id, region): + """Create the IAM service role for the Gateway.""" + assume_role_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "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:{region}:{account_id}:*"}, + }, + } + ], + } + + try: + role_response = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=json.dumps(assume_role_policy), + ) + print(f" Created role: {role_name}") + time.sleep(10) # Wait for IAM propagation + except iam_client.exceptions.EntityAlreadyExistsException: + role_response = iam_client.get_role(RoleName=role_name) + print(f" Role already exists: {role_name}") + + role_arn = role_response["Role"]["Arn"] + + iam_client.put_role_policy( + RoleName=role_name, + PolicyName="WebSearchGatewayPolicy", + PolicyDocument=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "InvokeGateway", + "Effect": "Allow", + "Action": "bedrock-agentcore:InvokeGateway", + "Resource": f"arn:aws:bedrock-agentcore:{region}:{account_id}:gateway/*", + }, + { + "Sid": "InvokeWebSearch", + "Effect": "Allow", + "Action": "bedrock-agentcore:InvokeWebSearch", + "Resource": f"arn:aws:bedrock-agentcore:{region}:aws:tool/web-search.v1", + }, + ], + } + ), + ) + print(" Permissions attached āœ“") + return role_arn + + +def _create_cognito_resources(cognito_client, region): + """Create Cognito User Pool, resource server, and M2M client.""" + pool_name = "agentcore-websearch-pool" + resource_server_id = "agentcore-websearch" + scopes = [{"ScopeName": "invoke", "ScopeDescription": "Invoke gateway"}] + scope_names = [f"{resource_server_id}/{s['ScopeName']}" for s in scopes] + + # Find or create user pool + user_pool_id = None + for pool in cognito_client.list_user_pools(MaxResults=60)["UserPools"]: + if pool["Name"] == pool_name: + user_pool_id = pool["Id"] + break + + if user_pool_id is None: + create_resp = cognito_client.create_user_pool(PoolName=pool_name) + user_pool_id = create_resp["UserPool"]["Id"] + domain = user_pool_id.replace("_", "").lower() + cognito_client.create_user_pool_domain(Domain=domain, UserPoolId=user_pool_id) + print(f" Created user pool: {user_pool_id}") + else: + print(f" User pool exists: {user_pool_id}") + + # Create resource server + try: + cognito_client.describe_resource_server(UserPoolId=user_pool_id, Identifier=resource_server_id) + except cognito_client.exceptions.ResourceNotFoundException: + cognito_client.create_resource_server( + UserPoolId=user_pool_id, + Identifier=resource_server_id, + Name="WebSearch Gateway Resource Server", + Scopes=scopes, + ) + print(" Resource server ensured āœ“") + + # Find or create M2M client + client_id, client_secret = None, None + for client in cognito_client.list_user_pool_clients(UserPoolId=user_pool_id, MaxResults=60)["UserPoolClients"]: + if client["ClientName"] == "agentcore-websearch-client": + desc = cognito_client.describe_user_pool_client(UserPoolId=user_pool_id, ClientId=client["ClientId"]) + client_id = client["ClientId"] + client_secret = desc["UserPoolClient"]["ClientSecret"] + break + + if client_id is None: + created = cognito_client.create_user_pool_client( + UserPoolId=user_pool_id, + ClientName="agentcore-websearch-client", + GenerateSecret=True, + AllowedOAuthFlows=["client_credentials"], + AllowedOAuthScopes=scope_names, + AllowedOAuthFlowsUserPoolClient=True, + SupportedIdentityProviders=["COGNITO"], + ExplicitAuthFlows=["ALLOW_REFRESH_TOKEN_AUTH"], + ) + client_id = created["UserPoolClient"]["ClientId"] + client_secret = created["UserPoolClient"]["ClientSecret"] + print(f" Created client: {client_id}") + else: + print(f" Client exists: {client_id}") + + domain = user_pool_id.replace("_", "").lower() + discovery_url = f"https://cognito-idp.{region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration" + scope_string = " ".join(scope_names) + + return { + "user_pool_id": user_pool_id, + "client_id": client_id, + "client_secret": client_secret, + "discovery_url": discovery_url, + "domain": domain, + "scope": scope_string, + } + + +def _create_gateway_and_target(gateway_client, name, role_arn, cognito_config): + """Create the AgentCore Gateway and Web Search target.""" + create_response = gateway_client.create_gateway( + name=name, + roleArn=role_arn, + protocolType="MCP", + protocolConfiguration={"mcp": {"supportedVersions": ["2025-03-26"], "searchType": "SEMANTIC"}}, + authorizerType="CUSTOM_JWT", + authorizerConfiguration={ + "customJWTAuthorizer": { + "allowedClients": [cognito_config["client_id"]], + "discoveryUrl": cognito_config["discovery_url"], + } + }, + description="AgentCore Gateway with Web Search Tool (auto-provisioned by Deep Research Agent)", + ) + + gateway_id = create_response["gatewayId"] + gateway_url = create_response["gatewayUrl"] + print(f" Gateway ID: {gateway_id}") + print(f" Gateway URL: {gateway_url}") + + status = _wait_for_gateway_status(gateway_client, gateway_id) + print(f" Gateway status: {status}") + + # Create Web Search connector target + gateway_client.create_gateway_target( + name="web-search-tool", + gatewayIdentifier=gateway_id, + targetConfiguration={ + "mcp": { + "connector": { + "source": {"connectorId": "web-search"}, + "configurations": [{"name": "WebSearch", "parameterValues": {}}], + } + } + }, + credentialProviderConfigurations=[{"credentialProviderType": "GATEWAY_IAM_ROLE"}], + ) + + if _wait_for_targets_ready(gateway_client, gateway_id): + print(" Web Search target: READY āœ“") + else: + print(" WARNING: Target did not reach READY state within timeout") + + return gateway_id, gateway_url + + +def provision_gateway(gateway_name: str | None = None) -> GatewayConfig: + """Create a new Gateway with Web Search from scratch. + + Args: + gateway_name: Optional custom name. Defaults to 'deep-research-web-search-gw'. + + Returns: + GatewayConfig with all connection details. + """ + name = gateway_name or "deep-research-web-search-gw" + region = REGION + + sts_client = boto3.client("sts", region_name=region) + account_id = sts_client.get_caller_identity()["Account"] + + print(f"\n Account: {account_id}") + print(f" Region: {region}") + + # Step 1: IAM Role + print("\n [1/4] Creating Gateway service role...") + iam_client = boto3.client("iam") + role_name = f"agentcore-{name}-role" + role_arn = _create_gateway_role(iam_client, role_name, account_id, region) + + # Step 2: Cognito + print("\n [2/4] Setting up Cognito authentication...") + cognito_client = boto3.client("cognito-idp", region_name=region) + cognito_config = _create_cognito_resources(cognito_client, region) + + # Step 3: Gateway + Target + print("\n [3/4] Creating AgentCore Gateway...") + gateway_client = boto3.client("bedrock-agentcore-control", region_name=region) + gateway_id, gateway_url = _create_gateway_and_target(gateway_client, name, role_arn, cognito_config) + + print("\n [4/4] Setup complete āœ“") + + return GatewayConfig( + gateway_url=gateway_url, + cognito_domain=cognito_config["domain"], + cognito_client_id=cognito_config["client_id"], + cognito_client_secret=cognito_config["client_secret"], + cognito_scope=cognito_config["scope"], + region=region, + gateway_id=gateway_id, + role_name=role_name, + user_pool_id=cognito_config["user_pool_id"], + ) + + +# ── Main entry point: Detect → Prompt → Provision ───────────────────────────── + + +def ensure_gateway(interactive: bool = True) -> GatewayConfig: + """Ensure a working Gateway + Web Search configuration is available. + + Resolution order: + 1. Environment variables already set → use them + 2. Existing Gateway with web-search target in the account → reuse it + 3. Prompt user (if interactive) → provision a new one + + Args: + interactive: If True, prompt the user before creating resources. + If False (e.g. runtime mode), raise an error instead. + + Returns: + GatewayConfig ready to use. + + Raises: + SystemExit: If no gateway is available and user declines to create one. + ValueError: If non-interactive and no gateway is available. + """ + # ── Step 1: Check environment variables ──────────────────────────────── + config = _config_from_env() + if config: + logger.info("Using gateway configuration from environment variables.") + return config + + print("\nāš™ļø No gateway configuration found in environment variables.") + print(" Scanning your account for an existing Gateway with Web Search...") + + # ── Step 2: Scan for existing gateway ────────────────────────────────── + existing = _find_existing_gateway() + if existing: + print(f"\nāœ… Found existing Gateway: {existing['gateway_name']} ({existing['gateway_id']})") + print(" Checking for associated Cognito credentials...") + + cognito = _find_cognito_for_gateway(existing["gateway_id"]) + if cognito: + print(" āœ… Found Cognito credentials.") + config = GatewayConfig( + gateway_url=existing["gateway_url"], + cognito_domain=cognito["domain"], + cognito_client_id=cognito["client_id"], + cognito_client_secret=cognito["client_secret"], + cognito_scope=cognito["scope"], + region=REGION, + gateway_id=existing["gateway_id"], + user_pool_id=cognito["user_pool_id"], + ) + config.export_to_env() + config.print_env_vars() + return config + else: + print(" āš ļø Could not find Cognito credentials for this Gateway.") + print(" Will need to provision a new setup.") + + # ── Step 3: Prompt and provision ─────────────────────────────────────── + if not interactive: + raise ValueError( + "No gateway configuration available. Set AGENTCORE_GATEWAY_URL and " + "Cognito environment variables, or run in interactive mode to auto-provision." + ) + + print("\n" + "-" * 60) + print(" No usable Gateway + Web Search setup found.") + print(" I can create one for you. This will provision:") + print(" • IAM service role (bedrock-agentcore trust)") + print(" • Cognito User Pool + M2M client") + print(" • AgentCore Gateway (MCP protocol)") + print(" • Web Search connector target") + print() + print(f" Region: {REGION}") + print(" Estimated time: ~60 seconds") + print("-" * 60) + + response = input("\n Create Gateway + Web Search now? [y/N]: ").strip().lower() + if response not in ("y", "yes"): + print("\n Aborted. To set up manually, you can either:") + print() + print(" Option 1 — Use the bundled setup module:") + print( + ' python -c "from gateway_setup import provision_gateway; cfg = provision_gateway(); cfg.print_env_vars()"' + ) + print() + print(" Option 2 — Use the standalone setup script:") + print( + " python ../../01-features/03-connect-your-agent-to-anything/03-web-search/01-setup-gateway/setup_gateway.py" + ) + print() + print(" Then export the printed environment variables and re-run the agent.") + raise SystemExit(1) + + # Optional: let user name the gateway + custom_name = input(" Gateway name [deep-research-web-search-gw]: ").strip() + + print("\nšŸš€ Provisioning Gateway + Web Search Tool...\n") + config = provision_gateway(gateway_name=custom_name or None) + config.export_to_env() + config.print_env_vars() + print() + + return config diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-agent-architecture.png b/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-agent-architecture.png new file mode 100644 index 000000000..14a1e285f Binary files /dev/null and b/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-agent-architecture.png differ diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-loop-diagram.png b/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-loop-diagram.png new file mode 100644 index 000000000..58fd67873 Binary files /dev/null and b/02-use-cases/01-conversational-agents/deep-research-agent/images/deep-research-loop-diagram.png differ diff --git a/02-use-cases/01-conversational-agents/deep-research-agent/requirements.txt b/02-use-cases/01-conversational-agents/deep-research-agent/requirements.txt new file mode 100644 index 000000000..bf6ee953e --- /dev/null +++ b/02-use-cases/01-conversational-agents/deep-research-agent/requirements.txt @@ -0,0 +1,6 @@ +bedrock-agentcore>=0.2.0 +strands-agents>=0.1.8 +boto3 +botocore +mcp +requests \ No newline at end of file diff --git a/02-use-cases/README.md b/02-use-cases/README.md index f0bb12523..65fbdf7e9 100644 --- a/02-use-cases/README.md +++ b/02-use-cases/README.md @@ -13,6 +13,7 @@ Agents that interact with users in real time. Users authenticate through an iden | [A2A-multi-agent-incident-response](./01-conversational-agents/A2A-multi-agent-incident-response/) | IT / DevOps | Runtime, Gateway, Memory, A2A (3 frameworks) | | [AWS-operations-agent](./01-conversational-agents/AWS-operations-agent/) | Cloud Operations | Runtime, Gateway, Memory, Policy, Observability | | [customer-support-assistant-vpc](./01-conversational-agents/customer-support-assistant-vpc/) | Retail / E-commerce | Runtime, Gateway (VPC) | +| [deep-research-agent](./01-conversational-agents/deep-research-agent/) | Research / Q&A | Gateway (Web Search), Runtime | | [device-management-agent](./01-conversational-agents/device-management-agent/) | IoT / Smart Home | Runtime, Gateway, Policy, Identity (Cognito) | | [finance-personal-assistant](./01-conversational-agents/finance-personal-assistant/) | Personal Finance | Gateway, Policy | | [healthcare-appointment-agent](./01-conversational-agents/healthcare-appointment-agent/) | Healthcare | Runtime, Gateway, Policy, Observability (FHIR R4) | diff --git a/06-workshops/05-AgentCore-tools/README.md b/06-workshops/05-AgentCore-tools/README.md index d30e601fc..e7f322724 100644 --- a/06-workshops/05-AgentCore-tools/README.md +++ b/06-workshops/05-AgentCore-tools/README.md @@ -2,9 +2,9 @@ ## Overview Amazon Bedrock AgentCore Tools provide enterprise-grade capabilities that enhance AI agents' ability to perform complex -tasks securely and efficiently. This suite includes two primary tools: +tasks securely and efficiently. This suite includes tools such as: -- Amazon Bedrock AgentCore Code Interpreter and +- Amazon Bedrock AgentCore Code Interpreter - Amazon Bedrock AgentCore Browser Tool ## Amazon Bedrock AgentCore Code Interpreter diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 53eabacb5..c2ad85b1e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -22,6 +22,7 @@ - Grace Lang - Hari Tripathi - Joshua Samuel +- Naga Gaddamu(nagagaddamu) - khastation - manchandakp - madhurprash diff --git a/README.md b/README.md index 0ce6004cb..166733217 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Focused examples for individual AgentCore capabilities: - **[Gateway](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html)** — Convert APIs, Lambda functions, and services into MCP-compatible tools - **[Identity](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html)** — Agent identity and access management across AWS and third-party apps - **[Memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html)** — Managed memory infrastructure for personalized agent experiences -- **[Tools](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html)** — Built-in Code Interpreter and Browser Tool +- **[Tools](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html)** — Built-in Code Interpreter, Browser Tool, Web Search Tool - **[Observability](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability.html)** — Trace, debug, and monitor agent performance with OpenTelemetry - **[Evaluation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/evaluations.html)** — Built-in and custom evaluators for on-demand and online evaluation - **[Policy](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/policy.html)** — Fine-grained access control with Cedar policies @@ -159,7 +159,7 @@ agentcore add online-eval # Enable continuous evaluation agentcore deploy # Sync changes to AWS ``` -Congratulations! Your agent is now running on Amazon Bedrock AgentCore Runtime. +Congratulations! Your agent is now running on Amazon Bedrock AgentCore runtime. For the full CLI reference, see the [AgentCore CLI documentation](https://github.com/aws/agentcore-cli).