diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/.gitignore b/03-integrations/gateway/agentcore-tool-search-plugin/.gitignore new file mode 100644 index 000000000..2b34709fc --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/.gitignore @@ -0,0 +1,10 @@ +.deploy_state.json +.DS_Store +__pycache__/ +*.pyc +.venv/ +bin/ +lib/ +include/ +share/ +pyvenv.cfg diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/README.md b/03-integrations/gateway/agentcore-tool-search-plugin/README.md new file mode 100644 index 000000000..ae45636e0 --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/README.md @@ -0,0 +1,148 @@ +# Semantic Tool Discovery with AgentCore Gateway Tool Search Plugin + +This sample demonstrates how to build a **Strands agent** that uses **Amazon Bedrock AgentCore Gateway's semantic tool search** to dynamically discover and invoke only the relevant tools for each user request — without loading all tools upfront. + +## What This Sample Does + +When agents have access to many tools (tens or hundreds), loading all of them for every request is inefficient and can degrade model performance. This sample shows how the `AgentCoreToolSearchPlugin` solves this problem by: + +1. **Deploying a Lambda function** with 34 travel-domain tools across 9 domains (flights, hotels, car rentals, restaurants, currency, loyalty, weather, activities, trip planning) +2. **Registering the function** with AgentCore Gateway as a tool target with semantic search enabled +3. **Creating a Strands agent** that uses the plugin to dynamically discover only the relevant tools per request based on user intent + +The key value: instead of loading all 34 tools for every query, the agent dynamically loads only the relevant tools per request via semantic search — improving response quality and reducing token usage. + +## Architecture + +![Architecture](images/architecture.png) + +On each agent invocation: + +1. **User query** — The user sends a query to the Strands agent +2. **Hook** — The agent triggers the `AgentCoreToolSearchPlugin` before model invocation +3. **Derive intent** — The `IntentProvider` sends the last N messages from conversation history to the configured LLM to produce a concise intent string +4. **Search gateway** — The intent is passed to AgentCore Gateway's `x_amz_bedrock_agentcore_search` tool to obtain the most relevant tools +5. **Invoke LLM** — The agent invokes the LLM with the user query along with the matched tools from registered MCP targets (Lambda, API Gateway, MCP Server) + +Previously loaded tools are cleared before each search, so the agent always has the most relevant tools available. + +## Key Concepts + +| Concept | Description | +|---------|-------------| +| **AgentCore Gateway** | Managed MCP-compatible endpoint that hosts tool targets and provides semantic search | +| **Tool Target** | A Lambda function (or other compute) registered with the gateway, wrapped automatically with MCP protocol | +| **Semantic Tool Search** | Gateway capability that matches user intent to relevant tools using embeddings | +| **AgentCoreToolSearchPlugin** | Strands plugin that hooks into the agent lifecycle to search and load tools dynamically | +| **Intent Provider** | Component that derives a concise intent string from conversation history (defaults to the agent's own model) | + +## Prerequisites + +| Requirement | Details | +|---|---| +| Python | 3.12+ | +| AWS CLI | 2.x, configured with valid credentials | +| AWS Account | With Bedrock model access enabled | + +### Required IAM Permissions + +Your calling identity needs: + +- `lambda:CreateFunction`, `lambda:InvokeFunction`, `lambda:DeleteFunction`, `lambda:UpdateFunctionCode`, `lambda:GetFunction` +- `iam:CreateRole`, `iam:AttachRolePolicy`, `iam:DetachRolePolicy`, `iam:DeleteRole` +- `bedrock:InvokeModel` (for agent reasoning) +- `bedrock-agentcore:*` (for gateway management and MCP connections) + +## Getting Started + +### 1. Install dependencies + +```bash +pip install -r requirements.txt +``` + +### 2. Configure AWS credentials + +```bash +export AWS_REGION=us-east-1 # optional, defaults to us-east-1 +aws sts get-caller-identity # verify credentials +``` + +### 3. Deploy infrastructure + +This creates the Lambda function, AgentCore Gateway, and registers the tools: + +```bash +python deploy.py +``` + +### 4. Run the agent + +This creates a Strands agent with the `AgentCoreToolSearchPlugin` and runs example queries across multiple travel domains: + +```bash +python invoke.py +``` + +You'll see the agent dynamically select different tool subsets for each domain: + +``` + Domain: Flight Search + Query: Find flights from San Francisco to New York next Friday + Tools loaded by semantic search: ['check_availability', 'get_flight_details', 'search_flights'] + + Domain: Hotel Search + Query: Search for hotels in Manhattan with a pool + Tools loaded by semantic search: ['get_hotel_amenities', 'get_hotel_details', 'search_hotels'] +``` + +### 5. Clean up resources + +```bash +python cleanup.py +``` + +## Project Structure + +``` +agentcore-tool-search-plugin/ +├── deploy.py # Deploy Lambda + Gateway + register tools +├── invoke.py # Create agent and run example queries +├── cleanup.py # Delete all created AWS resources +├── config.py # Shared configuration +├── requirements.txt # Python dependencies +├── README.md # This file +└── lambda/ + ├── travel_tools.py # Lambda function with 34 travel tools + └── tool_schemas.json # MCP tool schemas for gateway registration +``` + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `AWS_REGION` | `us-east-1` | AWS region for all resources | +| `MODEL_ID` | `us.anthropic.claude-sonnet-4-20250514-v1:0` | Bedrock model for agent reasoning | + +## Customization + +The `AgentCoreToolSearchPlugin` supports custom models for intent classification, custom system prompts, and fully custom intent providers. For details and code examples, see the [AgentCoreToolSearchPlugin documentation](https://strandsagents.com/docs/community/plugins/agentcore-tool-search/). + +## Cost Considerations + +Running this sample creates AWS resources that incur charges: + +- **AWS Lambda** — Function invocations during verification and agent tool calls +- **Amazon Bedrock** — Model inference for agent reasoning and intent classification +- **AgentCore Gateway** — Gateway usage for tool registration and MCP-based invocations + +Always run `python cleanup.py` when finished to delete all resources. + +## Related Resources + +- [AgentCoreToolSearchPlugin documentation (Strands)](https://strandsagents.com/docs/community/plugins/agentcore-tool-search/) +- [AgentCoreToolSearchPlugin source code](https://github.com/aws/bedrock-agentcore-sdk-python/tree/main/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search) +- [AgentCore Gateway — Semantic Tool Search](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-mcp-semantic-search.html) +- [Strands Agents SDK](https://strandsagents.com/) \ No newline at end of file diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/cleanup.py b/03-integrations/gateway/agentcore-tool-search-plugin/cleanup.py new file mode 100644 index 000000000..38d198caf --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/cleanup.py @@ -0,0 +1,96 @@ +""" +Delete all AWS resources created by this sample. + +Usage: + python cleanup.py +""" + +import json +import sys +import time + +import boto3 + +from config import ( + AWS_REGION, + GATEWAY_NAME, + GATEWAY_ROLE_NAME, + LAMBDA_FUNCTION_NAME, + LAMBDA_ROLE_NAME, + STATE_FILE, +) + + +def cleanup(): + if not __import__("os").path.exists(STATE_FILE): + print("[ERROR] No deployment state found. Nothing to clean up.") + sys.exit(1) + + with open(STATE_FILE) as f: + state = json.load(f) + + print("=" * 60) + print("CLEANING UP RESOURCES") + print("=" * 60) + + iam = boto3.client("iam", region_name=AWS_REGION) + lambda_client = boto3.client("lambda", region_name=AWS_REGION) + agentcore_control = boto3.client("bedrock-agentcore-control", region_name=AWS_REGION) + gateway_id = state["gateway_id"] + + # Deregister tool targets + try: + targets = agentcore_control.list_gateway_targets(gatewayIdentifier=gateway_id) + for target in targets.get("items", []): + agentcore_control.delete_gateway_target( + gatewayIdentifier=gateway_id, targetId=target["targetId"] + ) + print(" [OK] Deregistered tool targets from gateway") + except Exception as e: + print(f" [WARN] Error deregistering targets: {e}") + + # Delete gateway + time.sleep(10) + try: + agentcore_control.delete_gateway(gatewayIdentifier=gateway_id) + print(f" [OK] Deleted gateway: {GATEWAY_NAME}") + except Exception as e: + print(f" [WARN] Error deleting gateway: {e}") + + # Delete Lambda function + try: + lambda_client.delete_function(FunctionName=LAMBDA_FUNCTION_NAME) + print(f" [OK] Deleted Lambda: {LAMBDA_FUNCTION_NAME}") + except Exception as e: + print(f" [WARN] Error deleting Lambda: {e}") + + # Delete Lambda IAM role + try: + iam.detach_role_policy( + RoleName=LAMBDA_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ) + iam.delete_role(RoleName=LAMBDA_ROLE_NAME) + print(f" [OK] Deleted IAM role: {LAMBDA_ROLE_NAME}") + except Exception as e: + print(f" [WARN] Error deleting Lambda role: {e}") + + # Delete Gateway IAM role + try: + iam.detach_role_policy( + RoleName=GATEWAY_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaRole", + ) + iam.delete_role(RoleName=GATEWAY_ROLE_NAME) + print(f" [OK] Deleted IAM role: {GATEWAY_ROLE_NAME}") + except Exception as e: + print(f" [WARN] Error deleting Gateway role: {e}") + + # Remove state file + __import__("os").unlink(STATE_FILE) + print() + print(" [OK] Cleanup complete. All resources removed.") + + +if __name__ == "__main__": + cleanup() diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/config.py b/03-integrations/gateway/agentcore-tool-search-plugin/config.py new file mode 100644 index 000000000..fb6945d21 --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/config.py @@ -0,0 +1,12 @@ +"""Shared configuration for the AgentCore Tool Search Plugin sample.""" + +import os + +AWS_REGION = os.environ.get("AWS_REGION", "us-east-1") +LAMBDA_FUNCTION_NAME = "agentcore-travel-tools" +LAMBDA_ROLE_NAME = "agentcore-travel-tools-role" +GATEWAY_NAME = "agentcore-travel-gateway" +GATEWAY_ROLE_NAME = "agentcore-travel-gateway-role" +MODEL_ID = os.environ.get("MODEL_ID", "us.anthropic.claude-sonnet-4-20250514-v1:0") +STATE_FILE = os.path.join(os.path.dirname(__file__), ".deploy_state.json") +LAMBDA_DIR = os.path.join(os.path.dirname(__file__), "lambda") diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/deploy.py b/03-integrations/gateway/agentcore-tool-search-plugin/deploy.py new file mode 100644 index 000000000..3296dde99 --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/deploy.py @@ -0,0 +1,258 @@ +""" +Deploy Lambda function, create AgentCore Gateway, and register tools. + +Usage: + python deploy.py +""" + +import io +import json +import time +import zipfile + +import boto3 + +from config import ( + AWS_REGION, + GATEWAY_NAME, + GATEWAY_ROLE_NAME, + LAMBDA_DIR, + LAMBDA_FUNCTION_NAME, + LAMBDA_ROLE_NAME, + STATE_FILE, +) + + +def deploy(): + print("=" * 60) + print("STEP 1: Verify AWS Credentials") + print("=" * 60) + + sts = boto3.client("sts") + identity = sts.get_caller_identity() + print(f" Account: {identity['Account']}") + print(f" ARN: {identity['Arn']}") + print(f" Region: {AWS_REGION}") + print() + + iam = boto3.client("iam", region_name=AWS_REGION) + lambda_client = boto3.client("lambda", region_name=AWS_REGION) + agentcore_control = boto3.client("bedrock-agentcore-control", region_name=AWS_REGION) + + # --- Create Lambda execution role --- + print("=" * 60) + print("STEP 2: Create Lambda Execution Role") + print("=" * 60) + + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + + try: + role_response = iam.create_role( + RoleName=LAMBDA_ROLE_NAME, + AssumeRolePolicyDocument=json.dumps(trust_policy), + Description="Execution role for AgentCore travel tools Lambda", + ) + role_arn = role_response["Role"]["Arn"] + iam.attach_role_policy( + RoleName=LAMBDA_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ) + print(f" [OK] Created IAM role: {LAMBDA_ROLE_NAME}") + print(" Waiting 10s for IAM propagation...") + time.sleep(10) + except iam.exceptions.EntityAlreadyExistsException: + role_arn = f"arn:aws:iam::{identity['Account']}:role/{LAMBDA_ROLE_NAME}" + print(f" [INFO] IAM role already exists: {LAMBDA_ROLE_NAME}") + print() + + # --- Deploy Lambda function --- + print("=" * 60) + print("STEP 3: Deploy Lambda Function") + print("=" * 60) + + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf: + zf.write(f"{LAMBDA_DIR}/travel_tools.py", "travel_tools.py") + zip_buffer.seek(0) + + try: + response = lambda_client.create_function( + FunctionName=LAMBDA_FUNCTION_NAME, + Runtime="python3.12", + Role=role_arn, + Handler="travel_tools.lambda_handler", + Code={"ZipFile": zip_buffer.read()}, + Description="Travel domain tools for AgentCore Gateway", + Timeout=30, + MemorySize=256, + ) + lambda_arn = response["FunctionArn"] + print(f" [OK] Created Lambda: {LAMBDA_FUNCTION_NAME}") + except lambda_client.exceptions.ResourceConflictException: + zip_buffer.seek(0) + lambda_client.update_function_code( + FunctionName=LAMBDA_FUNCTION_NAME, ZipFile=zip_buffer.read() + ) + func_info = lambda_client.get_function(FunctionName=LAMBDA_FUNCTION_NAME) + lambda_arn = func_info["Configuration"]["FunctionArn"] + print(f" [INFO] Updated existing Lambda: {LAMBDA_FUNCTION_NAME}") + print(f" ARN: {lambda_arn}") + + # Wait for Lambda to become Active + print(" Waiting for Lambda to become Active...") + waiter = lambda_client.get_waiter("function_active_v2") + waiter.wait(FunctionName=LAMBDA_FUNCTION_NAME) + print(" [OK] Lambda is Active") + + # Verify deployment + test_event = {"tool_name": "get_supported_currencies"} + resp = lambda_client.invoke( + FunctionName=LAMBDA_FUNCTION_NAME, Payload=json.dumps(test_event) + ) + payload = json.loads(resp["Payload"].read()) + print(f" [OK] Verified: {payload['total']} currencies available") + print() + + # --- Create Gateway --- + print("=" * 60) + print("STEP 4: Create AgentCore Gateway") + print("=" * 60) + + gateway_trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "bedrock-agentcore.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + + try: + gw_role_response = iam.create_role( + RoleName=GATEWAY_ROLE_NAME, + AssumeRolePolicyDocument=json.dumps(gateway_trust_policy), + Description="Role for AgentCore Gateway to invoke Lambda targets", + ) + gateway_role_arn = gw_role_response["Role"]["Arn"] + iam.attach_role_policy( + RoleName=GATEWAY_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaRole", + ) + print(f" [OK] Created gateway role: {GATEWAY_ROLE_NAME}") + time.sleep(10) + except iam.exceptions.EntityAlreadyExistsException: + gateway_role_arn = f"arn:aws:iam::{identity['Account']}:role/{GATEWAY_ROLE_NAME}" + print(f" [INFO] Gateway role already exists: {GATEWAY_ROLE_NAME}") + + try: + gateway_response = agentcore_control.create_gateway( + name=GATEWAY_NAME, + roleArn=gateway_role_arn, + authorizerType="AWS_IAM", + protocolType="MCP", + protocolConfiguration={"mcp": {"searchType": "SEMANTIC"}}, + description="Travel domain gateway for semantic tool search demo", + ) + gateway_id = gateway_response["gatewayId"] + gateway_endpoint = gateway_response["gatewayUrl"] + print(f" [OK] Created gateway: {GATEWAY_NAME}") + except Exception as e: + if "already exists" in str(e).lower() or "conflict" in str(e).lower(): + gateways = agentcore_control.list_gateways() + for gw in gateways.get("items", []): + if gw.get("name") == GATEWAY_NAME: + gateway_id = gw["gatewayId"] + gw_detail = agentcore_control.get_gateway(gatewayIdentifier=gateway_id) + gateway_endpoint = gw_detail["gatewayUrl"] + break + print(f" [INFO] Gateway already exists: {GATEWAY_NAME}") + else: + raise + + print(f" Gateway ID: {gateway_id}") + print(f" Endpoint: {gateway_endpoint}") + + # Wait for gateway to become ACTIVE + print(" Waiting for gateway to become ACTIVE...") + for _ in range(60): + gw_detail = agentcore_control.get_gateway(gatewayIdentifier=gateway_id) + status = gw_detail.get("status", "UNKNOWN") + if status == "ACTIVE" or status == "READY": + break + time.sleep(5) + else: + print(f" [WARN] Gateway still in {status} state after timeout") + print(f" [OK] Gateway is {status}") + print() + + # --- Register tools --- + print("=" * 60) + print("STEP 5: Register Lambda as Tool Target") + print("=" * 60) + + with open(f"{LAMBDA_DIR}/tool_schemas.json", "r") as f: + tool_schemas = json.load(f) + + print(f" Loaded {len(tool_schemas)} tool schemas") + + try: + agentcore_control.create_gateway_target( + gatewayIdentifier=gateway_id, + name=LAMBDA_FUNCTION_NAME, + targetConfiguration={ + "mcp": { + "lambda": { + "lambdaArn": lambda_arn, + "toolSchema": {"inlinePayload": tool_schemas}, + } + } + }, + credentialProviderConfigurations=[ + {"credentialProviderType": "GATEWAY_IAM_ROLE"} + ], + description="Travel domain tools - flights, hotels, car rentals, restaurants, currency, loyalty, weather, activities, trip planning", + ) + print(f" [OK] Registered {len(tool_schemas)} tools with gateway") + except Exception as e: + if "already exists" in str(e).lower() or "conflict" in str(e).lower(): + print(" [INFO] Tool target already registered") + else: + raise + + # Verify registration + time.sleep(5) + targets = agentcore_control.list_gateway_targets(gatewayIdentifier=gateway_id) + print(f" [OK] Gateway has {len(targets.get('items', []))} registered target(s)") + print() + + # Save state for invoke/cleanup + state = { + "gateway_id": gateway_id, + "gateway_endpoint": gateway_endpoint, + "lambda_arn": lambda_arn, + } + with open(STATE_FILE, "w") as f: + json.dump(state, f) + + print("=" * 60) + print("DEPLOYMENT COMPLETE") + print("=" * 60) + print(f" Gateway endpoint: {gateway_endpoint}") + print(f" Tools registered: {len(tool_schemas)}") + print() + print(" Run 'python invoke.py' to test the agent") + + +if __name__ == "__main__": + deploy() diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/images/architecture.png b/03-integrations/gateway/agentcore-tool-search-plugin/images/architecture.png new file mode 100644 index 000000000..4e29dc45c Binary files /dev/null and b/03-integrations/gateway/agentcore-tool-search-plugin/images/architecture.png differ diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/invoke.py b/03-integrations/gateway/agentcore-tool-search-plugin/invoke.py new file mode 100644 index 000000000..77214c6d8 --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/invoke.py @@ -0,0 +1,90 @@ +""" +Create a Strands agent with AgentCoreToolSearchPlugin and run example queries. + +Usage: + python invoke.py +""" + +import json +import sys + +from config import AWS_REGION, MODEL_ID, STATE_FILE + + +def invoke(): + if not __import__("os").path.exists(STATE_FILE): + print("[ERROR] No deployment state found. Run 'python deploy.py' first.") + sys.exit(1) + + with open(STATE_FILE) as f: + state = json.load(f) + + gateway_endpoint = state["gateway_endpoint"] + + print("=" * 60) + print("Creating Strands Agent with AgentCoreToolSearchPlugin") + print("=" * 60) + print(f" Gateway: {gateway_endpoint}") + print(f" Model: {MODEL_ID}") + print() + + from strands import Agent + from strands.tools.mcp import MCPClient + from bedrock_agentcore.gateway.integrations.strands.plugins import ( + AgentCoreToolSearchPlugin, + ) + from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client + + # Connect to AgentCore Gateway via MCP + mcp_client = MCPClient( + lambda: aws_iam_streamablehttp_client( + endpoint=gateway_endpoint, + aws_region=AWS_REGION, + aws_service="bedrock-agentcore", + ) + ) + mcp_client.start() + print(" [OK] MCPClient connected to AgentCore Gateway") + print() + + # Create agent with semantic tool search plugin + agent = Agent(plugins=[AgentCoreToolSearchPlugin(mcp_client=mcp_client)]) + print(" [OK] Agent created with AgentCoreToolSearchPlugin") + print() + + # --- Run example queries across different travel domains --- + examples = [ + ("Flight Search", "Find flights from San Francisco to New York next Friday"), + ("Hotel Search", "Search for hotels in Manhattan with a pool"), + ("Car Rental", "Find available car rentals at JFK airport for next week"), + ("Restaurant Search", "Find Italian restaurants near Times Square with good reviews"), + ("Currency Conversion", "Convert 500 USD to EUR and show me the current exchange rate"), + ("Loyalty Program", "Check my loyalty points balance and what rewards I can redeem"), + ] + + for domain, query in examples: + print("-" * 60) + print(f" Domain: {domain}") + print(f" Query: {query}") + print("-" * 60) + + response = agent(query) + + # Show which tools were dynamically loaded + tools_config = agent.tool_registry.get_all_tools_config() + print() + print(" ┌─ Tools loaded by semantic search ─────────────────────────") + for tool_name in sorted(tools_config.keys()): + print(f" │ • {tool_name}") + print(" └───────────────────────────────────────────────────────────") + print() + print(f" Response: {response}") + print() + + # Cleanup MCP connection + mcp_client.__exit__(None, None, None) + print(" [OK] MCPClient disconnected") + + +if __name__ == "__main__": + invoke() diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/lambda/tool_schemas.json b/03-integrations/gateway/agentcore-tool-search-plugin/lambda/tool_schemas.json new file mode 100644 index 000000000..abf1db477 --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/lambda/tool_schemas.json @@ -0,0 +1,493 @@ +[ + { + "name": "search_flights", + "description": "Search for available flights based on origin, destination, and date", + "inputSchema": { + "type": "object", + "properties": { + "origin": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "date": { + "type": "string" + } + } + } + }, + { + "name": "get_flight_details", + "description": "Get detailed information about a specific flight", + "inputSchema": { + "type": "object", + "properties": { + "flight_id": { + "type": "string" + } + } + } + }, + { + "name": "check_availability", + "description": "Check seat availability for a specific flight", + "inputSchema": { + "type": "object", + "properties": { + "flight_id": { + "type": "string" + }, + "class": { + "type": "string" + } + } + } + }, + { + "name": "get_seat_map", + "description": "Get the seat map for a specific flight", + "inputSchema": { + "type": "object", + "properties": { + "flight_id": { + "type": "string" + } + } + } + }, + { + "name": "get_baggage_policy", + "description": "Get baggage policy for a specific airline", + "inputSchema": { + "type": "object", + "properties": { + "airline": { + "type": "string" + } + } + } + }, + { + "name": "get_flight_status", + "description": "Get real-time status of a specific flight", + "inputSchema": { + "type": "object", + "properties": { + "flight_id": { + "type": "string" + } + } + } + }, + { + "name": "search_connecting_flights", + "description": "Search for connecting flight options between two cities", + "inputSchema": { + "type": "object", + "properties": { + "origin": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "date": { + "type": "string" + } + } + } + }, + { + "name": "get_airline_info", + "description": "Get information about a specific airline", + "inputSchema": { + "type": "object", + "properties": { + "airline": { + "type": "string" + } + } + } + }, + { + "name": "compare_flight_prices", + "description": "Compare prices for the same route across multiple airlines", + "inputSchema": { + "type": "object", + "properties": { + "origin": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "date": { + "type": "string" + } + } + } + }, + { + "name": "search_hotels", + "description": "Search for hotels in a specified location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "check_in": { + "type": "string" + }, + "check_out": { + "type": "string" + } + } + } + }, + { + "name": "get_hotel_details", + "description": "Get detailed information about a specific hotel", + "inputSchema": { + "type": "object", + "properties": { + "hotel_id": { + "type": "string" + } + } + } + }, + { + "name": "check_room_availability", + "description": "Check room availability for specific dates", + "inputSchema": { + "type": "object", + "properties": { + "hotel_id": { + "type": "string" + }, + "check_in": { + "type": "string" + }, + "check_out": { + "type": "string" + } + } + } + }, + { + "name": "get_hotel_amenities", + "description": "Get detailed amenity information for a hotel", + "inputSchema": { + "type": "object", + "properties": { + "hotel_id": { + "type": "string" + } + } + } + }, + { + "name": "search_car_rentals", + "description": "Search for available car rentals at a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "pickup_date": { + "type": "string" + }, + "return_date": { + "type": "string" + } + } + } + }, + { + "name": "get_rental_details", + "description": "Get detailed information about a specific car rental", + "inputSchema": { + "type": "object", + "properties": { + "rental_id": { + "type": "string" + } + } + } + }, + { + "name": "check_car_availability", + "description": "Check availability of a specific car rental", + "inputSchema": { + "type": "object", + "properties": { + "rental_id": { + "type": "string" + }, + "pickup_date": { + "type": "string" + }, + "return_date": { + "type": "string" + } + } + } + }, + { + "name": "search_restaurants", + "description": "Search for restaurants in a specified area", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "cuisine": { + "type": "string" + } + } + } + }, + { + "name": "get_restaurant_details", + "description": "Get detailed information about a specific restaurant", + "inputSchema": { + "type": "object", + "properties": { + "restaurant_id": { + "type": "string" + } + } + } + }, + { + "name": "get_menu", + "description": "Get the menu for a specific restaurant", + "inputSchema": { + "type": "object", + "properties": { + "restaurant_id": { + "type": "string" + } + } + } + }, + { + "name": "check_reservations", + "description": "Check reservation availability at a restaurant", + "inputSchema": { + "type": "object", + "properties": { + "restaurant_id": { + "type": "string" + }, + "date": { + "type": "string" + }, + "party_size": { + "type": "integer" + } + } + } + }, + { + "name": "get_restaurant_reviews", + "description": "Get reviews for a specific restaurant", + "inputSchema": { + "type": "object", + "properties": { + "restaurant_id": { + "type": "string" + } + } + } + }, + { + "name": "convert_currency", + "description": "Convert an amount from one currency to another", + "inputSchema": { + "type": "object", + "properties": { + "amount": { + "type": "number" + }, + "from_currency": { + "type": "string" + }, + "to_currency": { + "type": "string" + } + } + } + }, + { + "name": "get_exchange_rates", + "description": "Get current exchange rates for a base currency", + "inputSchema": { + "type": "object", + "properties": { + "base_currency": { + "type": "string" + } + } + } + }, + { + "name": "get_supported_currencies", + "description": "Get list of supported currencies for conversion", + "inputSchema": { + "type": "object", + "properties": {} + } + }, + { + "name": "get_loyalty_balance", + "description": "Get loyalty program points balance for a member", + "inputSchema": { + "type": "object", + "properties": { + "member_id": { + "type": "string" + }, + "program": { + "type": "string" + } + } + } + }, + { + "name": "redeem_points", + "description": "Redeem loyalty points for rewards", + "inputSchema": { + "type": "object", + "properties": { + "member_id": { + "type": "string" + }, + "points": { + "type": "integer" + }, + "reward_type": { + "type": "string" + } + } + } + }, + { + "name": "get_loyalty_program_info", + "description": "Get information about a loyalty program", + "inputSchema": { + "type": "object", + "properties": { + "program": { + "type": "string" + } + } + } + }, + { + "name": "get_weather_forecast", + "description": "Get weather forecast for a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "days": { + "type": "integer" + } + } + } + }, + { + "name": "get_current_weather", + "description": "Get current weather conditions for a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + } + } + } + }, + { + "name": "search_activities", + "description": "Search for activities and attractions in a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "category": { + "type": "string" + } + } + } + }, + { + "name": "get_activity_details", + "description": "Get detailed information about a specific activity", + "inputSchema": { + "type": "object", + "properties": { + "activity_id": { + "type": "string" + } + } + } + }, + { + "name": "check_activity_availability", + "description": "Check availability for a specific activity on a date", + "inputSchema": { + "type": "object", + "properties": { + "activity_id": { + "type": "string" + }, + "date": { + "type": "string" + }, + "participants": { + "type": "integer" + } + } + } + }, + { + "name": "create_itinerary", + "description": "Create a trip itinerary based on destination and preferences", + "inputSchema": { + "type": "object", + "properties": { + "destination": { + "type": "string" + }, + "days": { + "type": "integer" + } + } + } + }, + { + "name": "get_travel_tips", + "description": "Get travel tips and recommendations for a destination", + "inputSchema": { + "type": "object", + "properties": { + "destination": { + "type": "string" + }, + "category": { + "type": "string" + } + } + } + } +] \ No newline at end of file diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/lambda/travel_tools.py b/03-integrations/gateway/agentcore-tool-search-plugin/lambda/travel_tools.py new file mode 100644 index 000000000..b0638016b --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/lambda/travel_tools.py @@ -0,0 +1,1221 @@ +""" +Travel Tools Lambda Function for Amazon Bedrock AgentCore Gateway + +This is a plain AWS Lambda function that serves as a tool target for AgentCore Gateway. +It does NOT implement the MCP protocol itself — AgentCore Gateway handles MCP protocol +translation and routes tool calls to this function. + +Input format from AgentCore Gateway: +- Event: A map of tool input properties (e.g., {"destination": "New York", "date": "2025-03-15"}) +- Context: Contains metadata including the tool name in context.client_context.custom['bedrockAgentCoreToolName'] + The tool name format is: ${target_name}___${tool_name} + +The function extracts the tool name from context, strips the target prefix, dispatches to +the appropriate domain handler, and returns a JSON response. + +All data is static/in-memory for demonstration purposes. + +Domains covered: +- Flights (9 tools): search, details, availability, seat map, baggage, status, connections, airline info, price comparison +- Hotels (4 tools): search, details, room availability, amenities +- Car Rentals (3 tools): search, details, availability +- Restaurants (5 tools): search, details, menu, reservations, reviews +- Currency (3 tools): convert, exchange rates, supported currencies +- Loyalty (3 tools): balance, redeem, program info +- Weather (2 tools): forecast, current +- Activities (3 tools): search, details, availability +- Trip Planning (2 tools): create itinerary, travel tips +""" + +DELIMITER = "___" + + +def lambda_handler(event, context): + """ + Main Lambda handler. Receives tool invocations from AgentCore Gateway. + + Event: Tool input properties as a flat dict (e.g., {"destination": "New York"}) + Context: client_context.custom contains bedrockAgentCoreToolName with format target___toolname + + Returns a JSON-serializable result (the gateway handles response formatting). + """ + # Extract tool name from context (format: targetName___toolName) + tool_name = "" + try: + original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + tool_name = original_tool_name[original_tool_name.index(DELIMITER) + len(DELIMITER):] + except (AttributeError, KeyError, ValueError): + # Fallback: check if tool_name is passed directly in event (for local testing) + tool_name = event.pop("tool_name", "") + + # The event IS the tool arguments (flat dict of input properties) + arguments = event + + # Dispatch to the appropriate tool handler + handler = TOOL_DISPATCH.get(tool_name) + + if handler is None: + return { + "error": f"Unknown tool: {tool_name}", + "available_tools": list(TOOL_DISPATCH.keys()) + } + + result = handler(arguments) + return result + + +# ============================================================================= +# FLIGHTS DOMAIN (9 tools) +# ============================================================================= + +def search_flights(args): + """Search for available flights based on origin, destination, and date.""" + destination = args.get("destination", "New York") + origin = args.get("origin", "San Francisco") + date = args.get("date", "2025-03-15") + + return { + "flights": [ + { + "flight_id": "FL-201", + "airline": "SkyWest Airlines", + "origin": origin, + "destination": destination, + "departure_time": f"{date}T08:00:00", + "arrival_time": f"{date}T16:30:00", + "duration": "5h 30m", + "price": 349.99, + "currency": "USD", + "class": "Economy", + "stops": 0 + }, + { + "flight_id": "FL-202", + "airline": "Pacific Air", + "origin": origin, + "destination": destination, + "departure_time": f"{date}T11:45:00", + "arrival_time": f"{date}T20:00:00", + "duration": "5h 15m", + "price": 289.50, + "currency": "USD", + "class": "Economy", + "stops": 0 + }, + { + "flight_id": "FL-203", + "airline": "Continental Express", + "origin": origin, + "destination": destination, + "departure_time": f"{date}T14:30:00", + "arrival_time": f"{date}T23:00:00", + "duration": "5h 30m", + "price": 415.00, + "currency": "USD", + "class": "Business", + "stops": 0 + } + ], + "total_results": 3, + "search_criteria": {"origin": origin, "destination": destination, "date": date} + } + + +def get_flight_details(args): + """Get detailed information about a specific flight.""" + flight_id = args.get("flight_id", "FL-201") + + return { + "flight_id": flight_id, + "airline": "SkyWest Airlines", + "flight_number": "SW-1042", + "origin": {"code": "SFO", "name": "San Francisco International", "terminal": "2"}, + "destination": {"code": "JFK", "name": "John F. Kennedy International", "terminal": "4"}, + "departure_time": "2025-03-15T08:00:00", + "arrival_time": "2025-03-15T16:30:00", + "duration": "5h 30m", + "aircraft": "Boeing 737-800", + "amenities": ["Wi-Fi", "In-flight entertainment", "USB charging", "Complimentary snacks"], + "price": {"economy": 349.99, "business": 799.99, "first": 1249.99}, + "currency": "USD" + } + + +def check_availability(args): + """Check seat availability for a specific flight.""" + flight_id = args.get("flight_id", "FL-201") + travel_class = args.get("class", "Economy") + + return { + "flight_id": flight_id, + "class": travel_class, + "available_seats": 42, + "total_seats": 180, + "availability_status": "available", + "fare": 349.99, + "currency": "USD", + "last_updated": "2025-03-10T12:00:00Z" + } + + +def get_seat_map(args): + """Get the seat map for a specific flight.""" + flight_id = args.get("flight_id", "FL-201") + + return { + "flight_id": flight_id, + "aircraft": "Boeing 737-800", + "sections": [ + { + "class": "First", + "rows": "1-4", + "seats_per_row": 4, + "layout": "2-2", + "available": 3 + }, + { + "class": "Business", + "rows": "5-10", + "seats_per_row": 6, + "layout": "3-3", + "available": 12 + }, + { + "class": "Economy", + "rows": "11-35", + "seats_per_row": 6, + "layout": "3-3", + "available": 42 + } + ], + "exit_rows": [12, 22], + "extra_legroom_rows": [12, 13, 22, 23] + } + + +def get_baggage_policy(args): + """Get baggage policy for a specific airline or flight.""" + airline = args.get("airline", "SkyWest Airlines") + + return { + "airline": airline, + "carry_on": { + "allowed": True, + "max_weight_kg": 10, + "max_dimensions_cm": "55x40x20", + "personal_item": True + }, + "checked_baggage": { + "first_bag_fee": 30.00, + "second_bag_fee": 45.00, + "max_weight_kg": 23, + "max_dimensions_cm": "158 linear cm" + }, + "overweight_fee": 100.00, + "oversize_fee": 150.00, + "special_items": { + "sports_equipment": 75.00, + "musical_instruments": "Carry-on or purchased seat", + "pets": 125.00 + }, + "currency": "USD" + } + + +def get_flight_status(args): + """Get real-time status of a specific flight.""" + flight_id = args.get("flight_id", "FL-201") + + return { + "flight_id": flight_id, + "flight_number": "SW-1042", + "status": "On Time", + "departure": { + "airport": "SFO", + "scheduled": "2025-03-15T08:00:00", + "estimated": "2025-03-15T08:00:00", + "gate": "B22", + "terminal": "2" + }, + "arrival": { + "airport": "JFK", + "scheduled": "2025-03-15T16:30:00", + "estimated": "2025-03-15T16:25:00", + "gate": "A15", + "terminal": "4" + }, + "last_updated": "2025-03-15T07:30:00Z" + } + + +def search_connecting_flights(args): + """Search for connecting flight options between two cities.""" + origin = args.get("origin", "San Francisco") + destination = args.get("destination", "London") + date = args.get("date", "2025-03-15") + + return { + "connections": [ + { + "route_id": "RT-101", + "total_duration": "14h 45m", + "total_price": 689.99, + "legs": [ + { + "flight_id": "FL-301", + "origin": origin, + "destination": "New York (JFK)", + "departure": f"{date}T06:00:00", + "arrival": f"{date}T14:30:00", + "airline": "SkyWest Airlines" + }, + { + "flight_id": "FL-302", + "origin": "New York (JFK)", + "destination": destination, + "departure": f"{date}T17:00:00", + "arrival": "2025-03-16T05:45:00", + "airline": "Atlantic Airways" + } + ], + "layover": "2h 30m at JFK" + }, + { + "route_id": "RT-102", + "total_duration": "13h 20m", + "total_price": 825.00, + "legs": [ + { + "flight_id": "FL-303", + "origin": origin, + "destination": "Chicago (ORD)", + "departure": f"{date}T07:30:00", + "arrival": f"{date}T13:30:00", + "airline": "Continental Express" + }, + { + "flight_id": "FL-304", + "origin": "Chicago (ORD)", + "destination": destination, + "departure": f"{date}T15:00:00", + "arrival": "2025-03-16T04:50:00", + "airline": "Atlantic Airways" + } + ], + "layover": "1h 30m at ORD" + } + ], + "search_criteria": {"origin": origin, "destination": destination, "date": date}, + "currency": "USD" + } + + +def get_airline_info(args): + """Get information about a specific airline.""" + airline = args.get("airline", "SkyWest Airlines") + + airlines_data = { + "SkyWest Airlines": { + "name": "SkyWest Airlines", + "code": "SW", + "country": "United States", + "hub_airports": ["SFO", "LAX", "DEN"], + "alliance": "Star Alliance", + "fleet_size": 245, + "founded": 1972, + "website": "https://www.skywest-example.com", + "rating": 4.2, + "on_time_performance": "82%" + }, + "Pacific Air": { + "name": "Pacific Air", + "code": "PA", + "country": "United States", + "hub_airports": ["LAX", "HNL"], + "alliance": "OneWorld", + "fleet_size": 180, + "founded": 1985, + "website": "https://www.pacificair-example.com", + "rating": 4.0, + "on_time_performance": "79%" + } + } + + return airlines_data.get(airline, { + "name": airline, + "code": "XX", + "country": "Unknown", + "hub_airports": [], + "alliance": "Independent", + "fleet_size": 0, + "founded": None, + "website": None, + "rating": None, + "on_time_performance": "N/A" + }) + + +def compare_flight_prices(args): + """Compare prices for the same route across multiple airlines.""" + origin = args.get("origin", "San Francisco") + destination = args.get("destination", "New York") + date = args.get("date", "2025-03-15") + + return { + "route": {"origin": origin, "destination": destination, "date": date}, + "comparisons": [ + {"airline": "Pacific Air", "price": 289.50, "class": "Economy", "duration": "5h 15m", "stops": 0}, + {"airline": "SkyWest Airlines", "price": 349.99, "class": "Economy", "duration": "5h 30m", "stops": 0}, + {"airline": "Continental Express", "price": 415.00, "class": "Business", "duration": "5h 30m", "stops": 0}, + {"airline": "Budget Wings", "price": 199.99, "class": "Economy", "duration": "7h 45m", "stops": 1} + ], + "cheapest": {"airline": "Budget Wings", "price": 199.99}, + "fastest": {"airline": "Pacific Air", "duration": "5h 15m"}, + "currency": "USD" + } + + +# ============================================================================= +# HOTELS DOMAIN (4 tools) +# ============================================================================= + +def search_hotels(args): + """Search for hotels in a specified location.""" + location = args.get("location", "Manhattan, New York") + check_in = args.get("check_in", "2025-03-15") + check_out = args.get("check_out", "2025-03-18") + guests = args.get("guests", 2) + + return { + "hotels": [ + { + "hotel_id": "HT-101", + "name": "Grand Central Hotel", + "location": location, + "rating": 4.5, + "stars": 4, + "price_per_night": 259.00, + "total_price": 777.00, + "amenities": ["Pool", "Gym", "Spa", "Restaurant", "Free Wi-Fi"], + "distance_to_center": "0.3 miles" + }, + { + "hotel_id": "HT-102", + "name": "The Metropolitan Inn", + "location": location, + "rating": 4.2, + "stars": 3, + "price_per_night": 189.00, + "total_price": 567.00, + "amenities": ["Gym", "Restaurant", "Free Wi-Fi", "Business Center"], + "distance_to_center": "0.8 miles" + }, + { + "hotel_id": "HT-103", + "name": "Luxury Suites NYC", + "location": location, + "rating": 4.8, + "stars": 5, + "price_per_night": 499.00, + "total_price": 1497.00, + "amenities": ["Pool", "Gym", "Spa", "Restaurant", "Rooftop Bar", "Concierge", "Free Wi-Fi"], + "distance_to_center": "0.1 miles" + } + ], + "total_results": 3, + "search_criteria": { + "location": location, + "check_in": check_in, + "check_out": check_out, + "guests": guests + }, + "currency": "USD" + } + + +def get_hotel_details(args): + """Get detailed information about a specific hotel.""" + hotel_id = args.get("hotel_id", "HT-101") + + return { + "hotel_id": hotel_id, + "name": "Grand Central Hotel", + "address": "123 Park Avenue, Manhattan, New York, NY 10017", + "phone": "+1-212-555-0100", + "rating": 4.5, + "stars": 4, + "total_reviews": 2847, + "check_in_time": "15:00", + "check_out_time": "11:00", + "room_types": [ + {"type": "Standard", "price": 259.00, "max_guests": 2, "beds": "1 King"}, + {"type": "Deluxe", "price": 349.00, "max_guests": 3, "beds": "1 King + 1 Sofa"}, + {"type": "Suite", "price": 549.00, "max_guests": 4, "beds": "2 Kings"} + ], + "amenities": ["Pool", "Gym", "Spa", "Restaurant", "Bar", "Free Wi-Fi", "Parking", "Concierge"], + "policies": { + "cancellation": "Free cancellation up to 24 hours before check-in", + "pets": "Pets allowed ($50/night fee)", + "smoking": "Non-smoking property" + }, + "currency": "USD" + } + + +def check_room_availability(args): + """Check room availability for specific dates.""" + hotel_id = args.get("hotel_id", "HT-101") + check_in = args.get("check_in", "2025-03-15") + check_out = args.get("check_out", "2025-03-18") + room_type = args.get("room_type", "Standard") + + return { + "hotel_id": hotel_id, + "room_type": room_type, + "check_in": check_in, + "check_out": check_out, + "available": True, + "rooms_remaining": 5, + "price_per_night": 259.00, + "total_price": 777.00, + "nights": 3, + "includes_breakfast": True, + "currency": "USD" + } + + +def get_hotel_amenities(args): + """Get detailed amenity information for a hotel.""" + hotel_id = args.get("hotel_id", "HT-101") + + return { + "hotel_id": hotel_id, + "amenities": { + "pool": {"available": True, "type": "Indoor heated", "hours": "6:00 AM - 10:00 PM"}, + "gym": {"available": True, "hours": "24/7", "equipment": ["Treadmills", "Weights", "Yoga studio"]}, + "spa": {"available": True, "hours": "9:00 AM - 8:00 PM", "services": ["Massage", "Facial", "Sauna"]}, + "restaurant": {"available": True, "name": "The Park Grill", "cuisine": "American", "hours": "6:30 AM - 11:00 PM"}, + "wifi": {"available": True, "free": True, "speed": "100 Mbps"}, + "parking": {"available": True, "type": "Valet", "price_per_day": 45.00}, + "business_center": {"available": True, "hours": "24/7", "services": ["Printing", "Meeting rooms"]}, + "concierge": {"available": True, "hours": "24/7"} + }, + "currency": "USD" + } + + +# ============================================================================= +# CAR RENTALS DOMAIN (3 tools) +# ============================================================================= + +def search_car_rentals(args): + """Search for available car rentals at a location.""" + location = args.get("location", "JFK Airport") + pickup_date = args.get("pickup_date", "2025-03-15") + return_date = args.get("return_date", "2025-03-18") + + return { + "rentals": [ + { + "rental_id": "CR-101", + "company": "National Car Rental", + "car_type": "Economy", + "model": "Toyota Corolla", + "price_per_day": 45.00, + "total_price": 135.00, + "features": ["Automatic", "A/C", "4 seats", "2 bags"], + "pickup_location": location + }, + { + "rental_id": "CR-102", + "company": "Premier Auto Rentals", + "car_type": "SUV", + "model": "Ford Explorer", + "price_per_day": 85.00, + "total_price": 255.00, + "features": ["Automatic", "A/C", "7 seats", "4 bags", "GPS"], + "pickup_location": location + }, + { + "rental_id": "CR-103", + "company": "Luxury Drive", + "car_type": "Luxury", + "model": "BMW 5 Series", + "price_per_day": 150.00, + "total_price": 450.00, + "features": ["Automatic", "A/C", "5 seats", "3 bags", "GPS", "Leather seats"], + "pickup_location": location + } + ], + "total_results": 3, + "search_criteria": { + "location": location, + "pickup_date": pickup_date, + "return_date": return_date + }, + "currency": "USD" + } + + +def get_rental_details(args): + """Get detailed information about a specific car rental.""" + rental_id = args.get("rental_id", "CR-101") + + return { + "rental_id": rental_id, + "company": "National Car Rental", + "car_type": "Economy", + "model": "Toyota Corolla", + "year": 2024, + "color": "Silver", + "features": ["Automatic transmission", "Air conditioning", "Bluetooth", "USB charging", "Backup camera"], + "capacity": {"passengers": 4, "bags_large": 1, "bags_small": 2}, + "fuel_policy": "Full-to-full", + "mileage": "Unlimited", + "insurance_options": [ + {"type": "Basic", "price_per_day": 15.00, "coverage": "Liability only"}, + {"type": "Full", "price_per_day": 30.00, "coverage": "Comprehensive + collision"} + ], + "pickup_location": {"address": "JFK Airport, Terminal 4, Level 1", "hours": "24/7"}, + "requirements": {"min_age": 21, "license": "Valid driver's license", "deposit": 200.00}, + "price_per_day": 45.00, + "currency": "USD" + } + + +def check_car_availability(args): + """Check availability of a specific car rental.""" + rental_id = args.get("rental_id", "CR-101") + pickup_date = args.get("pickup_date", "2025-03-15") + return_date = args.get("return_date", "2025-03-18") + + return { + "rental_id": rental_id, + "available": True, + "cars_remaining": 8, + "pickup_date": pickup_date, + "return_date": return_date, + "price_per_day": 45.00, + "total_days": 3, + "total_price": 135.00, + "extras_available": ["GPS ($10/day)", "Child seat ($8/day)", "Additional driver ($12/day)"], + "currency": "USD" + } + + +# ============================================================================= +# RESTAURANTS DOMAIN (5 tools) +# ============================================================================= + +def search_restaurants(args): + """Search for restaurants in a specified area.""" + location = args.get("location", "Times Square, New York") + cuisine = args.get("cuisine", "Italian") + + return { + "restaurants": [ + { + "restaurant_id": "RS-101", + "name": "Bella Italia", + "cuisine": cuisine, + "location": location, + "rating": 4.6, + "price_range": "$$$", + "distance": "0.2 miles", + "open_now": True + }, + { + "restaurant_id": "RS-102", + "name": "Trattoria Roma", + "cuisine": cuisine, + "location": location, + "rating": 4.3, + "price_range": "$$", + "distance": "0.5 miles", + "open_now": True + }, + { + "restaurant_id": "RS-103", + "name": "Il Palazzo", + "cuisine": cuisine, + "location": location, + "rating": 4.8, + "price_range": "$$$$", + "distance": "0.7 miles", + "open_now": False + } + ], + "total_results": 3, + "search_criteria": {"location": location, "cuisine": cuisine} + } + + +def get_restaurant_details(args): + """Get detailed information about a specific restaurant.""" + restaurant_id = args.get("restaurant_id", "RS-101") + + return { + "restaurant_id": restaurant_id, + "name": "Bella Italia", + "cuisine": "Italian", + "address": "456 Broadway, Times Square, New York, NY 10036", + "phone": "+1-212-555-0200", + "rating": 4.6, + "total_reviews": 1523, + "price_range": "$$$", + "hours": { + "monday_friday": "11:00 AM - 11:00 PM", + "saturday": "10:00 AM - 12:00 AM", + "sunday": "10:00 AM - 10:00 PM" + }, + "features": ["Outdoor seating", "Private dining", "Full bar", "Live music on weekends"], + "dress_code": "Smart casual", + "reservations": "Recommended", + "parking": "Street parking available" + } + + +def get_menu(args): + """Get the menu for a specific restaurant.""" + restaurant_id = args.get("restaurant_id", "RS-101") + + return { + "restaurant_id": restaurant_id, + "restaurant_name": "Bella Italia", + "menu": { + "appetizers": [ + {"name": "Bruschetta", "price": 12.00, "description": "Toasted bread with tomatoes, garlic, and basil"}, + {"name": "Calamari Fritti", "price": 15.00, "description": "Crispy fried calamari with marinara sauce"}, + {"name": "Caprese Salad", "price": 14.00, "description": "Fresh mozzarella, tomatoes, and basil"} + ], + "main_courses": [ + {"name": "Spaghetti Carbonara", "price": 22.00, "description": "Classic pasta with pancetta and egg"}, + {"name": "Osso Buco", "price": 34.00, "description": "Braised veal shank with gremolata"}, + {"name": "Margherita Pizza", "price": 18.00, "description": "San Marzano tomatoes, mozzarella, fresh basil"}, + {"name": "Risotto ai Funghi", "price": 24.00, "description": "Arborio rice with wild mushrooms and truffle oil"} + ], + "desserts": [ + {"name": "Tiramisu", "price": 12.00, "description": "Classic Italian coffee-flavored dessert"}, + {"name": "Panna Cotta", "price": 10.00, "description": "Vanilla cream with berry compote"} + ] + }, + "currency": "USD" + } + + +def check_reservations(args): + """Check reservation availability at a restaurant.""" + restaurant_id = args.get("restaurant_id", "RS-101") + date = args.get("date", "2025-03-15") + party_size = args.get("party_size", 2) + time = args.get("time", "19:00") + + return { + "restaurant_id": restaurant_id, + "restaurant_name": "Bella Italia", + "date": date, + "requested_time": time, + "party_size": party_size, + "available": True, + "available_times": ["18:30", "19:00", "19:30", "20:00", "20:30"], + "estimated_wait": "No wait with reservation", + "special_notes": "Window table available for parties of 2" + } + + +def get_restaurant_reviews(args): + """Get reviews for a specific restaurant.""" + restaurant_id = args.get("restaurant_id", "RS-101") + + return { + "restaurant_id": restaurant_id, + "restaurant_name": "Bella Italia", + "average_rating": 4.6, + "total_reviews": 1523, + "reviews": [ + { + "reviewer": "John D.", + "rating": 5, + "date": "2025-02-28", + "comment": "Absolutely fantastic! The carbonara was the best I've had outside of Rome.", + "helpful_votes": 24 + }, + { + "reviewer": "Sarah M.", + "rating": 4, + "date": "2025-02-25", + "comment": "Great food and atmosphere. Service was a bit slow during peak hours.", + "helpful_votes": 18 + }, + { + "reviewer": "Michael R.", + "rating": 5, + "date": "2025-02-20", + "comment": "The tiramisu is to die for. Romantic setting perfect for date night.", + "helpful_votes": 31 + } + ], + "rating_breakdown": {"5_star": 892, "4_star": 421, "3_star": 142, "2_star": 48, "1_star": 20} + } + + +# ============================================================================= +# CURRENCY DOMAIN (3 tools) +# ============================================================================= + +def convert_currency(args): + """Convert an amount from one currency to another.""" + amount = args.get("amount", 100) + from_currency = args.get("from_currency", "USD") + to_currency = args.get("to_currency", "EUR") + + # Static exchange rates for demonstration + rates = { + ("USD", "EUR"): 0.92, + ("USD", "GBP"): 0.79, + ("USD", "JPY"): 149.50, + ("USD", "CAD"): 1.36, + ("USD", "AUD"): 1.53, + ("EUR", "USD"): 1.09, + ("GBP", "USD"): 1.27, + ("JPY", "USD"): 0.0067, + } + + rate = rates.get((from_currency, to_currency), 1.0) + converted_amount = round(amount * rate, 2) + + return { + "from": {"currency": from_currency, "amount": amount}, + "to": {"currency": to_currency, "amount": converted_amount}, + "exchange_rate": rate, + "last_updated": "2025-03-10T12:00:00Z", + "source": "Market rate (demo data)" + } + + +def get_exchange_rates(args): + """Get current exchange rates for a base currency.""" + base_currency = args.get("base_currency", "USD") + + return { + "base_currency": base_currency, + "rates": { + "EUR": 0.92, + "GBP": 0.79, + "JPY": 149.50, + "CAD": 1.36, + "AUD": 1.53, + "CHF": 0.88, + "CNY": 7.24, + "INR": 83.10, + "MXN": 17.15, + "BRL": 4.97 + }, + "last_updated": "2025-03-10T12:00:00Z", + "source": "Demo exchange rates" + } + + +def get_supported_currencies(args): + """Get list of supported currencies for conversion.""" + return { + "currencies": [ + {"code": "USD", "name": "US Dollar", "symbol": "$"}, + {"code": "EUR", "name": "Euro", "symbol": "\u20ac"}, + {"code": "GBP", "name": "British Pound", "symbol": "\u00a3"}, + {"code": "JPY", "name": "Japanese Yen", "symbol": "\u00a5"}, + {"code": "CAD", "name": "Canadian Dollar", "symbol": "C$"}, + {"code": "AUD", "name": "Australian Dollar", "symbol": "A$"}, + {"code": "CHF", "name": "Swiss Franc", "symbol": "CHF"}, + {"code": "CNY", "name": "Chinese Yuan", "symbol": "\u00a5"}, + {"code": "INR", "name": "Indian Rupee", "symbol": "\u20b9"}, + {"code": "MXN", "name": "Mexican Peso", "symbol": "MX$"}, + {"code": "BRL", "name": "Brazilian Real", "symbol": "R$"} + ], + "total": 11 + } + + +# ============================================================================= +# LOYALTY DOMAIN (3 tools) +# ============================================================================= + +def get_loyalty_balance(args): + """Get loyalty program points balance for a member.""" + member_id = args.get("member_id", "LM-12345") + program = args.get("program", "TravelRewards") + + return { + "member_id": member_id, + "program": program, + "points_balance": 47500, + "tier": "Gold", + "tier_progress": {"current": 47500, "next_tier": 75000, "next_tier_name": "Platinum"}, + "points_expiring_soon": {"amount": 5000, "expiry_date": "2025-06-30"}, + "recent_activity": [ + {"date": "2025-03-01", "description": "Flight SFO-JFK", "points": 2500, "type": "earned"}, + {"date": "2025-02-15", "description": "Hotel stay - Grand Central", "points": 1200, "type": "earned"}, + {"date": "2025-02-10", "description": "Car rental redemption", "points": -3000, "type": "redeemed"} + ] + } + + +def redeem_points(args): + """Redeem loyalty points for rewards.""" + member_id = args.get("member_id", "LM-12345") + points = args.get("points", 5000) + reward_type = args.get("reward_type", "flight_discount") + + redemption_values = { + "flight_discount": {"value": points * 0.01, "description": f"${points * 0.01:.2f} off your next flight"}, + "hotel_night": {"value": points * 0.008, "description": f"${points * 0.008:.2f} hotel credit"}, + "car_rental": {"value": points * 0.007, "description": f"${points * 0.007:.2f} rental credit"}, + "gift_card": {"value": points * 0.005, "description": f"${points * 0.005:.2f} gift card"} + } + + reward = redemption_values.get(reward_type, redemption_values["flight_discount"]) + + return { + "member_id": member_id, + "redemption": { + "points_redeemed": points, + "reward_type": reward_type, + "value": reward["value"], + "description": reward["description"], + "status": "confirmed", + "confirmation_code": "RDM-78901" + }, + "remaining_balance": 42500, + "currency": "USD" + } + + +def get_loyalty_program_info(args): + """Get information about a loyalty program.""" + program = args.get("program", "TravelRewards") + + return { + "program": program, + "tiers": [ + {"name": "Silver", "min_points": 0, "benefits": ["Basic earning rate", "Member-only fares"]}, + {"name": "Gold", "min_points": 25000, "benefits": ["1.5x earning rate", "Priority boarding", "Lounge access"]}, + {"name": "Platinum", "min_points": 75000, "benefits": ["2x earning rate", "Free upgrades", "Companion pass"]}, + {"name": "Diamond", "min_points": 150000, "benefits": ["3x earning rate", "All Platinum benefits", "Personal concierge"]} + ], + "earning_rates": { + "flights": "1 point per $1 spent", + "hotels": "2 points per $1 spent", + "car_rentals": "1 point per $2 spent", + "dining": "3 points per $1 spent" + }, + "redemption_options": ["Flight discounts", "Hotel nights", "Car rental credits", "Gift cards", "Experience packages"], + "partners": ["SkyWest Airlines", "Grand Central Hotels", "National Car Rental", "Bella Italia Restaurant Group"] + } + + +# ============================================================================= +# WEATHER DOMAIN (2 tools) +# ============================================================================= + +def get_weather_forecast(args): + """Get weather forecast for a location.""" + location = args.get("location", "New York") + days = args.get("days", 5) + + forecast_data = [ + {"date": "2025-03-15", "high_f": 55, "low_f": 42, "condition": "Partly Cloudy", "precipitation": "10%"}, + {"date": "2025-03-16", "high_f": 58, "low_f": 45, "condition": "Sunny", "precipitation": "0%"}, + {"date": "2025-03-17", "high_f": 52, "low_f": 38, "condition": "Rain", "precipitation": "80%"}, + {"date": "2025-03-18", "high_f": 50, "low_f": 36, "condition": "Cloudy", "precipitation": "30%"}, + {"date": "2025-03-19", "high_f": 60, "low_f": 44, "condition": "Sunny", "precipitation": "5%"}, + {"date": "2025-03-20", "high_f": 62, "low_f": 46, "condition": "Partly Cloudy", "precipitation": "15%"}, + {"date": "2025-03-21", "high_f": 57, "low_f": 41, "condition": "Overcast", "precipitation": "25%"} + ] + + return { + "location": location, + "forecast": forecast_data[:days], + "units": "Fahrenheit", + "source": "Demo weather data" + } + + +def get_current_weather(args): + """Get current weather conditions for a location.""" + location = args.get("location", "New York") + + return { + "location": location, + "current": { + "temperature_f": 54, + "feels_like_f": 50, + "condition": "Partly Cloudy", + "humidity": "62%", + "wind": {"speed_mph": 12, "direction": "NW"}, + "visibility_miles": 10, + "uv_index": 3 + }, + "updated_at": "2025-03-15T10:30:00Z", + "units": "Fahrenheit", + "source": "Demo weather data" + } + + +# ============================================================================= +# ACTIVITIES DOMAIN (3 tools) +# ============================================================================= + +def search_activities(args): + """Search for activities and attractions in a location.""" + location = args.get("location", "New York") + category = args.get("category", "sightseeing") + + return { + "activities": [ + { + "activity_id": "AC-101", + "name": "Statue of Liberty & Ellis Island Tour", + "category": "sightseeing", + "location": location, + "duration": "4 hours", + "price": 45.00, + "rating": 4.7, + "available": True + }, + { + "activity_id": "AC-102", + "name": "Central Park Bike Tour", + "category": "outdoor", + "location": location, + "duration": "2.5 hours", + "price": 35.00, + "rating": 4.5, + "available": True + }, + { + "activity_id": "AC-103", + "name": "Broadway Show - The Lion King", + "category": "entertainment", + "location": location, + "duration": "2.5 hours", + "price": 125.00, + "rating": 4.9, + "available": True + }, + { + "activity_id": "AC-104", + "name": "NYC Food Walking Tour", + "category": "food", + "location": location, + "duration": "3 hours", + "price": 65.00, + "rating": 4.6, + "available": True + } + ], + "total_results": 4, + "search_criteria": {"location": location, "category": category}, + "currency": "USD" + } + + +def get_activity_details(args): + """Get detailed information about a specific activity.""" + activity_id = args.get("activity_id", "AC-101") + + return { + "activity_id": activity_id, + "name": "Statue of Liberty & Ellis Island Tour", + "description": "Visit two of New York's most iconic landmarks. Includes ferry tickets, guided tour of Liberty Island, and access to the Ellis Island Immigration Museum.", + "category": "sightseeing", + "location": "Battery Park, New York", + "duration": "4 hours", + "start_times": ["9:00 AM", "10:30 AM", "12:00 PM", "1:30 PM"], + "price": {"adult": 45.00, "child": 25.00, "senior": 38.00}, + "includes": ["Ferry tickets", "Guided tour", "Museum access", "Audio guide"], + "requirements": ["Comfortable walking shoes", "Valid ID for security check"], + "cancellation_policy": "Free cancellation up to 24 hours before", + "meeting_point": "Castle Clinton, Battery Park", + "rating": 4.7, + "total_reviews": 3420, + "currency": "USD" + } + + +def check_activity_availability(args): + """Check availability for a specific activity on a date.""" + activity_id = args.get("activity_id", "AC-101") + date = args.get("date", "2025-03-15") + participants = args.get("participants", 2) + + return { + "activity_id": activity_id, + "activity_name": "Statue of Liberty & Ellis Island Tour", + "date": date, + "participants": participants, + "available_slots": [ + {"time": "9:00 AM", "spots_remaining": 15}, + {"time": "10:30 AM", "spots_remaining": 8}, + {"time": "12:00 PM", "spots_remaining": 22}, + {"time": "1:30 PM", "spots_remaining": 30} + ], + "total_price": 45.00 * participants, + "currency": "USD" + } + + +# ============================================================================= +# TRIP PLANNING DOMAIN (2 tools) +# ============================================================================= + +def create_itinerary(args): + """Create a trip itinerary based on destination and preferences.""" + destination = args.get("destination", "New York") + days = args.get("days", 3) + + itinerary = { + "destination": destination, + "duration": f"{days} days", + "daily_plan": [ + { + "day": 1, + "theme": "Iconic Landmarks", + "activities": [ + {"time": "9:00 AM", "activity": "Statue of Liberty & Ellis Island", "duration": "4 hours"}, + {"time": "1:30 PM", "activity": "Lunch at Battery Park area", "duration": "1 hour"}, + {"time": "3:00 PM", "activity": "9/11 Memorial & Museum", "duration": "2 hours"}, + {"time": "6:00 PM", "activity": "Dinner in Tribeca", "duration": "1.5 hours"}, + {"time": "8:00 PM", "activity": "Brooklyn Bridge walk at sunset", "duration": "1 hour"} + ] + }, + { + "day": 2, + "theme": "Culture & Entertainment", + "activities": [ + {"time": "9:00 AM", "activity": "Metropolitan Museum of Art", "duration": "3 hours"}, + {"time": "12:30 PM", "activity": "Lunch on Museum Mile", "duration": "1 hour"}, + {"time": "2:00 PM", "activity": "Central Park walking tour", "duration": "2 hours"}, + {"time": "5:00 PM", "activity": "Times Square exploration", "duration": "1.5 hours"}, + {"time": "7:30 PM", "activity": "Broadway show", "duration": "2.5 hours"} + ] + }, + { + "day": 3, + "theme": "Food & Neighborhoods", + "activities": [ + {"time": "9:00 AM", "activity": "Chelsea Market food tour", "duration": "2 hours"}, + {"time": "11:30 AM", "activity": "High Line walk", "duration": "1.5 hours"}, + {"time": "1:30 PM", "activity": "Lunch in Greenwich Village", "duration": "1 hour"}, + {"time": "3:00 PM", "activity": "SoHo shopping", "duration": "2 hours"}, + {"time": "6:00 PM", "activity": "Farewell dinner in Little Italy", "duration": "2 hours"} + ] + } + ] + } + + # Trim to requested days + itinerary["daily_plan"] = itinerary["daily_plan"][:days] + + return { + "itinerary": itinerary, + "tips": [ + "Get a MetroCard for unlimited subway rides", + "Book Broadway tickets in advance for best prices", + "Wear comfortable walking shoes — NYC is best explored on foot" + ], + "estimated_budget": {"low": 150 * days, "high": 350 * days, "currency": "USD"} + } + + +def get_travel_tips(args): + """Get travel tips and recommendations for a destination.""" + destination = args.get("destination", "New York") + + tips = { + "destination": destination, + "tips": { + "general": [ + "Best time to visit: April-June and September-November for mild weather", + "Get a 7-day unlimited MetroCard ($33) for subway and bus travel", + "Many museums offer 'pay what you wish' hours", + "Tipping is expected: 18-20% at restaurants, $1-2 per drink at bars", + "Download offline maps — cell service can be spotty underground" + ], + "safety": [ + "NYC is generally safe for tourists, but stay aware of your surroundings", + "Keep valuables secure in crowded areas like Times Square and subway", + "Stick to well-lit streets at night", + "Use official yellow cabs or ride-sharing apps" + ], + "budget": [ + "Free attractions: Central Park, Brooklyn Bridge, Staten Island Ferry, High Line", + "Eat at food trucks and delis for affordable meals ($8-12)", + "TKTS booth in Times Square offers same-day Broadway tickets at 20-50% off", + "Happy hour deals at restaurants typically run 4-7 PM" + ], + "packing": [ + "Layers are key — weather can change quickly", + "Comfortable walking shoes are essential (expect 10-15k steps/day)", + "Compact umbrella for unexpected rain", + "Portable phone charger for navigation and photos" + ] + }, + "local_phrases": [ + {"phrase": "How do I get to...?", "context": "Asking for directions"}, + {"phrase": "What's good here?", "context": "Asking restaurant staff for recommendations"}, + {"phrase": "Can I get the check?", "context": "Requesting the bill at a restaurant"} + ] + } + + return tips + + +# ============================================================================= +# TOOL DISPATCH TABLE +# ============================================================================= + +TOOL_DISPATCH = { + # Flights domain (9 tools) + "search_flights": search_flights, + "get_flight_details": get_flight_details, + "check_availability": check_availability, + "get_seat_map": get_seat_map, + "get_baggage_policy": get_baggage_policy, + "get_flight_status": get_flight_status, + "search_connecting_flights": search_connecting_flights, + "get_airline_info": get_airline_info, + "compare_flight_prices": compare_flight_prices, + # Hotels domain (4 tools) + "search_hotels": search_hotels, + "get_hotel_details": get_hotel_details, + "check_room_availability": check_room_availability, + "get_hotel_amenities": get_hotel_amenities, + # Car Rentals domain (3 tools) + "search_car_rentals": search_car_rentals, + "get_rental_details": get_rental_details, + "check_car_availability": check_car_availability, + # Restaurants domain (5 tools) + "search_restaurants": search_restaurants, + "get_restaurant_details": get_restaurant_details, + "get_menu": get_menu, + "check_reservations": check_reservations, + "get_restaurant_reviews": get_restaurant_reviews, + # Currency domain (3 tools) + "convert_currency": convert_currency, + "get_exchange_rates": get_exchange_rates, + "get_supported_currencies": get_supported_currencies, + # Loyalty domain (3 tools) + "get_loyalty_balance": get_loyalty_balance, + "redeem_points": redeem_points, + "get_loyalty_program_info": get_loyalty_program_info, + # Weather domain (2 tools) + "get_weather_forecast": get_weather_forecast, + "get_current_weather": get_current_weather, + # Activities domain (3 tools) + "search_activities": search_activities, + "get_activity_details": get_activity_details, + "check_activity_availability": check_activity_availability, + # Trip Planning domain (2 tools) + "create_itinerary": create_itinerary, + "get_travel_tips": get_travel_tips, +} diff --git a/03-integrations/gateway/agentcore-tool-search-plugin/requirements.txt b/03-integrations/gateway/agentcore-tool-search-plugin/requirements.txt new file mode 100644 index 000000000..fe2b2b3ee --- /dev/null +++ b/03-integrations/gateway/agentcore-tool-search-plugin/requirements.txt @@ -0,0 +1,5 @@ +strands-agents>=0.1.0 +strands-agents-tools>=0.1.0 +boto3>=1.35.0 +bedrock-agentcore[strands-agents]>=0.1.0 +mcp-proxy-for-aws>=0.1.0 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 902154e56..7d2368505 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -120,3 +120,4 @@ - Visakh Madathil (vmmadathil) - JobRamos (jobdram) - Will Matos (wilmatos) +- Senthil Mohan (skmohan)