Skip to content

Instantly share code, notes, and snippets.

@yike5460
Created July 31, 2025 09:15
Show Gist options
  • Select an option

  • Save yike5460/77a17605b48fd0ca0840da0b78a2b5a0 to your computer and use it in GitHub Desktop.

Select an option

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
# 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