Created
July 31, 2025 09:15
-
-
Save yike5460/77a17605b48fd0ca0840da0b78a2b5a0 to your computer and use it in GitHub Desktop.
Amazon Bedrock AgentCore Gateway demonstration - Secure managed service connecting AI agents with tools via OAuth, Lambda functions, and OpenAPI endpoints
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # basic_gateway.py - Amazon Bedrock AgentCore Gateway demonstration | |
| """ | |
| This example demonstrates how to use Amazon Bedrock AgentCore Gateway to create | |
| a secure, managed service for connecting AI agents with tools and external resources. | |
| AgentCore Gateway provides: | |
| - Security Guard: OAuth authorization for secure access | |
| - Translation: Converts MCP requests to API/Lambda calls | |
| - Composition: Combines multiple tools into single endpoint | |
| - Semantic Tool Selection: Helps agents find appropriate tools | |
| - Serverless Infrastructure: Fully managed with observability | |
| """ | |
| import boto3 | |
| import json | |
| import time | |
| import requests | |
| from typing import Dict, List, Any, Optional | |
| from bedrock_agentcore.runtime import BedrockAgentCoreApp | |
| from botocore.exceptions import ClientError | |
| import random | |
| # Initialize the AgentCore app | |
| app = BedrockAgentCoreApp() | |
| # Initialize Gateway client | |
| gateway_client = boto3.client('bedrock-agentcore-control', region_name='us-west-2') | |
| class AgentCoreGatewayManager: | |
| """Manager for AgentCore Gateway operations""" | |
| def __init__(self, region: str = 'us-west-2'): | |
| self.region = region | |
| self.gateway_client = boto3.client('bedrock-agentcore-control', region_name=region) | |
| self.gateway_id = None | |
| self.gateway_endpoint = None | |
| def _wait_with_backoff(self, base_delay: float = 2.0, max_delay: float = 60.0): | |
| """Wait with exponential backoff for API rate limiting""" | |
| delay = base_delay + random.uniform(0, 1) # Add jitter | |
| delay = min(delay, max_delay) | |
| print(f"⏳ Waiting {delay:.1f} seconds to avoid rate limiting...") | |
| time.sleep(delay) | |
| def _execute_with_retry(self, operation, max_retries: int = 3, base_delay: float = 2.0): | |
| """Execute operation with exponential backoff retry on throttling""" | |
| last_exception = None | |
| for attempt in range(max_retries + 1): | |
| try: | |
| return operation() | |
| except ClientError as e: | |
| error_code = e.response.get('Error', {}).get('Code', '') | |
| if error_code == 'ThrottlingException' and attempt < max_retries: | |
| delay = base_delay * (2 ** attempt) + random.uniform(0, 1) | |
| print(f"⚠️ Rate limited (attempt {attempt + 1}/{max_retries + 1}), retrying in {delay:.1f}s...") | |
| time.sleep(delay) | |
| last_exception = e | |
| continue | |
| else: | |
| raise e | |
| except Exception as e: | |
| if attempt < max_retries: | |
| delay = base_delay * (2 ** attempt) | |
| print(f"⚠️ Error (attempt {attempt + 1}/{max_retries + 1}), retrying in {delay:.1f}s...") | |
| time.sleep(delay) | |
| last_exception = e | |
| continue | |
| else: | |
| raise e | |
| # If we get here, all retries failed | |
| if last_exception: | |
| raise last_exception | |
| def create_gateway(self, name: str, description: str = None, role_arn: str = None) -> Dict[str, Any]: | |
| """Create a new AgentCore Gateway or use existing one""" | |
| try: | |
| # First check if gateway with this name already exists | |
| existing_gateway = self._find_existing_gateway(name) | |
| if existing_gateway: | |
| self.gateway_id = existing_gateway['gatewayId'] | |
| # Get full gateway details to get the URL | |
| gateway_details = self.gateway_client.get_gateway(gatewayIdentifier=self.gateway_id) | |
| self.gateway_endpoint = gateway_details.get('gatewayUrl') | |
| print(f"✅ Using existing gateway:") | |
| print(f" Gateway ID: {self.gateway_id}") | |
| print(f" Gateway URL: {self.gateway_endpoint}") | |
| print(f" Status: {existing_gateway.get('status', 'UNKNOWN')}") | |
| return gateway_details | |
| # Create new gateway if none exists | |
| print(f"🔨 Creating new gateway: {name}") | |
| response = self.gateway_client.create_gateway( | |
| name=name, | |
| description=description or f"Gateway for {name}", | |
| protocolType='MCP', # Required: Currently only supports MCP | |
| roleArn=role_arn or 'arn:aws:iam::<account_id>:role/AmazonBedrockExecutionRoleFullAccess', # Required | |
| authorizerType='CUSTOM_JWT', # Required: Only valid value is CUSTOM_JWT | |
| authorizerConfiguration={ | |
| 'customJWTAuthorizer': { | |
| # OIDC discovery URL for your Cognito user pool | |
| 'discoveryUrl': 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_<user_pool_id>/.well-known/openid-configuration', | |
| # OAuth client IDs that are allowed to access this gateway | |
| 'allowedClients': ['<client_id>'], # Your actual client ID | |
| # 'allowedAudiences': ['your-audience'] # JWT audiences (alternative to clients) | |
| } | |
| } | |
| ) | |
| self.gateway_id = response['gatewayId'] | |
| self.gateway_endpoint = response.get('gatewayUrl') # Changed from gatewayEndpoint to gatewayUrl | |
| print(f"✅ Gateway created successfully:") | |
| print(f" Gateway ID: {self.gateway_id}") | |
| print(f" Gateway ARN: {response.get('gatewayArn', 'N/A')}") | |
| print(f" Gateway URL: {self.gateway_endpoint}") | |
| print(f" Status: {response.get('status', 'CREATING')}") | |
| return response | |
| except Exception as e: | |
| print(f"❌ Failed to create gateway: {e}") | |
| return None | |
| def _find_existing_gateway(self, name: str) -> Dict[str, Any]: | |
| """Find existing gateway by name""" | |
| try: | |
| response = self.gateway_client.list_gateways() | |
| gateways = response.get('items', []) | |
| for gateway in gateways: | |
| if gateway.get('name') == name: | |
| return gateway | |
| return None | |
| except Exception as e: | |
| print(f"⚠️ Warning: Failed to list existing gateways: {e}") | |
| return None | |
| def add_lambda_target(self, lambda_function_arn: str, target_name: str) -> Dict[str, Any]: | |
| """Add a Lambda function as a gateway target""" | |
| def _create_lambda_target(): | |
| return self.gateway_client.create_gateway_target( | |
| gatewayIdentifier=self.gateway_id, | |
| name=target_name, | |
| targetConfiguration={ | |
| 'mcp': { | |
| 'lambda': { | |
| 'lambdaArn': lambda_function_arn, | |
| 'toolSchema': { | |
| 'inlinePayload': [ | |
| { | |
| 'name': 'calculate', | |
| 'description': 'Calculate mathematical expressions', | |
| 'inputSchema': { | |
| 'type': 'object', | |
| 'properties': { | |
| 'expression': { | |
| 'type': 'string', | |
| 'description': 'Mathematical expression to evaluate (e.g., 2+2, 10*5)' | |
| } | |
| }, | |
| 'required': ['expression'] | |
| } | |
| } | |
| ] | |
| } | |
| } | |
| } | |
| }, | |
| credentialProviderConfigurations=[ | |
| { | |
| 'credentialProviderType': 'GATEWAY_IAM_ROLE' | |
| } | |
| ] | |
| ) | |
| try: | |
| response = self._execute_with_retry(_create_lambda_target) | |
| print(f"✅ Lambda target added: {target_name}") | |
| print(f" Target ID: {response['targetId']}") | |
| return response | |
| except Exception as e: | |
| print(f"❌ Failed to add Lambda target: {e}") | |
| return None | |
| def add_api_target(self, api_spec: Dict[str, Any], target_name: str) -> Dict[str, Any]: | |
| """Add an API as a gateway target""" | |
| def _create_api_target(): | |
| return self.gateway_client.create_gateway_target( | |
| gatewayIdentifier=self.gateway_id, | |
| name=target_name, | |
| targetConfiguration={ | |
| 'mcp': { | |
| 'openApiSchema': { | |
| 'inlinePayload': json.dumps(api_spec) | |
| } | |
| } | |
| }, | |
| credentialProviderConfigurations=[ | |
| { | |
| 'credentialProviderType': 'API_KEY', | |
| 'credentialProvider': { | |
| 'apiKeyCredentialProvider': { | |
| 'providerArn': 'arn:aws:bedrock-agentcore:us-west-2:<account_id>:token-vault/default/apikeycredentialprovider/demo-weather-api-key', | |
| 'credentialLocation': 'HEADER', | |
| 'credentialParameterName': 'X-API-Key' | |
| } | |
| } | |
| } | |
| ] | |
| ) | |
| try: | |
| # Add delay before API target creation to avoid throttling | |
| self._wait_with_backoff(3.0) | |
| response = self._execute_with_retry(_create_api_target) | |
| print(f"✅ API target added: {target_name}") | |
| print(f" Target ID: {response['targetId']}") | |
| return response | |
| except Exception as e: | |
| print(f"❌ Failed to add API target: {e}") | |
| return None | |
| def list_gateway_targets(self) -> List[Dict[str, Any]]: | |
| """List all targets in the gateway""" | |
| try: | |
| response = self.gateway_client.list_gateway_targets( | |
| gatewayIdentifier=self.gateway_id | |
| ) | |
| targets = response.get('items', []) | |
| print(f"📋 Gateway targets ({len(targets)}):") | |
| for target in targets: | |
| print(f" - {target['name']} (ID: {target['targetId']})") | |
| return targets | |
| except Exception as e: | |
| print(f"❌ Failed to list targets: {e}") | |
| return [] | |
| def get_gateway_status(self) -> Dict[str, Any]: | |
| """Check gateway status""" | |
| try: | |
| response = self.gateway_client.get_gateway( | |
| gatewayIdentifier=self.gateway_id | |
| ) | |
| status = response.get('status', 'UNKNOWN') | |
| print(f"🔍 Gateway status: {status}") | |
| return response | |
| except Exception as e: | |
| print(f"❌ Failed to get gateway status: {e}") | |
| return {} | |
| # Demo tools that can be exposed through Gateway | |
| class DemoTools: | |
| """Sample tools that can be exposed through AgentCore Gateway""" | |
| @staticmethod | |
| def calculator_tool(expression: str) -> Dict[str, Any]: | |
| """Calculator tool - can be exposed via Lambda""" | |
| try: | |
| # Simple expression evaluation (use safely in production) | |
| result = eval(expression.replace("^", "**")) | |
| return { | |
| "result": result, | |
| "expression": expression, | |
| "status": "success" | |
| } | |
| except Exception as e: | |
| return { | |
| "result": None, | |
| "expression": expression, | |
| "error": str(e), | |
| "status": "error" | |
| } | |
| @staticmethod | |
| def weather_tool(location: str) -> Dict[str, Any]: | |
| """Weather tool - can be exposed via API""" | |
| # Mock weather data (replace with real API in production) | |
| weather_data = { | |
| "new york": {"temperature": 22, "condition": "sunny", "humidity": 65}, | |
| "london": {"temperature": 15, "condition": "cloudy", "humidity": 80}, | |
| "tokyo": {"temperature": 25, "condition": "rainy", "humidity": 90} | |
| } | |
| location_key = location.lower() | |
| if location_key in weather_data: | |
| return { | |
| "location": location, | |
| "weather": weather_data[location_key], | |
| "status": "success" | |
| } | |
| else: | |
| return { | |
| "location": location, | |
| "weather": None, | |
| "error": "Location not found", | |
| "status": "error" | |
| } | |
| @staticmethod | |
| def get_openapi_spec() -> Dict[str, Any]: | |
| """Sample OpenAPI specification for weather tool""" | |
| return { | |
| "openapi": "3.0.0", | |
| "info": { | |
| "title": "Weather API", | |
| "version": "1.0.0", | |
| "description": "Simple weather information API" | |
| }, | |
| "servers": [ | |
| { | |
| "url": "https://api.weather.example.com/v1", | |
| "description": "Weather API server" | |
| } | |
| ], | |
| "paths": { | |
| "/weather": { | |
| "get": { | |
| "summary": "Get weather information", | |
| "description": "Get current weather for a location", | |
| "operationId": "getWeather", | |
| "parameters": [ | |
| { | |
| "name": "location", | |
| "in": "query", | |
| "required": True, | |
| "schema": { | |
| "type": "string" | |
| }, | |
| "description": "City name or location" | |
| } | |
| ], | |
| "responses": { | |
| "200": { | |
| "description": "Weather information", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "type": "object", | |
| "properties": { | |
| "location": {"type": "string"}, | |
| "weather": { | |
| "type": "object", | |
| "properties": { | |
| "temperature": {"type": "number"}, | |
| "condition": {"type": "string"}, | |
| "humidity": {"type": "number"} | |
| } | |
| }, | |
| "status": {"type": "string"} | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| # Gateway-enabled agent | |
| @app.entrypoint | |
| async def gateway_agent(request): | |
| """Agent that uses Gateway to access tools""" | |
| user_input = request.get("prompt", "") | |
| user_id = request.get("user_id", "demo_user") | |
| print(f"🤖 Gateway Agent processing: {user_input}") | |
| # Simulate tool selection and invocation through Gateway | |
| if "calculate" in user_input.lower() or "math" in user_input.lower(): | |
| # Use calculator tool through Gateway | |
| expression = user_input.split("calculate")[-1].strip() if "calculate" in user_input.lower() else "2+2" | |
| result = DemoTools.calculator_tool(expression) | |
| if result["status"] == "success": | |
| response = f"Using Gateway Calculator Tool: {expression} = {result['result']}" | |
| else: | |
| response = f"Calculator error: {result['error']}" | |
| elif "weather" in user_input.lower(): | |
| # Use weather tool through Gateway | |
| location = "New York" # Extract location from input in production | |
| result = DemoTools.weather_tool(location) | |
| if result["status"] == "success": | |
| weather = result["weather"] | |
| response = f"Using Gateway Weather Tool: {location} is {weather['condition']} with {weather['temperature']}°C" | |
| else: | |
| response = f"Weather error: {result['error']}" | |
| elif "tools" in user_input.lower(): | |
| response = """Available tools through AgentCore Gateway: | |
| • Calculator Tool (Lambda): Performs mathematical calculations | |
| • Weather Tool (API): Provides weather information | |
| • More tools can be added through Gateway targets""" | |
| else: | |
| response = """Hello! I'm a Gateway-enabled agent with access to multiple tools through AgentCore Gateway. | |
| Try asking me to: | |
| • Calculate something: "calculate 15 * 23" | |
| • Get weather: "what's the weather?" | |
| • List tools: "what tools are available?" | |
| Gateway provides secure, managed access to external tools and APIs.""" | |
| return { | |
| "response": response, | |
| "gateway_enabled": True, | |
| "available_tools": ["calculator", "weather"], | |
| "status": "success" | |
| } | |
| @app.ping | |
| def health_check(): | |
| """Health check for Gateway agent""" | |
| return { | |
| "status": "Healthy", | |
| "gateway_enabled": True, | |
| "time_of_last_update": int(time.time()) | |
| } | |
| def demo_gateway_setup(): | |
| """Demonstrate Gateway setup and configuration""" | |
| print("🚀 AgentCore Gateway Setup Demo") | |
| print("=" * 50) | |
| print("ℹ️ Note: This demo uses placeholder values. For actual setup:") | |
| print(" - Replace discoveryUrl with your OAuth provider's discovery URL") | |
| print(" - Replace allowedClients with your actual OAuth client IDs") | |
| print(" - Replace roleArn with your actual IAM role ARN") | |
| print(" - Run 'python basic_gateway.py oauth' for detailed OAuth setup guide") | |
| # Initialize Gateway manager | |
| gateway_manager = AgentCoreGatewayManager() | |
| # Create gateway or use existing one | |
| print("\n1. Creating/Finding Gateway...") | |
| gateway_result = gateway_manager.create_gateway( | |
| name="DemoGateway", | |
| description="Demo gateway for AgentCore tutorial" | |
| ) | |
| if not gateway_result: | |
| print("❌ Gateway creation failed, exiting demo") | |
| return | |
| # Create and add Lambda target (calculator) | |
| print("\n2. Creating Lambda function for calculator tool...") | |
| lambda_function_arn = create_lambda_function_for_gateway("gateway-calculator-tool") | |
| print("\n3. Adding Lambda target to Gateway...") | |
| lambda_target = gateway_manager.add_lambda_target( | |
| lambda_function_arn=lambda_function_arn, | |
| target_name="CalculatorTool" | |
| ) | |
| # Skip API target for now due to credential provider complexity | |
| print("\n4. Adding API target (requires API key credential provider setup)...") | |
| print(" 💡 To add API targets, you need to:") | |
| print(" - Create an API key credential provider in AgentCore Identity") | |
| print(" - Configure the provider ARN in the credential configuration") | |
| print(" - Or use OAuth credential provider for OAuth-based APIs") | |
| # Uncomment below to add API target once credential provider is set up: | |
| print("\n⏳ Waiting before adding API target to avoid rate limiting...") | |
| time.sleep(5.0) # 5 second delay | |
| api_spec = DemoTools.get_openapi_spec() | |
| api_target = gateway_manager.add_api_target( | |
| api_spec=api_spec, | |
| target_name="WeatherTool" | |
| ) | |
| # Wait before listing targets | |
| print("\n⏳ Waiting before listing targets...") | |
| time.sleep(2.0) # 2 second delay | |
| # List targets | |
| print("\n5. Listing Gateway targets...") | |
| targets = gateway_manager.list_gateway_targets() | |
| # Wait before checking status | |
| time.sleep(1.0) # 1 second delay | |
| # Check status | |
| print("\n6. Checking Gateway status...") | |
| status = gateway_manager.get_gateway_status() | |
| print("\n✅ Gateway setup complete!") | |
| print(f"Gateway ID: {gateway_manager.gateway_id}") | |
| print(f"Gateway URL: {gateway_manager.gateway_endpoint}") | |
| print(f"Lambda Function ARN: {lambda_function_arn}") | |
| return gateway_manager | |
| def demo_gateway_usage(gateway_manager=None): | |
| """Demonstrate Gateway usage with actual MCP calls to live gateway""" | |
| print("\n🧪 Testing Live Gateway MCP Interface...") | |
| print("=" * 60) | |
| print("🔍 This demo makes ACTUAL MCP calls to the live AgentCore Gateway") | |
| print(" to show how it exposes Lambda and OpenAPI targets as tools.") | |
| # Use actual gateway from setup or find existing gateway | |
| if not gateway_manager: | |
| print("\n🔍 Finding existing gateway...") | |
| gateway_manager = AgentCoreGatewayManager() | |
| existing_gateway = gateway_manager._find_existing_gateway("DemoGateway") | |
| if existing_gateway: | |
| gateway_manager.gateway_id = existing_gateway['gatewayId'] | |
| gateway_details = gateway_manager.gateway_client.get_gateway(gatewayIdentifier=gateway_manager.gateway_id) | |
| gateway_manager.gateway_endpoint = gateway_details.get('gatewayUrl') | |
| print(f" ✅ Found Gateway: {gateway_manager.gateway_id}") | |
| print(f" ✅ Gateway URL: {gateway_manager.gateway_endpoint}") | |
| else: | |
| print("❌ No existing gateway found. Please run 'python basic_gateway.py setup' first.") | |
| return None | |
| gateway_url = gateway_manager.gateway_endpoint | |
| print(f"\n🎯 Using Live Gateway: {gateway_url}") | |
| # Obtain actual OAuth access token | |
| print("\n🔐 Authentication: Obtaining OAuth Token...") | |
| access_token = fetch_oauth_token() | |
| if not access_token: | |
| print("❌ Failed to obtain OAuth token. Cannot proceed with live demo.") | |
| print("💡 Please ensure:") | |
| print(" - Client credentials are configured correctly") | |
| print(" - Cognito user pool is accessible") | |
| print(" - Network connectivity is available") | |
| return None | |
| print(f"✅ OAuth Token Obtained: {mask_token(access_token)}") | |
| # Test 1: List Available Tools (ACTUAL MCP CALL) | |
| print("\n🔧 Test 1: Live MCP tools/list Call") | |
| print("-" * 40) | |
| tools_result = make_actual_list_tools_call(gateway_url, access_token) | |
| # Extract actual tool names from discovery | |
| discovered_tools = extract_tool_names_from_discovery(tools_result) | |
| lambda_tool_name = discovered_tools.get("lambda_tool") | |
| openapi_tool_name = discovered_tools.get("openapi_tool") | |
| print(f"\n🔍 Discovered Tool Names:") | |
| print(f" Lambda Tool: {lambda_tool_name or 'NOT FOUND'}") | |
| print(f" OpenAPI Tool: {openapi_tool_name or 'NOT FOUND'}") | |
| # Test 2: Call Lambda Tool (ACTUAL MCP CALL) | |
| print("\n🧮 Test 2: Live MCP tools/call - Lambda Calculator") | |
| print("-" * 40) | |
| calculator_result = make_actual_lambda_tool_call(gateway_url, access_token, lambda_tool_name) | |
| # Test 3: Call OpenAPI Tool (ACTUAL MCP CALL) | |
| print("\n🌤️ Test 3: Live MCP tools/call - OpenAPI Weather") | |
| print("-" * 40) | |
| weather_result = make_actual_openapi_tool_call(gateway_url, access_token, openapi_tool_name) | |
| # Summary of actual results | |
| print("\n📊 Live Gateway Test Results Summary") | |
| print("-" * 40) | |
| gateway_summary = summarize_actual_results(gateway_manager, tools_result, calculator_result, weather_result) | |
| # Summary | |
| print("\n📊 Gateway MCP Demo Summary") | |
| print("=" * 60) | |
| print("✅ Gateway successfully exposes:") | |
| print(" 🔹 Lambda functions as callable MCP tools") | |
| print(" 🔹 OpenAPI endpoints as structured MCP tools") | |
| print(" 🔹 Unified tool discovery via tools/list") | |
| print(" 🔹 Consistent tool invocation via tools/call") | |
| print(" 🔹 Semantic tool search capabilities") | |
| print("\n💡 Key Benefits:") | |
| print(" • Single MCP endpoint for multiple tool types") | |
| print(" • Automatic schema validation and transformation") | |
| print(" • Built-in authentication and authorization") | |
| print(" • Observability and monitoring") | |
| print(" • Managed scaling and reliability") | |
| return { | |
| "gateway_url": gateway_url, | |
| "gateway_id": gateway_manager.gateway_id, | |
| "tools_discovered": tools_result, | |
| "calculator_result": calculator_result, | |
| "weather_result": weather_result, | |
| "gateway_summary": gateway_summary | |
| } | |
| def mask_token(token: str) -> str: | |
| """Mask token for display purposes""" | |
| if len(token) > 20: | |
| return f"{token[:8]}...{token[-8:]}" | |
| return "demo-token-masked" | |
| def fetch_oauth_token(): | |
| """Fetch actual OAuth token from Cognito using client credentials flow""" | |
| print("🔐 Obtaining OAuth token from Cognito...") | |
| # Configuration from the gateway setup | |
| CLIENT_ID = "<client_id>" | |
| TOKEN_URL = "https://gateway-pool-<unique_id>.auth.us-west-2.amazoncognito.com/oauth2/token" | |
| # Note: In production, client secret should be retrieved from secure storage | |
| # For this demo, we'll attempt to get the token but expect it might fail without the secret | |
| print(f"📍 Token URL: {TOKEN_URL}") | |
| print(f"📍 Client ID: {CLIENT_ID}") | |
| print("⚠️ Client Secret: Required but not stored in demo code") | |
| try: | |
| # In a real implementation, you would have the client secret from secure storage | |
| CLIENT_SECRET = "<client_secret>" | |
| response = requests.post( | |
| TOKEN_URL, | |
| data=f"grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}", | |
| headers={'Content-Type': 'application/x-www-form-urlencoded'}, | |
| timeout=30 | |
| ) | |
| if response.status_code == 200: | |
| token_data = response.json() | |
| return token_data['access_token'] | |
| else: | |
| print(f"❌ Token request failed: {response.status_code} - {response.text}") | |
| return None | |
| except Exception as e: | |
| print(f"❌ Error fetching OAuth token: {e}") | |
| return None | |
| def extract_tool_names_from_discovery(tools_result: Dict[str, Any]) -> Dict[str, str]: | |
| """Extract actual tool names from tools discovery response""" | |
| discovered_tools = {"lambda_tool": None, "openapi_tool": None} | |
| if 'result' in tools_result and 'tools' in tools_result['result']: | |
| tools = tools_result['result']['tools'] | |
| for tool in tools: | |
| tool_name = tool.get('name', '') | |
| description = tool.get('description', '').lower() | |
| # Identify Lambda tool (likely calculator-related) | |
| if 'calculate' in tool_name.lower() or 'calculate' in description or 'math' in description: | |
| discovered_tools["lambda_tool"] = tool_name | |
| # Identify OpenAPI tool (likely weather-related) | |
| elif 'weather' in tool_name.lower() or 'weather' in description: | |
| discovered_tools["openapi_tool"] = tool_name | |
| # Fallback: first tool could be Lambda, second could be OpenAPI | |
| elif not discovered_tools["lambda_tool"]: | |
| discovered_tools["lambda_tool"] = tool_name | |
| elif not discovered_tools["openapi_tool"]: | |
| discovered_tools["openapi_tool"] = tool_name | |
| return discovered_tools | |
| def make_actual_list_tools_call(gateway_url: str, access_token: str) -> Dict[str, Any]: | |
| """Make actual HTTP call to gateway's tools/list endpoint""" | |
| print("🌐 Making LIVE HTTP request to gateway...") | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {access_token}" | |
| } | |
| payload = { | |
| "jsonrpc": "2.0", | |
| "id": "list-tools-request", | |
| "method": "tools/list" | |
| } | |
| print(f"📤 Request Details:") | |
| print(f" URL: {gateway_url}") | |
| print(f" Method: POST") | |
| print(f" Headers: {json.dumps({k: v if k != 'Authorization' else f'Bearer {mask_token(access_token)}' for k, v in headers.items()}, indent=6)}") | |
| print(f" Body: {json.dumps(payload, indent=6)}") | |
| try: | |
| print("🔄 Sending request...") | |
| response = requests.post(gateway_url, headers=headers, json=payload, timeout=30) | |
| print(f"📥 Response Status: {response.status_code}") | |
| if response.status_code == 200: | |
| result = response.json() | |
| print(f"✅ SUCCESS! Gateway Response:") | |
| print(json.dumps(result, indent=2)) | |
| if 'result' in result and 'tools' in result['result']: | |
| tools = result['result']['tools'] | |
| print(f"\\n🔧 Discovered {len(tools)} live tools:") | |
| for tool in tools: | |
| print(f" • {tool['name']}: {tool.get('description', 'No description')}") | |
| return result | |
| elif response.status_code == 401: | |
| print(f"❌ Authentication Failed: {response.text}") | |
| print("💡 This is expected in demo mode without valid client secret") | |
| return {"error": "authentication_failed", "status_code": 401} | |
| else: | |
| print(f"❌ Request Failed: {response.status_code}") | |
| print(f"Response: {response.text}") | |
| return {"error": "request_failed", "status_code": response.status_code, "response": response.text} | |
| except requests.exceptions.Timeout: | |
| print("❌ Request Timeout") | |
| return {"error": "timeout"} | |
| except requests.exceptions.ConnectionError as e: | |
| print(f"❌ Connection Error: {e}") | |
| return {"error": "connection_error"} | |
| except Exception as e: | |
| print(f"❌ Unexpected Error: {e}") | |
| return {"error": "unexpected_error", "details": str(e)} | |
| def make_actual_lambda_tool_call(gateway_url: str, access_token: str, tool_name: str = None) -> Dict[str, Any]: | |
| """Make actual HTTP call to invoke Lambda tool through gateway""" | |
| print("🧮 Making LIVE call to Lambda calculator tool...") | |
| if not tool_name: | |
| print("❌ No Lambda tool name provided - skipping call") | |
| return {"error": "no_tool_name", "message": "Lambda tool name not discovered"} | |
| print(f"🎯 Using Discovered Tool Name: {tool_name}") | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {access_token}" | |
| } | |
| payload = { | |
| "jsonrpc": "2.0", | |
| "id": "calculate-request", | |
| "method": "tools/call", | |
| "params": { | |
| "name": tool_name, # Use actual discovered tool name | |
| "arguments": { | |
| "expression": "15 * 8 + 25" | |
| } | |
| } | |
| } | |
| print(f"📤 Lambda Tool Request:") | |
| print(f" Expression: 15 * 8 + 25") | |
| print(f" Expected Result: 145") | |
| print(f" Body: {json.dumps(payload, indent=6)}") | |
| try: | |
| print("🔄 Calling Lambda through Gateway...") | |
| response = requests.post(gateway_url, headers=headers, json=payload, timeout=30) | |
| print(f"📥 Response Status: {response.status_code}") | |
| if response.status_code == 200: | |
| result = response.json() | |
| print(f"✅ SUCCESS! Lambda Tool Response:") | |
| print(json.dumps(result, indent=2)) | |
| if 'result' in result and 'content' in result['result']: | |
| content_raw = result['result']['content'] | |
| try: | |
| # Handle case where content might already be parsed or be a list | |
| if isinstance(content_raw, str): | |
| content = json.loads(content_raw) | |
| elif isinstance(content_raw, (dict, list)): | |
| content = content_raw | |
| else: | |
| content = content_raw | |
| print(f"\\n🧮 Calculation Result:") | |
| if isinstance(content, dict): | |
| print(f" Expression: {content.get('expression', 'N/A')}") | |
| print(f" Result: {content.get('result', 'N/A')}") | |
| print(f" Status: {content.get('status', 'N/A')}") | |
| else: | |
| print(f" Raw Content: {content}") | |
| except json.JSONDecodeError as e: | |
| print(f" Raw Content (JSON parse failed): {content_raw}") | |
| print(f" Parse Error: {e}") | |
| return result | |
| else: | |
| print(f"❌ Lambda Tool Call Failed: {response.status_code}") | |
| print(f"Response: {response.text}") | |
| return {"error": "tool_call_failed", "status_code": response.status_code} | |
| except Exception as e: | |
| print(f"❌ Error calling Lambda tool: {e}") | |
| return {"error": "lambda_call_error", "details": str(e)} | |
| def make_actual_openapi_tool_call(gateway_url: str, access_token: str, tool_name: str = None) -> Dict[str, Any]: | |
| """Make actual HTTP call to invoke OpenAPI tool through gateway""" | |
| print("🌤️ Making LIVE call to OpenAPI weather tool...") | |
| if not tool_name: | |
| print("❌ No OpenAPI tool name provided - skipping call") | |
| return {"error": "no_tool_name", "message": "OpenAPI tool name not discovered"} | |
| print(f"🎯 Using Discovered Tool Name: {tool_name}") | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {access_token}" | |
| } | |
| payload = { | |
| "jsonrpc": "2.0", | |
| "id": "weather-request", | |
| "method": "tools/call", | |
| "params": { | |
| "name": tool_name, # Use actual discovered tool name | |
| "arguments": { | |
| "location": "San Francisco" | |
| } | |
| } | |
| } | |
| print(f"📤 OpenAPI Tool Request:") | |
| print(f" Location: San Francisco") | |
| print(f" Body: {json.dumps(payload, indent=6)}") | |
| try: | |
| print("🔄 Calling OpenAPI through Gateway...") | |
| response = requests.post(gateway_url, headers=headers, json=payload, timeout=30) | |
| print(f"📥 Response Status: {response.status_code}") | |
| if response.status_code == 200: | |
| result = response.json() | |
| print(f"✅ SUCCESS! OpenAPI Tool Response:") | |
| print(json.dumps(result, indent=2)) | |
| if 'result' in result and 'content' in result['result']: | |
| content_raw = result['result']['content'] | |
| try: | |
| # Handle case where content might already be parsed or be a list | |
| if isinstance(content_raw, str): | |
| content = json.loads(content_raw) | |
| elif isinstance(content_raw, (dict, list)): | |
| content = content_raw | |
| else: | |
| content = content_raw | |
| print(f"\\n🌤️ Weather Result:") | |
| if isinstance(content, dict): | |
| print(f" Location: {content.get('location', 'N/A')}") | |
| if 'weather' in content and isinstance(content['weather'], dict): | |
| weather = content['weather'] | |
| print(f" Temperature: {weather.get('temperature', 'N/A')}°C") | |
| print(f" Condition: {weather.get('condition', 'N/A')}") | |
| print(f" Humidity: {weather.get('humidity', 'N/A')}%") | |
| else: | |
| print(f" Raw Content: {content}") | |
| except json.JSONDecodeError as e: | |
| print(f" Raw Content (JSON parse failed): {content_raw}") | |
| print(f" Parse Error: {e}") | |
| return result | |
| else: | |
| print(f"❌ OpenAPI Tool Call Failed: {response.status_code}") | |
| print(f"Response: {response.text}") | |
| return {"error": "openapi_call_failed", "status_code": response.status_code} | |
| except Exception as e: | |
| print(f"❌ Error calling OpenAPI tool: {e}") | |
| return {"error": "openapi_call_error", "details": str(e)} | |
| def summarize_actual_results(gateway_manager: 'AgentCoreGatewayManager', tools_result: Dict, calculator_result: Dict, weather_result: Dict) -> Dict[str, Any]: | |
| """Summarize the results of actual gateway calls""" | |
| print("📊 Live Test Results Analysis") | |
| print("═" * 50) | |
| # Analyze tools discovery | |
| tools_success = 'result' in tools_result and 'tools' in tools_result['result'] | |
| tools_count = len(tools_result.get('result', {}).get('tools', [])) if tools_success else 0 | |
| print(f"🔧 Tools Discovery:") | |
| print(f" Status: {'✅ SUCCESS' if tools_success else '❌ FAILED'}") | |
| print(f" Tools Found: {tools_count}") | |
| # Analyze Lambda tool call | |
| lambda_success = 'result' in calculator_result and 'content' in calculator_result['result'] | |
| print(f"\\n🧮 Lambda Tool (Calculator):") | |
| print(f" Status: {'✅ SUCCESS' if lambda_success else '❌ FAILED'}") | |
| if lambda_success: | |
| try: | |
| content_raw = calculator_result['result']['content'] | |
| if isinstance(content_raw, str): | |
| content = json.loads(content_raw) | |
| elif isinstance(content_raw, (dict, list)): | |
| content = content_raw | |
| else: | |
| content = content_raw | |
| print(f" Result: {content.get('result', 'N/A') if isinstance(content, dict) else content}") | |
| except: | |
| print(" Result: Unable to parse") | |
| # Analyze OpenAPI tool call | |
| api_success = 'result' in weather_result and 'content' in weather_result['result'] | |
| print(f"\\n🌤️ OpenAPI Tool (Weather):") | |
| print(f" Status: {'✅ SUCCESS' if api_success else '❌ FAILED'}") | |
| if api_success: | |
| try: | |
| content_raw = weather_result['result']['content'] | |
| if isinstance(content_raw, str): | |
| content = json.loads(content_raw) | |
| elif isinstance(content_raw, (dict, list)): | |
| content = content_raw | |
| else: | |
| content = content_raw | |
| print(f" Location: {content.get('location', 'N/A') if isinstance(content, dict) else content}") | |
| except: | |
| print(" Data: Unable to parse") | |
| # Overall assessment | |
| overall_success = tools_success or lambda_success or api_success | |
| print(f"\\n🎯 Overall Assessment:") | |
| print(f" Gateway Status: {'✅ OPERATIONAL' if overall_success else '❌ NEEDS CONFIGURATION'}") | |
| print(f" MCP Protocol: {'✅ WORKING' if overall_success else '❌ AUTH REQUIRED'}") | |
| if not overall_success: | |
| print(f"\\n💡 To enable live calls:") | |
| print(f" 1. Add CLIENT_SECRET to fetch_oauth_token() function") | |
| print(f" 2. Ensure gateway targets are properly configured") | |
| print(f" 3. Verify OAuth client has correct permissions") | |
| return { | |
| "gateway_id": gateway_manager.gateway_id, | |
| "tools_success": tools_success, | |
| "lambda_success": lambda_success, | |
| "api_success": api_success, | |
| "overall_success": overall_success | |
| } | |
| def show_gateway_integration_summary(gateway_manager: 'AgentCoreGatewayManager') -> Dict[str, Any]: | |
| """Show summary of actual gateway integration""" | |
| print("📊 Real Gateway Integration Analysis") | |
| print("═" * 50) | |
| try: | |
| # Get actual gateway details | |
| gateway_info = gateway_manager.get_gateway_status() | |
| targets = gateway_manager.list_gateway_targets() | |
| print(f"🏗️ Gateway Infrastructure:") | |
| print(f" └─ Gateway ID: {gateway_manager.gateway_id}") | |
| print(f" └─ Status: {gateway_info.get('status', 'Unknown')}") | |
| print(f" └─ Protocol: MCP over HTTPS") | |
| print(f" └─ Authentication: OAuth JWT") | |
| print(f"\n🔧 Tool Targets ({len(targets)} configured):") | |
| for target in targets: | |
| print(f" └─ {target['name']} (ID: {target['targetId']})") | |
| if target['name'] == 'CalculatorTool': | |
| print(f" ├─ Type: AWS Lambda Function") | |
| print(f" ├─ Runtime: Python 3.12") | |
| print(f" ├─ Auth: Gateway IAM Role") | |
| print(f" └─ MCP Tool: 'calculate'") | |
| elif target['name'] == 'WeatherTool': | |
| print(f" ├─ Type: OpenAPI REST endpoint") | |
| print(f" ├─ Schema: OpenAPI 3.0") | |
| print(f" ├─ Auth: API Key credential provider") | |
| print(f" └─ MCP Tool: 'getWeather'") | |
| print(f"\n🌐 MCP Interface Benefits:") | |
| print(f" ✅ Single endpoint for multiple tool types") | |
| print(f" ✅ Consistent authentication and error handling") | |
| print(f" ✅ Automatic schema validation and transformation") | |
| print(f" ✅ Built-in observability and monitoring") | |
| print(f" ✅ Managed scaling and high availability") | |
| print(f"\n🔍 Agent Integration:") | |
| print(f" • Agents connect to: {gateway_manager.gateway_endpoint}") | |
| print(f" • Authentication: OAuth client credentials flow") | |
| print(f" • Discovery: tools/list MCP method") | |
| print(f" • Invocation: tools/call MCP method") | |
| print(f" • Response format: Standardized MCP JSON-RPC") | |
| return { | |
| "gateway_id": gateway_manager.gateway_id, | |
| "gateway_status": gateway_info.get('status'), | |
| "target_count": len(targets), | |
| "targets": targets | |
| } | |
| except Exception as e: | |
| print(f"❌ Error getting gateway summary: {e}") | |
| return {} | |
| def show_api_credential_setup_guide(): | |
| """Show how to set up API key credential providers for OpenAPI targets""" | |
| setup_guide = ''' | |
| 🔑 API Key Credential Provider Setup for OpenAPI Targets: | |
| ⚠️ IMPORTANT: OpenAPI targets require API_KEY or OAUTH credential providers! | |
| 🔧 Step 1: Create API Key Credential Provider (Required for OpenAPI targets) | |
| ┌────────────────────────────────────────────────────────────┐ | |
| │ Using AWS CLI: │ | |
| │ aws bedrock-agentcore-identity create-api-key-credential \\ │ | |
| │ --name "demo-weather-api-key" \\ │ | |
| │ --description "API key for weather service" \\ │ | |
| │ --api-key "your-actual-api-key" \\ │ | |
| │ --region us-west-2 │ | |
| │ │ | |
| │ Using Boto3: │ | |
| │ identity_client = boto3.client('bedrock-agentcore-identity') │ | |
| │ response = identity_client.create_api_key_credential_provider( │ | |
| │ name="demo-weather-api-key", │ | |
| │ apiKey="demo-api-key-12345", │ | |
| │ ) │ | |
| │ provider_arn = response['providerArn'] │ | |
| └────────────────────────────────────────────────────────────┘ | |
| 🎯 Step 2: Use Credential Provider in OpenAPI Target | |
| credentialProviderConfigurations=[ | |
| { | |
| 'credentialProviderType': 'API_KEY', | |
| 'credentialProvider': { | |
| 'apiKeyCredentialProvider': { | |
| 'providerArn': 'arn:aws:agent-credential-provider:region:account:token-vault/default/apikeycredentialprovider/provider-id', | |
| 'credentialLocation': 'HEADER', # or 'QUERY_PARAMETER' | |
| 'credentialParameterName': 'X-API-Key' # header/parameter name | |
| } | |
| } | |
| } | |
| ] | |
| 🔄 Alternative: OAuth Credential Provider | |
| ┌────────────────────────────────────────────────────────────┐ | |
| │ credentialProviderConfigurations=[ │ | |
| │ { │ | |
| │ 'credentialProviderType': 'OAUTH', │ | |
| │ 'credentialProvider': { │ | |
| │ 'oauthCredentialProvider': { │ | |
| │ 'providerArn': 'arn:aws:agent-credential-provider:region:account:token-vault/default/oauth2credentialprovider/provider-id' │ | |
| │ } │ | |
| │ } │ | |
| │ } │ | |
| │ ] │ | |
| └────────────────────────────────────────────────────────────┘ | |
| 💡 Why This is Required: | |
| - Lambda targets can use GATEWAY_IAM_ROLE (gateway's own role) | |
| - OpenAPI targets need external authentication (API keys/OAuth) | |
| - This is because OpenAPI targets call external REST APIs that require authentication | |
| - Lambda targets are internal AWS resources that can use IAM roles | |
| 🚀 Complete Working Example: | |
| # 1. Create credential provider first | |
| identity_client = boto3.client('bedrock-agentcore-identity') | |
| cred_response = identity_client.create_api_key_credential_provider( | |
| name="weather-api-key", | |
| apiKey="demo-key-123", | |
| description="Weather API key" | |
| ) | |
| # 2. Use the ARN in your target configuration | |
| target_response = gateway_client.create_gateway_target( | |
| gatewayIdentifier="your-gateway-id", | |
| name="WeatherTool", | |
| targetConfiguration={"mcp": {"openApiSchema": {"inlinePayload": json.dumps(api_spec)}}}, | |
| credentialProviderConfigurations=[{ | |
| 'credentialProviderType': 'API_KEY', | |
| 'credentialProvider': { | |
| 'apiKeyCredentialProvider': { | |
| 'providerArn': cred_response['providerArn'], | |
| 'credentialLocation': 'HEADER', | |
| 'credentialParameterName': 'X-API-Key' | |
| } | |
| } | |
| }] | |
| ) | |
| ''' | |
| print(setup_guide) | |
| return setup_guide | |
| def show_oauth_setup_guide(): | |
| """Show how to set up OAuth authentication for AgentCore Gateway""" | |
| setup_guide = ''' | |
| 📝 OAuth Setup for AgentCore Gateway (REQUIRED): | |
| ⚠️ IMPORTANT: Gateway requires OAuth authentication - it's NOT optional! | |
| 🔧 Option 1: Amazon Cognito (Recommended) | |
| ┌─────────────────────────────────────────────────────────────┐ | |
| │ 1. Create user pool: │ | |
| │ aws cognito-idp create-user-pool \\ │ | |
| │ --pool-name gateway-pool \\ │ | |
| │ --region us-west-2 │ | |
| │ │ | |
| │ 2. Create resource server with scopes: │ | |
| │ aws cognito-idp create-resource-server \\ │ | |
| │ --user-pool-id <pool-id-from-step-1> \\ │ | |
| │ --identifier gateway-resource-server \\ │ | |
| │ --name "Gateway Resource Server" \\ │ | |
| │ --scopes '[{"ScopeName":"read","ScopeDescription":"Read access"},{"ScopeName":"write","ScopeDescription":"Write access"}]' \\ │ | |
| │ --region us-west-2 │ | |
| │ │ | |
| │ 3. Create client (MUST include --generate-secret): │ | |
| │ aws cognito-idp create-user-pool-client \\ │ | |
| │ --user-pool-id <pool-id-from-step-1> \\ │ | |
| │ --client-name gateway-client \\ │ | |
| │ --generate-secret \\ │ | |
| │ --allowed-o-auth-flows client_credentials \\ │ | |
| │ --allowed-o-auth-scopes "gateway-resource-server/read" "gateway-resource-server/write" \\ │ | |
| │ --allowed-o-auth-flows-user-pool-client \\ │ | |
| │ --supported-identity-providers COGNITO \\ │ | |
| │ --region us-west-2 │ | |
| │ │ | |
| │ 4. Create domain for OAuth endpoints: │ | |
| │ aws cognito-idp create-user-pool-domain \\ │ | |
| │ --domain gateway-pool-<unique-suffix> \\ │ | |
| │ --user-pool-id <pool-id-from-step-1> \\ │ | |
| │ --region us-west-2 │ | |
| │ │ | |
| │ 5. Discovery URL format: │ | |
| │ https://cognito-idp.region.amazonaws.com/<pool-id>/.well-known/openid-configuration │ | |
| | | |
| 6. Get Client ID and Secret: | |
| aws cognito-idp describe-user-pool-client \\ | |
| --user-pool-id us-west-2_i2K7SN8wc \\ | |
| --client-id 7p6memtgr04nmlv2su5jia3f71 \\ | |
| --region us-west-2 | |
| | | | |
| │ 7. Test OAuth token generation: │ | |
| │ curl -X POST https://<domain>.auth.region.amazoncognito.com/oauth2/token \\ │ | |
| │ -H "Content-Type: application/x-www-form-urlencoded" \\ │ | |
| │ -d "grant_type=client_credentials&client_id=<client-id>&client_secret=<client-secret>" │ | |
| └─────────────────────────────────────────────────────────────┘ | |
| 🔧 Option 2: Auth0 | |
| ┌─────────────────────────────────────────────────────────────┐ | |
| │ 1. Create Auth0 application (Machine to Machine) │ | |
| │ 2. Configure client credentials grant │ | |
| │ 3. Discovery URL: │ | |
| │ https://your-domain.auth0.com/.well-known/openid-configuration │ | |
| │ 4. Use client ID from Auth0 dashboard │ | |
| └─────────────────────────────────────────────────────────────┘ | |
| 🔧 Option 3: Google OAuth | |
| ┌─────────────────────────────────────────────────────────────┐ | |
| │ 1. Create OAuth 2.0 client in Google Cloud Console │ | |
| │ 2. Discovery URL: │ | |
| │ https://accounts.google.com/.well-known/openid-configuration │ | |
| │ 3. Use client ID from Google Cloud Console │ | |
| └─────────────────────────────────────────────────────────────┘ | |
| 📋 Gateway Configuration Format: | |
| authorizerConfiguration={ | |
| 'customJWTAuthorizer': { | |
| 'discoveryUrl': 'https://your-provider/.well-known/openid-configuration', | |
| 'allowedClients': ['your-client-id'] # OR use allowedAudiences | |
| } | |
| } | |
| 🚀 Easy Setup with AgentCore SDK: | |
| from bedrock_agentcore_starter_toolkit.operations.gateway.client import GatewayClient | |
| client = GatewayClient(region_name="us-west-2") | |
| # Auto-creates Cognito OAuth setup | |
| cognito_result = client.create_oauth_authorizer_with_cognito("my-gateway") | |
| ⚡ Required Parameters for create_gateway(): | |
| - name: Gateway name (required) | |
| - protocolType: 'MCP' (required) | |
| - roleArn: IAM role ARN (required) | |
| - authorizerType: 'CUSTOM_JWT' (required) | |
| - authorizerConfiguration: OAuth config (required) | |
| 🎯 Complete Working Example (Pre-configured Cognito): | |
| ┌─────────────────────────────────────────────────────────────┐ | |
| │ User Pool ID: us-west-2_<user_pool_id> │ | |
| │ Client ID: <client_id> │ | |
| │ Discovery URL: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_<user_pool_id>/.well-known/openid-configuration │ | |
| │ │ | |
| │ Test Token Generation: │ | |
| │ curl -X POST https://gateway-pool-<unique_id>.auth.us-west-2.amazoncognito.com/oauth2/token \\ │ | |
| │ -H "Content-Type: application/x-www-form-urlencoded" \\ │ | |
| │ -d "grant_type=client_credentials&client_id=<client_id>&client_secret=<client_secret>" │ | |
| │ │ | |
| │ Gateway Configuration (already in your code): │ | |
| │ authorizerConfiguration={ │ | |
| │ 'customJWTAuthorizer': { │ | |
| │ 'discoveryUrl': 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_<user_pool_id>/.well-known/openid-configuration', │ | |
| │ 'allowedClients': ['<client_id>'] │ | |
| │ } │ | |
| │ } │ | |
| └─────────────────────────────────────────────────────────────┘ | |
| ''' | |
| print(setup_guide) | |
| return setup_guide | |
| def create_lambda_execution_role(role_name: str = "AgentCoreGatewayLambdaRole") -> str: | |
| """Create IAM role for Lambda execution with proper trust policy""" | |
| try: | |
| iam_client = boto3.client('iam', region_name='us-west-2') | |
| # Trust policy allowing Lambda service to assume this role | |
| trust_policy = { | |
| "Version": "2012-10-17", | |
| "Statement": [ | |
| { | |
| "Effect": "Allow", | |
| "Principal": { | |
| "Service": "lambda.amazonaws.com" | |
| }, | |
| "Action": "sts:AssumeRole" | |
| } | |
| ] | |
| } | |
| print(f"📋 Creating IAM role for Lambda: {role_name}") | |
| try: | |
| # Create the role | |
| response = iam_client.create_role( | |
| RoleName=role_name, | |
| AssumeRolePolicyDocument=json.dumps(trust_policy), | |
| Description="Execution role for AgentCore Gateway Lambda functions", | |
| Tags=[ | |
| {'Key': 'Project', 'Value': 'AgentCore-Gateway-Demo'}, | |
| {'Key': 'Purpose', 'Value': 'Lambda-Execution'} | |
| ] | |
| ) | |
| role_arn = response['Role']['Arn'] | |
| print(f"✅ IAM role created: {role_arn}") | |
| except iam_client.exceptions.EntityAlreadyExistsException: | |
| print(f"⚠️ Role {role_name} already exists, getting ARN...") | |
| response = iam_client.get_role(RoleName=role_name) | |
| role_arn = response['Role']['Arn'] | |
| print(f"✅ Using existing role: {role_arn}") | |
| # Attach basic Lambda execution policy | |
| policy_arn = 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' | |
| try: | |
| iam_client.attach_role_policy( | |
| RoleName=role_name, | |
| PolicyArn=policy_arn | |
| ) | |
| print(f"✅ Attached policy: AWSLambdaBasicExecutionRole") | |
| except iam_client.exceptions.NoSuchEntityException: | |
| print(f"⚠️ Policy already attached or role doesn't exist") | |
| except Exception as e: | |
| print(f"⚠️ Policy attachment warning: {e}") | |
| # Wait for role propagation | |
| print("⏳ Waiting for IAM role propagation...") | |
| import time | |
| time.sleep(10) # AWS IAM eventual consistency | |
| return role_arn | |
| except Exception as e: | |
| print(f"❌ Failed to create IAM role: {e}") | |
| # Get current account ID for fallback | |
| try: | |
| sts_client = boto3.client('sts') | |
| account_id = sts_client.get_caller_identity()['Account'] | |
| fallback_arn = f"arn:aws:iam::{account_id}:role/{role_name}" | |
| print(f"💡 Using fallback ARN: {fallback_arn}") | |
| return fallback_arn | |
| except: | |
| return f"arn:aws:iam::123456789012:role/{role_name}" | |
| def create_lambda_function_for_gateway(function_name: str = "gateway-calculator-tool") -> str: | |
| """Create an actual Lambda function for Gateway target""" | |
| import zipfile | |
| import tempfile | |
| import os | |
| lambda_code = '''import json | |
| def lambda_handler(event, context): | |
| """Calculator tool as Lambda function for Gateway""" | |
| try: | |
| # Parse input from Gateway | |
| body = json.loads(event.get('body', '{}')) if isinstance(event.get('body'), str) else event.get('body', {}) | |
| expression = body.get('expression', '2+2') | |
| # Perform calculation (safely evaluate simple expressions) | |
| result = eval(expression.replace("^", "**")) | |
| return { | |
| 'statusCode': 200, | |
| 'headers': { | |
| 'Content-Type': 'application/json' | |
| }, | |
| 'body': json.dumps({ | |
| 'result': result, | |
| 'expression': expression, | |
| 'status': 'success' | |
| }) | |
| } | |
| except Exception as e: | |
| return { | |
| 'statusCode': 400, | |
| 'headers': { | |
| 'Content-Type': 'application/json' | |
| }, | |
| 'body': json.dumps({ | |
| 'result': None, | |
| 'expression': expression if 'expression' in locals() else 'unknown', | |
| 'error': str(e), | |
| 'status': 'error' | |
| }) | |
| } | |
| ''' | |
| try: | |
| # Create temporary directory and file | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| lambda_file = os.path.join(temp_dir, "lambda_function.py") | |
| zip_file = os.path.join(temp_dir, "function.zip") | |
| # Write Lambda code to file | |
| with open(lambda_file, 'w') as f: | |
| f.write(lambda_code) | |
| # Create ZIP file | |
| with zipfile.ZipFile(zip_file, 'w') as zf: | |
| zf.write(lambda_file, "lambda_function.py") | |
| # Read ZIP file content | |
| with open(zip_file, 'rb') as f: | |
| zip_content = f.read() | |
| print(f"📦 Creating Lambda function: {function_name}") | |
| # Create IAM role for Lambda execution | |
| lambda_role_arn = create_lambda_execution_role() | |
| # Create Lambda function using boto3 | |
| lambda_client = boto3.client('lambda', region_name='us-west-2') | |
| try: | |
| response = lambda_client.create_function( | |
| FunctionName=function_name, | |
| Runtime='python3.12', | |
| Role=lambda_role_arn, # Use dynamically created role | |
| Handler='lambda_function.lambda_handler', | |
| Code={'ZipFile': zip_content}, | |
| Description='Calculator tool for AgentCore Gateway', | |
| Timeout=30, | |
| MemorySize=128, | |
| Tags={ | |
| 'Project': 'AgentCore-Gateway-Demo', | |
| 'Purpose': 'Calculator-Tool' | |
| } | |
| ) | |
| function_arn = response['FunctionArn'] | |
| print(f"✅ Lambda function created successfully!") | |
| print(f" Function Name: {function_name}") | |
| print(f" Function ARN: {function_arn}") | |
| # Test the function | |
| print(f"🧪 Testing Lambda function...") | |
| test_response = lambda_client.invoke( | |
| FunctionName=function_name, | |
| Payload=json.dumps({ | |
| 'body': json.dumps({'expression': '10 + 5'}) | |
| }) | |
| ) | |
| result = json.loads(test_response['Payload'].read()) | |
| print(f"✅ Test result: {result}") | |
| return function_arn | |
| except lambda_client.exceptions.ResourceConflictException: | |
| print(f"⚠️ Function {function_name} already exists, getting ARN...") | |
| response = lambda_client.get_function(FunctionName=function_name) | |
| function_arn = response['Configuration']['FunctionArn'] | |
| print(f"✅ Using existing function ARN: {function_arn}") | |
| return function_arn | |
| except Exception as e: | |
| print(f"❌ Failed to create Lambda function: {e}") | |
| print("💡 Using placeholder ARN for demo purposes") | |
| return f"arn:aws:lambda:us-west-2:<account_id>:function:{function_name}" | |
| if __name__ == "__main__": | |
| import sys | |
| if len(sys.argv) > 1: | |
| command = sys.argv[1] | |
| if command == "setup": | |
| # Demo Gateway setup | |
| gateway_manager = demo_gateway_setup() | |
| elif command == "test": | |
| # Test Gateway usage | |
| demo_gateway_usage() | |
| elif command == "lambda": | |
| # Create actual Lambda function | |
| lambda_arn = create_lambda_function_for_gateway() | |
| print(f"\n🎯 Lambda function ready for Gateway: {lambda_arn}") | |
| elif command == "oauth": | |
| # Show OAuth setup guide | |
| show_oauth_setup_guide() | |
| elif command == "api-creds": | |
| # Show API credential setup guide | |
| show_api_credential_setup_guide() | |
| else: | |
| print("Usage: python basic_gateway.py [setup|test|lambda|oauth|api-creds]") | |
| print("Commands:") | |
| print(" setup - Demo Gateway setup and configuration") | |
| print(" test - Test Gateway usage with agents") | |
| print(" lambda - Create actual Lambda function for Gateway target") | |
| print(" oauth - Show how to set up OAuth authentication (REQUIRED)") | |
| print(" api-creds - Show how to set up API key credential providers for OpenAPI targets") | |
| else: | |
| # Run Gateway-enabled agent server | |
| print("🚀 Starting Gateway-enabled Agent...") | |
| print("Gateway provides secure access to external tools and APIs") | |
| print("Available endpoints:") | |
| print(" POST /invocations - Main agent endpoint") | |
| print(" GET /ping - Health check") | |
| app.run(host="0.0.0.0", port=8080) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment