diff --git a/.gitignore b/.gitignore index 0274f0d5f..7719bde91 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,6 @@ pyrightconfig.json ### Zip file for prereq infa deployment lambda.zip + +### AgentCore ### +.agentcore_primitives/ diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/DEPLOYMENT_GUIDE.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/DEPLOYMENT_GUIDE.md new file mode 100644 index 000000000..dded37f14 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/DEPLOYMENT_GUIDE.md @@ -0,0 +1,229 @@ +# AgentCore Insurance App - Deployment Guide + +## Quick Deployment Options + +### Option 1: Automated Deployment (5-10 minutes) + +**Best for**: Quick setup, demos, getting started + +```bash +./deploy_all.sh +``` + +This single command deploys everything automatically. + +### Option 2: Manual Step-by-Step (20-30 minutes) + +**Best for**: Learning, customization, troubleshooting + +Follow the detailed steps in [README.md](README.md) + +--- + +## Automated Deployment Details + +### Prerequisites + +Before running `./deploy_all.sh`, ensure you have: + +- ✅ AWS CLI configured with admin access +- ✅ Python 3.10+ +- ✅ jq command-line tool +- ✅ Docker Desktop (for local testing) +- ✅ Bedrock model access enabled + +### What Gets Deployed + +1. **Insurance API** (Lambda + API Gateway) + - FastAPI application + - Auto insurance data endpoints + - API key authentication + +2. **MCP Gateway** (AgentCore Gateway) + - OAuth2 authentication (Cognito) + - OpenAPI integration + - MCP tool exposure + +3. **AgentCore Identity** + - Workload Identity (inbound auth) + - API Key Provider (outbound auth) + - Secure credential management + +4. **Strands Agent** (AgentCore Runtime) + - IAM execution role + - Cognito user pool + - Agent runtime deployment + - Memory integration + - Observability enabled + +### Deployment Time + +- **Automated**: 5-10 minutes +- **Manual**: 20-30 minutes + +### Post-Deployment + +After successful deployment: + +```bash +# View logs +agentcore logs --tail 50 + +# Invoke agent +cd cloud_strands_insurance_agent +source .env +cd 1_pre_req_setup/cognito_auth +./refresh_token.sh +export BEARER_TOKEN=$(jq -r '.bearer_token' cognito_config.json) +cd ../.. + +agentcore invoke --bearer-token $BEARER_TOKEN \ + '{"user_input": "Can you help me get a quote?", "actor_id": "user123"}' +``` + +--- + +## Manual Deployment Steps + +### Step 1: Deploy Insurance API + +```bash +cd cloud_insurance_api/deployment +./deploy.sh +``` + +### Step 2: Setup MCP Gateway + +```bash +cd ../../cloud_mcp_server +./setup.sh +``` + +### Step 3: Configure Identity + +```bash +cd ../cloud_strands_insurance_agent +cp .env_example .env +# Edit .env with your values +./setup_identity.sh +``` + +### Step 4: Deploy Agent + +```bash +# Setup IAM +cd 1_pre_req_setup/iam_roles_setup +./setup_role.sh + +# Setup Cognito +cd ../cognito_auth +./setup_cognito.sh + +# Configure and deploy +cd ../.. +ROLE_ARN=$(aws iam get-role --role-name BedrockAgentCoreExecutionRole --query 'Role.Arn' --output text) +agentcore configure -e "agentcore_strands_insurance_agent.py" --name insurance_agent_strands -er $ROLE_ARN + +source .env +agentcore launch \ + -env MCP_SERVER_URL="$MCP_SERVER_URL" \ + -env MCP_ACCESS_TOKEN="$MCP_ACCESS_TOKEN" \ + -env MODEL_NAME="$MODEL_NAME" \ + -env AWS_REGION="$AWS_REGION" \ + -env WORKLOAD_IDENTITY_ARN="$WORKLOAD_IDENTITY_ARN" \ + -env WORKLOAD_IDENTITY_ID="$WORKLOAD_IDENTITY_ID" \ + -env API_KEY_PROVIDER_NAME="$API_KEY_PROVIDER_NAME" +``` + +--- + +## Troubleshooting + +### Automated Deployment Failed + +If `./deploy_all.sh` fails: + +1. **Check which step failed** - The script shows clear step numbers +2. **Run that step manually** - Follow the manual steps for that component +3. **Check logs** - Look at the error message for details +4. **Continue from where it failed** - You don't need to start over + +### Common Issues + +| Issue | Solution | +|-------|----------| +| AWS CLI not configured | Run `aws configure` | +| Python not found | Install Python 3.10+ | +| jq not found | Install jq: `brew install jq` (macOS) | +| Docker not running | Start Docker Desktop | +| Region mismatch | Ensure `.env` has `AWS_REGION="us-east-1"` | +| Token expired | Run `./refresh_token.sh` in cognito_auth folder | + +### Getting Help + +- **Deployment Checklist**: [cloud_strands_insurance_agent/DEPLOYMENT_CHECKLIST.md](cloud_strands_insurance_agent/DEPLOYMENT_CHECKLIST.md) +- **Troubleshooting Guide**: [cloud_strands_insurance_agent/DEPLOYMENT_TROUBLESHOOTING.md](cloud_strands_insurance_agent/DEPLOYMENT_TROUBLESHOOTING.md) +- **Identity Guide**: [cloud_strands_insurance_agent/IDENTITY_QUICK_START.md](cloud_strands_insurance_agent/IDENTITY_QUICK_START.md) + +--- + +## Verification + +After deployment, verify everything works: + +```bash +# 1. Check agent is deployed +cat cloud_strands_insurance_agent/.bedrock_agentcore.yaml + +# 2. Check environment variables are set +cat cloud_strands_insurance_agent/.bedrock_agentcore.yaml | grep -A 20 environment + +# 3. Test invocation +cd cloud_strands_insurance_agent +agentcore invoke '{"user_input": "test"}' --bearer-token $BEARER_TOKEN + +# 4. Check logs +agentcore logs --tail 50 +``` + +### Success Criteria + +✅ Insurance API returns 200 OK +✅ MCP Gateway has tools available +✅ Identity resources created +✅ Agent responds to invocations +✅ No "client initialization failed" errors +✅ Memory is created and working + +--- + +## Clean Up + +To remove all deployed resources: + +```bash +# Delete agent runtime +agentcore delete + +# Delete CloudFormation stacks +aws cloudformation delete-stack --stack-name insurance-api-dev + +# Delete Identity resources +cd cloud_strands_insurance_agent +python cleanup_duplicate_memories.py --delete + +# Delete IAM roles and Cognito (manual via AWS Console) +``` + +--- + +## Next Steps + +After successful deployment: + +1. **Explore the agent**: Try different insurance queries +2. **Check observability**: View traces in CloudWatch GenAI Dashboard +3. **Test memory**: Ask follow-up questions with the same actor_id +4. **Customize**: Modify the agent code for your use case + +For more information, see the main [README.md](README.md) diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/README.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/README.md index c037822dc..3ba4ba611 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/README.md +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/README.md @@ -34,9 +34,41 @@ The solution consists of three main components: - Bedrock model access enabled in your AWS account - jq command-line JSON processor -## Setup Process +## Quick Start -The setup involves three main steps: +📚 **[Complete Deployment Guide](DEPLOYMENT_GUIDE.md)** - Comprehensive guide with both automated and manual options + +### Option A: Automated Full Deployment (Recommended) + +Deploy everything at once with a single script: + +```bash +# Run the automated deployment script +./deploy_all.sh +``` + +**Time**: 5-10 minutes | **Best for**: Quick setup, demos + +This script will: +1. ✅ Deploy the Insurance API +2. ✅ Setup the MCP Gateway +3. ✅ Configure AgentCore Identity +4. ✅ Deploy the Strands Agent +5. ✅ Test the deployment + +See [Automated Deployment](#automated-deployment) section below for details. + +### Option B: Manual Step-by-Step Setup + +**Time**: 20-30 minutes | **Best for**: Learning, customization + +Follow the detailed steps below for manual deployment and learning. + +--- + +## Manual Setup Process + +The setup involves four main steps: ### 1. Deploy the Cloud Insurance API @@ -54,23 +86,60 @@ This deploys the FastAPI application using AWS SAM and creates all necessary res Next, configure the AWS Bedrock AgentCore Gateway to expose the insurance API as an MCP tool: +**Option A: Automated Setup (Recommended)** ```bash -cd ../cloud_mcp_server +cd ../../cloud_mcp_server + +# Run automated setup script +./setup.sh +``` + +**Option B: Manual Setup** +```bash +cd ../../cloud_mcp_server + +# Create and activate virtual environment (required on macOS) +python3 -m venv .venv +source .venv/bin/activate + +# Install required packages +pip install -r requirements.txt # Setup AgentCore Gateway with OpenAPI integration python agentcore_gateway_setup_openapi.py ``` +**Note**: On macOS, you must use a virtual environment due to system Python restrictions (PEP 668). The virtual environment keeps packages isolated from your system Python. + This creates an AgentCore Gateway with OAuth authorization that provides an MCP endpoint for accessing insurance API tools. -### 3. Deploy the Strands Insurance Agent +### 3. Setup AgentCore Identity (Optional but Recommended) -Finally, deploy the agent that will interact with the MCP Gateway: +Configure AgentCore Identity for secure credential management: ```bash cd ../cloud_strands_insurance_agent -# Setup IAM execution role +# Copy example environment file and edit with your values +cp .env_example .env +#nano .env + +# Run the identity setup script +./setup_identity.sh +``` + +This sets up: +- **Phase 1 (Outbound)**: API Key Provider for Insurance API access +- **Phase 2 (Inbound)**: Workload Identity for agent authentication + +See [IDENTITY_INTEGRATION.md](cloud_strands_insurance_agent/IDENTITY_INTEGRATION.md) for detailed information. + +### 4. Deploy the Strands Insurance Agent + +Finally, deploy the agent that will interact with the MCP Gateway: + +```bash +# Setup IAM execution role (if not already done) cd 1_pre_req_setup/iam_roles_setup ./setup_role.sh @@ -81,10 +150,6 @@ cd ../cognito_auth # Go back to project root cd ../../ -# Copy example environment file and edit with your values -cp .env_example .env -nano .env - # Get your role ARN from the setup output or AWS console ROLE_ARN=$(aws iam get-role --role-name BedrockAgentCoreExecutionRole --query 'Role.Arn' --output text) @@ -96,10 +161,13 @@ agentcore configure -e "agentcore_strands_insurance_agent.py" \ # Load environment variables from .env file source .env -# Deploy to cloud +# Deploy to cloud (with Identity configuration if set up) agentcore launch \ -env MCP_SERVER_URL=$MCP_SERVER_URL \ - -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN + -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN \ + -env WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN \ + -env WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID \ + -env API_KEY_PROVIDER_NAME=$API_KEY_PROVIDER_NAME ``` ## Component Details @@ -136,11 +204,25 @@ See the [Cloud Strands Insurance Agent README](cloud_strands_insurance_agent/REA ## Benefits of AgentCore - **Authentication**: Secure access through OAuth2 with Cognito +- **Identity Management**: Centralized credential management with AgentCore Identity - **Observability**: Monitoring and logging through CloudWatch +- **Memory**: Conversation history and context with AgentCore Memory - **Scalability**: Managed runtime environments that scale automatically - **Compliance**: Managed service with built-in security controls - **Cost Optimization**: Pay-per-use pricing model +## AgentCore Services Used + +This demo showcases the following AgentCore services: + +1. **AgentCore Runtime**: Managed execution environment for the Strands agent +2. **AgentCore Gateway**: MCP server that exposes Insurance API as tools +3. **AgentCore Identity**: + - Workload Identity for agent authentication (Inbound) + - API Key Provider for secure credential storage (Outbound) +4. **AgentCore Memory**: Conversation history and customer preference tracking +5. **AgentCore Observability**: CloudWatch integration for monitoring and tracing + ## Usage Example ```bash @@ -157,7 +239,7 @@ export BEARER_TOKEN=$(jq -r '.bearer_token' cognito_config.json) cd ../../ # Invoke agent -agentcore invoke --bearer-token $BEARER_TOKEN '{"user_input": "Can you help me get a quote for auto insurance?"}' +agentcore invoke '{"user_input": "Can you help me get a quote for auto insurance?"}' --bearer-token $BEARER_TOKEN ``` ## Troubleshooting @@ -170,6 +252,44 @@ agentcore invoke --bearer-token $BEARER_TOKEN '{"user_input": "Can you help me g - **IAM role errors**: Make sure the IAM role has all required permissions specified in `iam_roles_setup/README.md` - **Cognito authentication issues**: Check the documentation in `cognito_auth/README.md` for troubleshooting +## Automated Deployment + +The `deploy_all.sh` script automates the entire deployment process: + +```bash +# Make the script executable +chmod +x deploy_all.sh + +# Run the automated deployment +./deploy_all.sh +``` + +### What the Script Does + +1. **Checks Prerequisites**: Verifies AWS CLI, Python, jq, and Docker are installed +2. **Deploys Insurance API**: Runs the SAM deployment for the Lambda function +3. **Sets up MCP Gateway**: Creates the AgentCore Gateway with OAuth +4. **Configures Identity**: Sets up workload identity and API key provider +5. **Deploys Agent**: Configures IAM, Cognito, and deploys the Strands agent +6. **Tests Deployment**: Invokes the agent to verify everything works + +### Auto-Configuration + +The script automatically: +- Extracts API Gateway URL from CloudFormation +- Configures MCP server URL and access token +- Sets up environment variables +- Creates `.env` files with correct values + +### Troubleshooting Automated Deployment + +If the automated deployment fails: +1. Check the error message for which step failed +2. Run that step manually following the manual setup instructions +3. Continue from where it failed + +--- + ## Clean Up When you're done using the agentcore app, follow these steps to clean up resources: diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_insurance_api/deployment/deploy.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_insurance_api/deployment/deploy.sh index 6e58712c7..280f28fbc 100755 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_insurance_api/deployment/deploy.sh +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_insurance_api/deployment/deploy.sh @@ -60,7 +60,7 @@ cp ../local_insurance_api/data/*.json build/data/ # Install dependencies in the build directory echo "Installing dependencies..." -pip install -r ../local_insurance_api/requirements.txt -t build/ +python3 -m pip install -r ../local_insurance_api/requirements.txt -t build/ # Package the application echo "Packaging the application..." diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/.env_example b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/.env_example index f66cbbb9b..10f016738 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/.env_example +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/.env_example @@ -12,13 +12,13 @@ # AWS Configuration # ============================================================================= # AWS_REGION: The AWS region where your resources are deployed -# Example: us-west-2, us-east-1, eu-west-1 -AWS_REGION=us-west-2 +# Example: us-east-1, us-west-2, eu-west-1 +AWS_REGION=us-east-1 # ENDPOINT_URL: Amazon Bedrock AgentCore control plane endpoint for your region # Format: https://bedrock-agentcore-control..amazonaws.com # Replace with your AWS region -ENDPOINT_URL=https://bedrock-agentcore-control.us-west-2.amazonaws.com +ENDPOINT_URL=https://bedrock-agentcore-control.us-east-1.amazonaws.com # ============================================================================= # Gateway Configuration diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/QUICK_START.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/QUICK_START.md new file mode 100644 index 000000000..042a23bf5 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/QUICK_START.md @@ -0,0 +1,104 @@ +# MCP Server - Quick Start Guide + +## 🚀 Quick Setup (5 minutes) + +### Step 1: Configure Environment + +```bash +cd cloud_mcp_server + +# Copy example and edit +cp .env_example .env +nano .env +``` + +Required values: +- `AWS_REGION` - Your AWS region +- `API_GATEWAY_URL` - Your deployed Insurance API URL +- `API_KEY` - Your API key +- `OPENAPI_FILE_PATH` - Path to openapi.json + +### Step 2: Run Automated Setup + +```bash +./setup.sh +``` + +That's it! The script will: +- ✅ Create virtual environment +- ✅ Install all packages +- ✅ Set up the gateway +- ✅ Save configuration to `gateway_info.json` + +### Step 3: Get Your Credentials + +```bash +# View your MCP URL and token +cat gateway_info.json +``` + +Copy these values: +- `gateway.mcp_url` → Use as `MCP_SERVER_URL` +- `auth.access_token` → Use as `MCP_ACCESS_TOKEN` + +## 🔄 Token Refresh + +When your token expires: + +```bash +source .venv/bin/activate +python refresh_gateway_token.py +``` + +## 🐛 Troubleshooting + +### "No module named 'bedrock_agentcore_starter_toolkit'" + +**Solution**: Use virtual environment +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +### "externally-managed-environment" error + +**Solution**: This is macOS protecting system Python. Use the virtual environment (already handled by `./setup.sh`) + +### ".env file not found" + +**Solution**: +```bash +cp .env_example .env +nano .env # Add your values +``` + +## 📝 Manual Commands + +If you prefer manual control: + +```bash +# Create virtual environment +python3 -m venv .venv + +# Activate it +source .venv/bin/activate + +# Install packages +pip install -r requirements.txt + +# Run setup +python agentcore_gateway_setup_openapi.py + +# Refresh token (when needed) +python refresh_gateway_token.py +``` + +## 🎯 Next Steps + +After setup: +1. Copy `MCP_SERVER_URL` and `MCP_ACCESS_TOKEN` from `gateway_info.json` +2. Add them to your agent's `.env` file +3. Deploy your agent + +See [README.md](README.md) for full documentation. diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/README.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/README.md index 601462fbd..cc03a2ef7 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/README.md +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/README.md @@ -23,12 +23,26 @@ The Cloud MCP Server acts as a bridge between your LLM-powered agents and your i ## Installation 1. Clone this repository -2. Install dependencies: +2. Create a virtual environment and install dependencies: ```bash +# Create virtual environment (required on macOS) +python3 -m venv .venv + +# Activate virtual environment +source .venv/bin/activate + +# Install dependencies pip install -r requirements.txt ``` +**Note for macOS users**: You must use a virtual environment due to system Python restrictions (PEP 668). The virtual environment keeps packages isolated from your system Python. + +**For future sessions**: Remember to activate the virtual environment before running scripts: +```bash +source .venv/bin/activate +``` + ## Configuration Create a `.env` file in the `cloud_mcp_server` directory with the following variables: @@ -59,9 +73,29 @@ Replace the placeholder values with your actual configuration. ## Usage -Run the gateway setup script: +**Option A: Automated Setup (Recommended)** + +Run the automated setup script that handles virtual environment creation and package installation: ```bash +./setup.sh +``` + +This script will: +- Check for `.env` file (create from `.env_example` if needed) +- Create virtual environment if it doesn't exist +- Install all required packages +- Run the gateway setup script + +**Option B: Manual Setup** + +If you prefer manual control: + +```bash +# Make sure virtual environment is activated +source .venv/bin/activate + +# Run the setup script python agentcore_gateway_setup_openapi.py ``` @@ -144,10 +178,24 @@ with mcp_client: - **Gateway Creation Fails**: Check your AWS permissions and Bedrock service limits - **Invalid Endpoint**: Verify your API Gateway URL is accessible +## Token Refresh + +Access tokens expire after a period of time. To refresh your token: + +```bash +# Make sure virtual environment is activated +source .venv/bin/activate + +# Run the refresh script +python refresh_gateway_token.py +``` + +This will update the `gateway_info.json` file with a new access token. + ## Next Steps After setting up the MCP server: 1. Configure your agents to use the MCP URL and authentication -2. Set up token refresh for production use +2. Set up token refresh for production use (see Token Refresh section above) 3. Add monitoring and logging for gateway operations \ No newline at end of file diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/agentcore_gateway_setup_openapi.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/agentcore_gateway_setup_openapi.py index e0d4cdbd0..06d15e686 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/agentcore_gateway_setup_openapi.py +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/agentcore_gateway_setup_openapi.py @@ -7,8 +7,8 @@ The script uses environment variables loaded from a .env file for configuration: Environment Variables: - AWS_REGION (str): AWS region for the gateway (default: "us-west-2") - ENDPOINT_URL (str): Endpoint URL for Bedrock AgentCore (default: Bedrock AgentCore endpoint in us-west-2) + AWS_REGION (str): AWS region for the gateway (default: "us-east-1") + ENDPOINT_URL (str): Endpoint URL for Bedrock AgentCore (default: Bedrock AgentCore endpoint in us-east-1) GATEWAY_NAME (str): Name for the gateway (default: "InsuranceAPIGateway") GATEWAY_DESCRIPTION (str): Description for the gateway API_GATEWAY_URL (str): API Gateway URL for the insurance API @@ -34,8 +34,8 @@ load_dotenv() # Setup the client using environment variables -region = os.getenv("AWS_REGION", "us-west-2") -endpoint_url = os.getenv("ENDPOINT_URL", "https://bedrock-agentcore-control.us-west-2.amazonaws.com") +region = os.getenv("AWS_REGION", "us-east-1") +endpoint_url = os.getenv("ENDPOINT_URL", "https://bedrock-agentcore-control.us-east-1.amazonaws.com") print(f"Setting up Gateway client in region {region}") client = GatewayClient(endpoint_url=endpoint_url, region_name=region) @@ -49,12 +49,41 @@ print(f"Creating OAuth authorizer for gateway '{gateway_name}'") cognito_response = client.create_oauth_authorizer_with_cognito(gateway_name) -# Create the gateway -print(f"Creating MCP gateway '{gateway_name}'") -gateway = client.create_mcp_gateway( - name=gateway_name, - authorizer_config=cognito_response["authorizer_config"], -) +# Check if gateway already exists first +print(f"Checking if gateway '{gateway_name}' already exists...") +import boto3 +bedrock_client = boto3.client('bedrock-agentcore-control', + region_name=region, + endpoint_url=endpoint_url) + +gateway = None +try: + response = bedrock_client.list_gateways(maxResults=100) + for gw in response.get('gateways', []): + if gw['name'] == gateway_name: + gateway = gw + print(f"✓ Found existing gateway: {gateway['gatewayId']}") + break +except Exception as list_error: + print(f"⚠️ Error listing gateways: {list_error}") + +# If gateway doesn't exist, create it +if not gateway: + print(f"Creating new MCP gateway '{gateway_name}'") + try: + gateway = client.create_mcp_gateway( + name=gateway_name, + authorizer_config=cognito_response["authorizer_config"], + ) + print(f"✓ Created new gateway: {gateway['gatewayId']}") + except Exception as e: + if "ConflictException" in str(type(e).__name__) or "already exists" in str(e): + print(f"❌ Gateway exists but couldn't be found in list. This is a timing issue.") + print(f"Please wait a moment and run the script again, or run cleanup first:") + print(f" cd cloud_mcp_server && python cleanup_gateway.py") + raise e +else: + print(f"✓ Using existing gateway: {gateway['gatewayId']}") # Load the insurance API OpenAPI specification from environment or default path @@ -66,7 +95,7 @@ openapi_spec = json.load(f) # Set the API Gateway URL from environment variables -api_gateway_url = os.getenv("API_GATEWAY_URL", "https://i0zzy6t0x9.execute-api.us-west-2.amazonaws.com/dev") +api_gateway_url = os.getenv("API_GATEWAY_URL", "https://i0zzy6t0x9.execute-api.us-east-1.amazonaws.com/dev") # Add server URL if not present in OpenAPI spec if "servers" not in openapi_spec: @@ -80,19 +109,42 @@ # Create the OpenAPI target with OAuth2 configuration using Cognito print("Creating MCP gateway target with OpenAPI specification") -open_api_target = client.create_mcp_gateway_target( - gateway=gateway, - name="API", - target_type="openApiSchema", - target_payload={ - "inlinePayload": json.dumps(openapi_spec) - }, - credentials={ - "api_key": api_key, - "credential_location": credential_location, - "credential_parameter_name": credential_parameter_name - } -) +try: + open_api_target = client.create_mcp_gateway_target( + gateway=gateway, + name="API", + target_type="openApiSchema", + target_payload={ + "inlinePayload": json.dumps(openapi_spec) + }, + credentials={ + "api_key": api_key, + "credential_location": credential_location, + "credential_parameter_name": credential_parameter_name + } + ) + print(f"✓ Created new target: {open_api_target['targetId']}") +except Exception as e: + if "ConflictException" in str(type(e).__name__) or "already exists" in str(e): + print(f"⚠️ Target 'API' already exists, retrieving existing target...") + # List targets and find the one with matching name + import boto3 + bedrock_client = boto3.client('bedrock-agentcore-control', + region_name=region, + endpoint_url=endpoint_url) + response = bedrock_client.list_gateway_targets(gatewayArn=gateway['gatewayArn']) + open_api_target = None + for target in response.get('targets', []): + if target['name'] == 'API': + open_api_target = target + print(f"✓ Found existing target: {target['targetId']}") + break + + if not open_api_target: + print(f"❌ Could not find existing target 'API'") + raise e + else: + raise e # Print the gateway information print("\n✅ Gateway setup complete!") diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/cleanup_gateway.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/cleanup_gateway.py new file mode 100755 index 000000000..9d83559ef --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/cleanup_gateway.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Cleanup Bedrock AgentCore Gateway + +This script removes the gateway and associated resources created by the setup script. +Use this if you want to start fresh or remove the gateway. +""" + +import boto3 +import json +import os +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +region = os.getenv("AWS_REGION", "us-east-1") +endpoint_url = os.getenv("ENDPOINT_URL", "https://bedrock-agentcore-control.us-east-1.amazonaws.com") +gateway_name = os.getenv("GATEWAY_NAME", "InsuranceAPIGateway") + +print(f"Cleaning up gateway '{gateway_name}' in region {region}") + +# Create boto3 client +client = boto3.client('bedrock-agentcore-control', + region_name=region, + endpoint_url=endpoint_url) + +# Find the gateway +print(f"Looking for gateway '{gateway_name}'...") +response = client.list_gateways() +gateway = None +for gw in response.get('gateways', []): + if gw['name'] == gateway_name: + gateway = gw + print(f"✓ Found gateway: {gateway['gatewayId']}") + break + +if not gateway: + print(f"⚠️ Gateway '{gateway_name}' not found. Nothing to clean up.") + exit(0) + +# List and delete targets +print(f"Listing targets for gateway...") +try: + targets_response = client.list_gateway_targets(gatewayArn=gateway['gatewayArn']) + targets = targets_response.get('targets', []) + + if targets: + print(f"Found {len(targets)} target(s) to delete") + for target in targets: + print(f" Deleting target: {target['targetId']} ({target['name']})") + try: + client.delete_gateway_target( + gatewayArn=gateway['gatewayArn'], + targetId=target['targetId'] + ) + print(f" ✓ Deleted target: {target['targetId']}") + except Exception as e: + print(f" ⚠️ Error deleting target: {e}") + else: + print("No targets found") +except Exception as e: + print(f"⚠️ Error listing targets: {e}") + +# Delete the gateway +print(f"Deleting gateway: {gateway['gatewayId']}") +try: + client.delete_gateway(gatewayArn=gateway['gatewayArn']) + print(f"✓ Deleted gateway: {gateway['gatewayId']}") +except Exception as e: + print(f"❌ Error deleting gateway: {e}") + exit(1) + +# Clean up Cognito resources (optional - commented out by default) +# Uncomment if you want to also delete the Cognito user pool +""" +print("\nCleaning up Cognito resources...") +cognito_client = boto3.client('cognito-idp', region_name=region) + +# Find user pool by domain +domain_name = f"agentcore-{gateway_name.lower()}" +try: + pools_response = cognito_client.list_user_pools(MaxResults=60) + for pool in pools_response.get('UserPools', []): + pool_id = pool['Id'] + # Check if this pool has our domain + try: + domain_response = cognito_client.describe_user_pool_domain(Domain=domain_name) + if domain_response.get('DomainDescription', {}).get('UserPoolId') == pool_id: + print(f"Found user pool: {pool_id}") + # Delete domain first + print(f"Deleting domain: {domain_name}") + cognito_client.delete_user_pool_domain(Domain=domain_name, UserPoolId=pool_id) + print(f"✓ Deleted domain") + + # Delete user pool + print(f"Deleting user pool: {pool_id}") + cognito_client.delete_user_pool(UserPoolId=pool_id) + print(f"✓ Deleted user pool") + break + except: + continue +except Exception as e: + print(f"⚠️ Error cleaning up Cognito: {e}") +""" + +print("\n✅ Cleanup complete!") +print("\nNote: Cognito resources (user pool, domain) were NOT deleted.") +print("To delete them manually, uncomment the Cognito cleanup section in this script.") diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/setup.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/setup.sh new file mode 100755 index 000000000..8fda61f2f --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_mcp_server/setup.sh @@ -0,0 +1,82 @@ +#!/bin/bash +set -e + +echo "==========================================" +echo "MCP Server Setup" +echo "==========================================" +echo "" + +# Check if .env file exists +if [ ! -f .env ]; then + echo "⚠️ .env file not found" + echo "Please create .env file from .env_example:" + echo " cp .env_example .env" + echo " nano .env # Edit with your values" + echo "" + read -p "Do you want to create .env from .env_example now? (y/n) " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + cp .env_example .env + echo "✓ Created .env file" + echo "Please edit .env with your values and run this script again" + exit 0 + else + exit 1 + fi +fi + +echo "✓ .env file found" +echo "" + +# Check if virtual environment exists +if [ ! -d ".venv" ]; then + echo "Creating virtual environment..." + python3 -m venv .venv + echo "✓ Virtual environment created" +else + echo "✓ Virtual environment already exists" +fi +echo "" + +# Activate virtual environment +echo "Activating virtual environment..." +source .venv/bin/activate +echo "✓ Virtual environment activated" +echo "" + +# Install requirements +echo "Installing requirements..." +pip install -r requirements.txt +echo "✓ Requirements installed" +echo "" + +# Run gateway setup +echo "==========================================" +echo "Running Gateway Setup" +echo "==========================================" +echo "" +python agentcore_gateway_setup_openapi.py + +if [ $? -eq 0 ]; then + echo "" + echo "==========================================" + echo "Setup Complete!" + echo "==========================================" + echo "" + echo "Gateway information saved to gateway_info.json" + echo "" + echo "Next steps:" + echo "1. Note your MCP_SERVER_URL and MCP_ACCESS_TOKEN from gateway_info.json" + echo "2. Add these to your agent's .env file" + echo "3. Deploy your agent" + echo "" + echo "To refresh your token later:" + echo " source .venv/bin/activate" + echo " python refresh_gateway_token.py" + echo "" +else + echo "" + echo "❌ Setup failed" + echo "Please check the error messages above" + exit 1 +fi diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.dockerignore b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.dockerignore index 97f25b14f..aa330e7aa 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.dockerignore +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.dockerignore @@ -60,10 +60,8 @@ docs/ # Project specific tests/ -# Bedrock AgentCore specific - keep config but exclude runtime files -.bedrock_agentcore.yaml -Dockerfile -.dockerignore +# Bedrock AgentCore specific - exclude local build artifacts only +# Note: Dockerfile and .bedrock_agentcore.yaml are needed for deployment # Keep wheelhouse for offline installations # wheelhouse/ diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env.bak b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env.bak new file mode 100644 index 000000000..12c8602dc --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env.bak @@ -0,0 +1,115 @@ +# ============================================================================= +# STRANDS INSURANCE AGENT ENVIRONMENT CONFIGURATION +# ============================================================================= +# Copy this file to .env and update the values below with your specific configuration +# +# Instructions: +# 1. Copy this file: cp .env_example .env +# 2. Update each value according to the instructions below +# 3. Never commit the .env file to version control (it's in .gitignore) + +# ============================================================================= +# MCP Server Configuration +# ============================================================================= +# MCP_SERVER_URL: The URL of your deployed MCP server through AgentCore Gateway +# Get this from the gateway_info.json file after deploying the MCP server +# Format: https://.gateway.bedrock-agentcore..amazonaws.com/mcp +MCP_SERVER_URL="https://insuranceapigateway-vb6ovw8uo1.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp" + +# MCP_ACCESS_TOKEN: JWT access token for authenticating with the MCP server +# IMPORTANT: This token expires and must be refreshed regularly +# +# How to get this token: +# 1. Run the refresh_token.sh script in this directory +# 2. Or get it from the gateway_info.json file after MCP server deployment +# 3. Token typically expires in 1 hour - use refresh_token.sh to get a new one +# +# Note: This is a long JWT token string starting with "eyJ" +MCP_ACCESS_TOKEN="eyJraWQiOiJXWGVZS0l4MFJ0Tks4YnpVeHh2ZHFtVk1FZ0xRZHIyWDRUTmhEK05iTWV3PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI0OXJzZmpqOWlvcThmcWx1YXJuam5nbWtjMyIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiSW5zdXJhbmNlQVBJR2F0ZXdheVwvaW52b2tlIiwiYXV0aF90aW1lIjoxNzYwMDA0OTA2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV93OUFRZnlaVlkiLCJleHAiOjE3NjAwMDg1MDYsImlhdCI6MTc2MDAwNDkwNiwidmVyc2lvbiI6MiwianRpIjoiMzhkMGIwYWEtYjU1YS00NjFhLTk0OGQtZjA4ZDQ2MDJmNmNiIiwiY2xpZW50X2lkIjoiNDlyc2Zqajlpb3E4ZnFsdWFybmpuZ21rYzMifQ.gL7pP3nrEgcn7BGrePiCv1c7PJqbA57ma2_LUK2asMZj3HgE9xfnaQwavwKQSJlNbC8KnE0UOdIddHBl7031pYhpER8hcFrWF6Q3XQCB8D-wEkWkqxlQEzJzBycm7Q-K-GBRX2IONdOeVMTTmibEpszpXbS9JPjrVBZUku-kT_wve2EDojk_6uCSyIzRvncut6NQDJ5rwE9zAkM5GX95odCLd7TpFFAyngMyi9Ku7EtII41_w6bW2np-y8LJ5juNspj57vS72iwSoZeAGEag2OYuSb8t2OG2RrPaZ7jKTUBJlIfO2WNXw-neeJzvCqikZmZzftYEdrf-smbGI0vNUg" + +# ============================================================================= +# Model Configuration +# ============================================================================= +# MODEL_NAME: The Amazon Bedrock model to use for the insurance agent +# Available models include: +# - us.anthropic.claude-3-7-sonnet-20250219-v1:0 (Claude 3.7 Sonnet) +# - us.anthropic.claude-3-5-sonnet-20241022-v2:0 (Claude 3.5 Sonnet) +# - us.anthropic.claude-3-haiku-20240307-v1:0 (Claude 3 Haiku) +# +# Note: Ensure the model is available in your AWS region +MODEL_NAME="us.anthropic.claude-3-7-sonnet-20250219-v1:0" + +# ============================================================================= +# Optional Configuration +# ============================================================================= +# GATEWAY_INFO_FILE: Path to the gateway info file from MCP server deployment +# This file contains gateway details and is used by refresh_token.sh script +# Update the path if your MCP server deployment is in a different location +GATEWAY_INFO_FILE="../cloud_mcp_server/gateway_info.json" + +# ============================================================================= +# Memory Configuration (Optional) +# ============================================================================= +# AWS_REGION: AWS region for memory service (default: us-west-2) +AWS_REGION="us-east-1" + +# MEMORY_ID: Existing memory resource ID (optional but recommended) +# If not provided, the agent will search for an existing "InsuranceAgentMemory" +# or create a new one. Setting this avoids creating duplicate memories. +# Format: mem-xxxxxxxxxxxxxxxxxxxxxxxxxx +# To get your memory ID after first run: +# aws bedrock-agentcore-control list-memories --region us-east-1 +# MEMORY_ID="" + +# ============================================================================= +# AgentCore Identity Configuration +# ============================================================================= +# These values are generated by running: python identity_setup.py +# Run the identity setup script first to create the identity infrastructure + +# WORKLOAD_IDENTITY_ARN: ARN of the workload identity for this agent (Phase 2: Inbound) +# This identity represents the agent itself and is used to authenticate incoming requests +# Format: arn:aws:bedrock-agentcore:::workload-identity/ +# WORKLOAD_IDENTITY_ARN=arn:aws:bedrock-agentcore:us-east-1:200937443282:workload-identity-directory/default/workload-identity/insurance-agent-workload + +# WORKLOAD_IDENTITY_ID: ID of the workload identity +# WORKLOAD_IDENTITY_ID=insurance-agent-workload + +# API_KEY_PROVIDER_NAME: Name of the API key credential provider (Phase 1: Outbound) +# This provider manages the API key for accessing the Insurance API +# API_KEY_PROVIDER_NAME=InsuranceAPIKeyProvider + +# API_KEY: The actual API key for the Insurance API (used during identity setup) +# This is stored securely in the Identity service after running identity_setup.py +# API_KEY="" + +# ============================================================================= +# TOKEN REFRESH INSTRUCTIONS +# ============================================================================= +# The MCP_ACCESS_TOKEN expires regularly (typically every hour). +# To refresh your token: +# +# 1. Make sure you have the gateway_info.json file from MCP server deployment +# 2. Run: ./refresh_token.sh +# 3. The script will update your .env file with a new token automatically +# +# If you get authentication errors, your token has likely expired. + +# ============================================================================= +# IDENTITY SETUP INSTRUCTIONS +# ============================================================================= +# To set up AgentCore Identity for this agent: +# +# 1. Ensure you have completed the MCP gateway setup +# 2. Run: python identity_setup.py +# 3. The script will create: +# - Workload Identity for the agent (Phase 2: Inbound) +# - API Key Credential Provider (Phase 1: Outbound) +# 4. Update this .env file with the generated values +# +# This enables: +# - Secure credential management for external services +# - Authenticated user requests via workload identity +# - Automatic token rotation (future enhancement) +API_KEY="demo-insurance-api-key-12345" +API_KEY="demo-insurance-api-key-12345" diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env_example b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env_example index 03a934147..d8c2415e6 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env_example +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/.env_example @@ -14,7 +14,7 @@ # MCP_SERVER_URL: The URL of your deployed MCP server through AgentCore Gateway # Get this from the gateway_info.json file after deploying the MCP server # Format: https://.gateway.bedrock-agentcore..amazonaws.com/mcp -MCP_SERVER_URL="https://insurance-app-uniqueid.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp" +MCP_SERVER_URL="https://insurance-app-uniqueid.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp" # MCP_ACCESS_TOKEN: JWT access token for authenticating with the MCP server # IMPORTANT: This token expires and must be refreshed regularly @@ -45,7 +45,43 @@ MODEL_NAME="us.anthropic.claude-3-7-sonnet-20250219-v1:0" # GATEWAY_INFO_FILE: Path to the gateway info file from MCP server deployment # This file contains gateway details and is used by refresh_token.sh script # Update the path if your MCP server deployment is in a different location -GATEWAY_INFO_FILE="../cloud_mcp_server/1_pre_req_setup/gateway_info.json" +GATEWAY_INFO_FILE="../cloud_mcp_server/gateway_info.json" + +# ============================================================================= +# Memory Configuration (Optional) +# ============================================================================= +# AWS_REGION: AWS region for memory service (default: us-east-1) +AWS_REGION="us-east-1" + +# MEMORY_ID: Existing memory resource ID (optional but recommended) +# If not provided, the agent will search for an existing "InsuranceAgentMemory" +# or create a new one. Setting this avoids creating duplicate memories. +# Format: mem-xxxxxxxxxxxxxxxxxxxxxxxxxx +# To get your memory ID after first run: +# aws bedrock-agentcore-control list-memories --region us-east-1 +# MEMORY_ID="" + +# ============================================================================= +# AgentCore Identity Configuration +# ============================================================================= +# These values are generated by running: python identity_setup.py +# Run the identity setup script first to create the identity infrastructure + +# WORKLOAD_IDENTITY_ARN: ARN of the workload identity for this agent (Phase 2: Inbound) +# This identity represents the agent itself and is used to authenticate incoming requests +# Format: arn:aws:bedrock-agentcore:::workload-identity/ +# WORKLOAD_IDENTITY_ARN="" + +# WORKLOAD_IDENTITY_ID: ID of the workload identity +# WORKLOAD_IDENTITY_ID="" + +# API_KEY_PROVIDER_NAME: Name of the API key credential provider (Phase 1: Outbound) +# This provider manages the API key for accessing the Insurance API +# API_KEY_PROVIDER_NAME="InsuranceAPIKeyProvider" + +# API_KEY: The actual API key for the Insurance API (used during identity setup) +# This is stored securely in the Identity service after running identity_setup.py +# API_KEY="" # ============================================================================= # TOKEN REFRESH INSTRUCTIONS @@ -58,3 +94,20 @@ GATEWAY_INFO_FILE="../cloud_mcp_server/1_pre_req_setup/gateway_info.json" # 3. The script will update your .env file with a new token automatically # # If you get authentication errors, your token has likely expired. + +# ============================================================================= +# IDENTITY SETUP INSTRUCTIONS +# ============================================================================= +# To set up AgentCore Identity for this agent: +# +# 1. Ensure you have completed the MCP gateway setup +# 2. Run: python identity_setup.py +# 3. The script will create: +# - Workload Identity for the agent (Phase 2: Inbound) +# - API Key Credential Provider (Phase 1: Outbound) +# 4. Update this .env file with the generated values +# +# This enables: +# - Secure credential management for external services +# - Authenticated user requests via workload identity +# - Automatic token rotation (future enhancement) diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/README.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/README.md index 43dedf34b..c2ec4d426 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/README.md +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/README.md @@ -33,15 +33,27 @@ If you prefer a more customized setup, you can use the Python modules: ## Required Permissions The IAM role includes permissions for: -- ECR (container registry access) -- CloudWatch Logs -- X-Ray tracing -- CloudWatch metrics -- Bedrock AgentCore access tokens -- Bedrock model invocation +- **ECR (Elastic Container Registry)**: Access to pull Docker images + - `ecr:GetAuthorizationToken` - Get authentication token + - `ecr:BatchGetImage` - Pull container images + - `ecr:GetDownloadUrlForLayer` - Download image layers + - Resource: `arn:aws:ecr:*:ACCOUNT_ID:repository/bedrock-agentcore-*` (supports all agent repositories) +- **CloudWatch Logs**: Write application logs +- **X-Ray**: Distributed tracing +- **CloudWatch Metrics**: Publish custom metrics +- **Bedrock AgentCore**: Access tokens and workload identities +- **Bedrock Models**: Invoke foundation models +- **AgentCore Memory**: Store and retrieve conversation history + - `bedrock-agentcore:CreateMemory` - Create memory resources + - `bedrock-agentcore:GetMemory` - Retrieve memory resources + - `bedrock-agentcore:CreateEvent` - Save conversation events + - `bedrock-agentcore:RetrieveMemories` - Query stored memories + - Resource: `arn:aws:bedrock-agentcore:*:ACCOUNT_ID:memory/*` These permissions follow AWS best practices with least-privilege principle. +**Note**: The ECR permissions use a wildcard (`bedrock-agentcore-*`) to support any agent name you deploy. This is required for the agent runtime to pull your Docker images from ECR. + ## Prerequisites - AWS CLI installed and configured with appropriate permissions diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/config.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/config.py index a03403ff4..7e13a2a90 100755 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/config.py +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/config.py @@ -22,7 +22,7 @@ }, 'account': { 'id': '', - 'regions': 'us-east-1,us-west-2' + 'regions': 'us-east-1' }, 'ecr': { 'repository_name': 'bedrock-agentcore' @@ -103,7 +103,7 @@ def get_regions(config_data: Dict[str, Dict[str, str]]) -> List[str]: Returns: List of region strings """ - regions_str = config_data.get('account', {}).get('regions', 'us-east-1,us-west-2') + regions_str = config_data.get('account', {}).get('regions', 'us-east-1') return [r.strip() for r in regions_str.split(',') if r.strip()] def get_account_id(config_data: Dict[str, Dict[str, str]]) -> str: diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/policy_templates.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/policy_templates.py index 7380b12bd..5220a98d9 100755 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/policy_templates.py +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/policy_templates.py @@ -48,12 +48,12 @@ def get_ecr_policy(account_id: str, regions: List[str], repository_name: str = ' Args: account_id: The AWS account ID regions: List of AWS regions - repository_name: ECR repository name + repository_name: ECR repository name (supports wildcards) Returns: Dictionary containing the ECR policy statements """ - # ECR image access statement + # ECR image access statement - use wildcard to support any agent name ecr_image_access = { "Sid": "ECRImageAccess", "Effect": "Allow", @@ -61,7 +61,7 @@ def get_ecr_policy(account_id: str, regions: List[str], repository_name: str = ' "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer" ], - "Resource": [f"arn:aws:ecr:{region}:{account_id}:repository/{repository_name}" for region in regions] + "Resource": [f"arn:aws:ecr:{region}:{account_id}:repository/bedrock-agentcore-*" for region in regions] } # ECR token access statement @@ -209,6 +209,32 @@ def get_bedrock_models_policy(account_id: str, regions: List[str]) -> Dict[str, ] } +def get_memory_policy(account_id: str, regions: List[str]) -> Dict[str, Any]: + """ + Generate the AgentCore Memory access policy statement + + Args: + account_id: The AWS account ID + regions: List of AWS regions + + Returns: + Dictionary containing the Memory policy statement + """ + return { + "Sid": "MemoryAccess", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:CreateMemory", + "bedrock-agentcore:GetMemory", + "bedrock-agentcore:UpdateMemory", + "bedrock-agentcore:DeleteMemory", + "bedrock-agentcore:ListMemories", + "bedrock-agentcore:CreateEvent", + "bedrock-agentcore:RetrieveMemories" + ], + "Resource": [f"arn:aws:bedrock-agentcore:{region}:{account_id}:memory/*" for region in regions] + } + def build_execution_policy(config_data: Dict[str, Dict[str, str]]) -> Dict[str, Any]: """ Build the complete execution policy document based on configuration @@ -252,6 +278,9 @@ def build_execution_policy(config_data: Dict[str, Dict[str, str]]) -> Dict[str, if policies.get('enable_bedrock_models', 'true').lower() == 'true': statements.append(get_bedrock_models_policy(account_id, regions)) + if policies.get('enable_memory', 'true').lower() == 'true': + statements.append(get_memory_policy(account_id, regions)) + # Create the complete policy document policy_document = { "Version": "2012-10-17", diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/setup_role.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/setup_role.sh index e650cae19..3b475a813 100755 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/setup_role.sh +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/1_pre_req_setup/iam_roles_setup/setup_role.sh @@ -47,9 +47,9 @@ fi echo -e "${GREEN}Using AWS Account ID: ${ACCOUNT_ID}${NC}" # Get AWS Regions -echo -e "\nEnter comma-separated list of AWS regions to use (default: us-east-1,us-west-2):" +echo -e "\nEnter comma-separated list of AWS regions to use (default: us-east-1):" read -p "> " REGIONS_INPUT -REGIONS=${REGIONS_INPUT:-"us-east-1,us-west-2"} +REGIONS=${REGIONS_INPUT:-"us-east-1"} IFS=',' read -ra REGIONS_ARRAY <<< "$REGIONS" echo -e "${GREEN}Using regions: ${REGIONS}${NC}" diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/Dockerfile b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/Dockerfile index 32c585222..e52dcafc8 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/Dockerfile +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/Dockerfile @@ -1,42 +1,27 @@ -FROM public.ecr.aws/docker/library/python:3.12-slim -WORKDIR /app - -# Install system dependencies if needed - - -# Copy entire project (respecting .dockerignore) -COPY . . - -# Install dependencies +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +# All environment variables in one layer +ENV UV_SYSTEM_PYTHON=1 UV_COMPILE_BYTECODE=1 PYTHONUNBUFFERED=1 \ + AWS_REGION=us-east-1 AWS_DEFAULT_REGION=us-east-1 \ + DOCKER_CONTAINER=1 -# Install from requirements file -RUN python -m pip install --no-cache-dir -r requirements.txt +COPY requirements.txt requirements.txt +RUN uv pip install -r requirements.txt && \ + uv pip install aws-opentelemetry-distro>=0.10.1 -# Set AWS region environment variable -ENV AWS_REGION=us-west-2 -ENV AWS_DEFAULT_REGION=us-west-2 +EXPOSE 8080 8000 +# Copy entire project -# Signal that this is running in Docker for host binding logic -ENV DOCKER_CONTAINER=1 - - -RUN python -m pip install aws_opentelemetry_distro_genai_beta>=0.1.2 - - -# Create non-root user -RUN useradd -m -u 1000 bedrock_agentcore -USER bedrock_agentcore +COPY . . -EXPOSE 8080 # Use the full module path diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/README.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/README.md index c7d832445..69884641d 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/README.md +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/README.md @@ -33,6 +33,12 @@ cloud_strands_insurance_agent/ └── .env_example # Environment variable template ``` +## Quick Reference + +📋 **[Deployment Checklist](DEPLOYMENT_CHECKLIST.md)** - Step-by-step checklist to ensure successful deployment + +🔧 **[Troubleshooting Guide](DEPLOYMENT_TROUBLESHOOTING.md)** - Solutions to common deployment issues + ## Step 1: Set Up Prerequisites Set up the required IAM roles and Cognito authentication: @@ -98,7 +104,38 @@ sed -i "s|MCP_SERVER_URL=.*|MCP_SERVER_URL=\"$MCP_URL\"|g" .env sed -i "s|MCP_ACCESS_TOKEN=.*|MCP_ACCESS_TOKEN=\"$ACCESS_TOKEN\"|g" .env ``` -## Step 3: Configure Your Agent +## Step 3: Set Up AgentCore Identity (Optional but Recommended) + +Configure AgentCore Identity for secure credential management: + +```bash +# Run the automated identity setup +./setup_identity.sh +``` + +This will: +- Create a **Workload Identity** for the agent (Phase 2: Inbound authentication) +- Create an **API Key Credential Provider** for secure credential storage (Phase 1: Outbound) +- Save configuration to `identity_config.json` +- Optionally update your `.env` file with identity values + +**What you need**: +- `AWS_REGION` set in `.env` +- `GATEWAY_INFO_FILE` path pointing to `../cloud_mcp_server/gateway_info.json` +- `API_KEY` (optional) - Insurance API key for secure storage + +**What gets created**: +- Workload Identity: `insurance-agent-workload` +- API Key Provider: `InsuranceAPIKeyProvider` (if API_KEY is set) +- Environment variables: `WORKLOAD_IDENTITY_ARN`, `WORKLOAD_IDENTITY_ID` + +For more details, see: +- Quick guide: [IDENTITY_QUICK_START.md](IDENTITY_QUICK_START.md) +- Full documentation: [IDENTITY_INTEGRATION.md](IDENTITY_INTEGRATION.md) + +**Skip this step if**: You want to use basic authentication without Identity features. + +## Step 4: Configure Your Agent Configure the agent with your execution role (using the ARN from Step 1): @@ -117,7 +154,7 @@ This creates: - `Dockerfile` - Container build instructions (if not already present) - `.dockerignore` - Files to exclude from build -## Step 4: Local Testing +## Step 5: Local Testing Test your agent locally before cloud deployment: @@ -143,7 +180,7 @@ curl -X POST http://localhost:8080/invocations \ -d '{"user_input": "I need a quote for auto insurance"}' ``` -## Step 5: Deploy to Cloud +## Step 6: Deploy to Cloud Deploy your agent to AWS: @@ -151,19 +188,47 @@ Deploy your agent to AWS: # Load environment variables from .env file source .env +# Verify environment variables are loaded +echo "Checking environment variables..." +echo "MCP_SERVER_URL: $MCP_SERVER_URL" +echo "AWS_REGION: $AWS_REGION" +echo "MODEL_NAME: $MODEL_NAME" + # Deploy to AWS Bedrock AgentCore +# IMPORTANT: All -env flags are REQUIRED for the agent to function +# The agent code expects these environment variables at runtime + +# Full deployment with Identity (recommended): agentcore launch \ -env MCP_SERVER_URL=$MCP_SERVER_URL \ - -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN + -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN \ + -env MODEL_NAME=$MODEL_NAME \ + -env AWS_REGION=$AWS_REGION \ + -env WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN \ + -env WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID \ + -env API_KEY_PROVIDER_NAME=$API_KEY_PROVIDER_NAME + +# Basic deployment without Identity: +# agentcore launch \ +# -env MCP_SERVER_URL=$MCP_SERVER_URL \ +# -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN \ +# -env MODEL_NAME=$MODEL_NAME \ +# -env AWS_REGION=$AWS_REGION ``` +**Important Notes:** +- The `-env` flags pass environment variables to the agent runtime +- Without these variables, the agent will fail with "client initialization failed" +- After deployment, verify variables were set: `cat .bedrock_agentcore.yaml | grep -A 20 environment` +- If you see no `environment:` section, redeploy with the `-env` flags + This will: - Build and push Docker image to ECR - Create Bedrock AgentCore runtime - Deploy agent to the cloud - Return agent ARN for invocation -## Step 6: Invoke Your Agent +## Step 7: Invoke Your Agent Set your bearer token and invoke the deployed agent: @@ -181,7 +246,7 @@ export BEARER_TOKEN=$(jq -r '.bearer_token' cognito_config.json) cd ../../ # Invoke agent -agentcore invoke --bearer-token $BEARER_TOKEN '{"user_input": "Can you help me get a quote for auto insurance?"}' +agentcore invoke '{"user_input": "Can you help me get a quote for auto insurance?"}' --bearer-token $BEARER_TOKEN ``` ## Agent Code Structure @@ -233,6 +298,8 @@ python-dotenv>=1.0.0 ## Troubleshooting +### Common Deployment Issues + - **424 Failed Dependency**: Check agent logs in CloudWatch - **Token expired**: Run `./1_pre_req_setup/cognito_auth/refresh_token.sh` and update your `.env` file - **Permission denied**: Verify execution role has Bedrock model access @@ -241,11 +308,201 @@ python-dotenv>=1.0.0 - **IAM role errors**: Make sure the IAM role has all required permissions specified in `iam_roles_setup/README.md` - **Cognito authentication issues**: Check the documentation in `cognito_auth/README.md` for troubleshooting +### ECR Permission Errors + +If you see: `Access denied while validating ECR URI... requires permissions for ecr:GetAuthorizationToken, ecr:BatchGetImage, and ecr:GetDownloadUrlForLayer` + +**Solution**: Update your IAM role with ECR permissions: + +```bash +# Get your account ID +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + +# Update the IAM role policy +cat > /tmp/ecr-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ECRImageAccess", + "Effect": "Allow", + "Action": [ + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer" + ], + "Resource": [ + "arn:aws:ecr:*:${ACCOUNT_ID}:repository/bedrock-agentcore-*" + ] + }, + { + "Sid": "ECRTokenAccess", + "Effect": "Allow", + "Action": [ + "ecr:GetAuthorizationToken" + ], + "Resource": "*" + } + ] +} +EOF + +aws iam put-role-policy \ + --role-name BedrockAgentCoreExecutionRole \ + --policy-name ECRAccessPolicy \ + --policy-document file:///tmp/ecr-policy.json +``` + +**Note**: If you re-run the IAM setup script (`./1_pre_req_setup/iam_roles_setup/setup_role.sh`), it will automatically include these permissions. + +### Memory Not Working + +If memory features aren't working (no conversation history): + +**Solution**: Add memory permissions to your IAM role: + +```bash +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + +cat > /tmp/memory-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "MemoryAccess", + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:CreateMemory", + "bedrock-agentcore:GetMemory", + "bedrock-agentcore:UpdateMemory", + "bedrock-agentcore:DeleteMemory", + "bedrock-agentcore:ListMemories", + "bedrock-agentcore:CreateEvent", + "bedrock-agentcore:RetrieveMemories" + ], + "Resource": [ + "arn:aws:bedrock-agentcore:*:${ACCOUNT_ID}:memory/*" + ] + } + ] +} +EOF + +aws iam put-role-policy \ + --role-name BedrockAgentCoreExecutionRole \ + --policy-name MemoryAccessPolicy \ + --policy-document file:///tmp/memory-policy.json +``` + +**Note**: Re-running the IAM setup script will automatically include memory permissions. + +### Docker Build Errors + +If CodeBuild fails with "Dockerfile not found": +- Check that `.dockerignore` is not excluding the `Dockerfile` +- The Dockerfile should be in the root of your agent directory +- Re-run `agentcore configure` if needed + +### Client Initialization Failed + +If you see: `I'm sorry, I encountered an error: the client initialization failed` + +**Cause**: Environment variables were not passed to the agent runtime. + +**Solution**: Redeploy with all required environment variables: + +```bash +source .env + +agentcore launch \ + -env MCP_SERVER_URL=$MCP_SERVER_URL \ + -env MCP_ACCESS_TOKEN=$MCP_ACCESS_TOKEN \ + -env MODEL_NAME=$MODEL_NAME \ + -env AWS_REGION=$AWS_REGION \ + -env WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN \ + -env WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID +``` + +**Verify**: Check that environment variables are in the config: +```bash +cat .bedrock_agentcore.yaml | grep -A 20 environment +``` + +You should see an `environment:` section with all your variables. + ## Monitoring and Observability -- Monitor agent performance in CloudWatch -- View traces in AWS X-Ray -- Check agent logs for detailed error information +The agent now includes **AgentCore Observability** for comprehensive monitoring: + +### Automatic Instrumentation +- **Session Tracking**: Each invocation is tracked with a unique session ID +- **Distributed Tracing**: OpenTelemetry automatically traces agent execution +- **CloudWatch Integration**: Traces and metrics sent to CloudWatch automatically + +### Viewing Observability Data + +1. **GenAI Observability Dashboard**: + - Open CloudWatch Console → GenAI Observability + - View the **Bedrock AgentCore** tab + - See agents, sessions, and traces + +2. **CloudWatch Logs**: + - Location: `/aws/bedrock-agentcore/runtimes/-/runtime-logs` + - View structured OTEL logs + +3. **Transaction Search**: + - CloudWatch → Transaction Search + - Filter by service name or session ID + - View detailed execution graphs + +### Session Tracking +Pass `session_id` in your payload to correlate multiple invocations: +```bash +agentcore invoke --bearer-token $BEARER_TOKEN '{ + "user_input": "I need a quote", + "session_id": "customer-session-123", + "actor_id": "customer-456" +}' +``` + +## Memory Features + +The agent includes **AgentCore Memory** for conversation persistence and customer preferences: + +### Memory Strategy +- **User Preference Strategy**: Automatically learns customer preferences +- **Namespace**: `/insurance/customers/{actor_id}` - organized by customer +- **Auto-extraction**: Learns coverage needs, vehicle preferences, interaction patterns + +### How It Works +1. **Automatic Context**: Previous conversations are retrieved and added to prompts +2. **Preference Learning**: Customer preferences are extracted and stored automatically +3. **Cross-Session**: Memory persists across multiple sessions for the same customer + +### Using Memory +Simply include `actor_id` (customer identifier) in your payload: +```bash +agentcore invoke --bearer-token $BEARER_TOKEN '{ + "user_input": "What coverage did I ask about last time?", + "actor_id": "customer-456" +}' +``` + +### Memory Configuration +- **Auto-creation**: Memory resource created automatically on first run +- **Manual configuration**: Set `MEMORY_ID` in `.env` to use existing memory resource +- **Region**: Configure via `AWS_REGION` in `.env` (default: us-west-2) + +### Viewing Memory Data +```python +from bedrock_agentcore.memory import MemoryClient + +client = MemoryClient(region_name="us-west-2") +memories = client.retrieve_memories( + memory_id="your-memory-id", + namespace="/insurance/customers/customer-456", + query="What are the customer's preferences?" +) +``` ## Next Steps diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/agentcore_strands_insurance_agent.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/agentcore_strands_insurance_agent.py index 08a423f97..7b1aa490d 100755 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/agentcore_strands_insurance_agent.py +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/agentcore_strands_insurance_agent.py @@ -1,68 +1,222 @@ #!/usr/bin/env python3 """ -Auto Insurance Agent using Strands and MCP +Insurance Agent with AWS Bedrock AgentCore -This agent connects to the local insurance MCP server to provide -auto insurance quotes, customer information, and vehicle details. - -It can be used in two ways: -1. With a direct command-line input: python interactive_insurance_agent.py --user_input "your question" -2. As an AWS Bedrock Agent (when deployed to AgentCore) +This demonstrates how to build production-ready agents using AgentCore services: +- Runtime: Serverless deployment with auto-scaling +- Memory: Persistent conversation history +- Identity: Secure credential management +- Gateway: MCP tool integration +- Observability: Built-in tracing and monitoring """ -# Standard library imports import logging -from typing import Dict, List, Optional -import time -import json import os -from datetime import datetime - -# Import dotenv for loading environment variables +import uuid +from typing import Dict from dotenv import load_dotenv +# Strands Agent Framework from strands import Agent from strands.tools.mcp import MCPClient from mcp.client.streamable_http import streamablehttp_client -# ADDED: BEDROCK_AGENTCORE IMPORT -from bedrock_agentcore.runtime import BedrockAgentCoreApp +# AgentCore Services - Import the services you need +from bedrock_agentcore.runtime import BedrockAgentCoreApp # Serverless deployment +from bedrock_agentcore.memory import MemoryClient # Conversation persistence +from bedrock_agentcore.services.identity import IdentityClient # Secure auth + +# Observability - OpenTelemetry for distributed tracing +from opentelemetry import baggage, context -# Load environment variables from .env file load_dotenv() -# ADDED: BEDROCK_AGENTCORE APP CREATION +# ============================================================================ +# AGENTCORE RUNTIME - Initialize the serverless app +# ============================================================================ +# BedrockAgentCoreApp handles: +# - Lambda function deployment and scaling +# - Request/response handling +# - Integration with other AgentCore services +# - Automatic CloudWatch logging app = BedrockAgentCoreApp() -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - # logging.StreamHandler() - ] -) +# ============================================================================ +# CONFIGURATION - Environment variables +# ============================================================================ +AWS_REGION = os.getenv("AWS_REGION", "us-east-1") +MCP_SERVER_URL = os.getenv("MCP_SERVER_URL") # AgentCore Gateway endpoint +MODEL_NAME = os.getenv("MODEL_NAME", "us.anthropic.claude-3-7-sonnet-20250219-v1:0") + +logging.basicConfig(level=logging.INFO) logger = logging.getLogger("InsuranceAgent") -# MCP server URL and access token from environment variables -MCP_SERVER_URL = os.getenv("MCP_SERVER_URL") -access_token = os.getenv("MCP_ACCESS_TOKEN") +# ============================================================================ +# AGENTCORE IDENTITY - Secure credential management +# ============================================================================ +# Identity provides two authentication patterns: +# 1. INBOUND: Workload Identity - Authenticates your agent (who is calling) +# 2. OUTBOUND: API Key Provider - Stores credentials for external APIs +# +# Benefits: +# - Centralized credential storage (no hardcoded secrets) +# - Automatic credential rotation +# - Fine-grained access control +# - Audit logging -# Check if environment variables are set -if not MCP_SERVER_URL: - logger.error("MCP_SERVER_URL not found in environment variables.") - raise ValueError("MCP_SERVER_URL environment variable is required") +def setup_identity(): + """Initialize Identity service for authentication""" + try: + identity_client = IdentityClient(region_name=AWS_REGION) + workload_identity_arn = os.getenv("WORKLOAD_IDENTITY_ARN") + + if workload_identity_arn: + logger.info(f"✓ Identity configured: {workload_identity_arn}") + return identity_client, workload_identity_arn + else: + logger.warning("⚠ Identity not configured (optional)") + return None, None + except Exception as e: + logger.warning(f"⚠ Identity setup failed: {e}") + return None, None -if not access_token: - logger.warning("MCP_ACCESS_TOKEN not found in environment variables. Authentication might fail.") - # Don't set a default access token as it's sensitive and should be provided via environment +identity_client, workload_identity_arn = setup_identity() -# Create an MCP Client pointing to our MCP server -insurance_client = MCPClient(lambda: streamablehttp_client(MCP_SERVER_URL, headers={"Authorization": f"Bearer {access_token}"})) +# ============================================================================ +# AGENTCORE MEMORY - Persistent conversation storage +# ============================================================================ +# Memory stores conversation history and user preferences across sessions. +# +# Key features: +# - Event Memory: Stores conversation turns (user/assistant messages) +# - Semantic Memory: Retrieves relevant context based on query similarity +# - User Preferences: Tracks customer preferences over time +# - Namespaces: Organize memories by customer (/insurance/customers/{actorId}) +# +# Best practice: Set MEMORY_ID in .env to reuse existing memory +_memory_client = None +_memory_resource = None +_memory_initialized = False -# System prompt for the insurance agent -INSURANCE_SYSTEM_PROMPT = """ +def setup_memory(): + """Initialize Memory service (lazy initialization for performance)""" + global _memory_client, _memory_resource, _memory_initialized + + if _memory_initialized: + return _memory_client, _memory_resource + + _memory_initialized = True + + try: + memory_client = MemoryClient(region_name=AWS_REGION) + + # Best practice: Use MEMORY_ID from .env to avoid searching/creating + memory_id = os.getenv("MEMORY_ID") + if memory_id: + logger.info(f"✓ Using memory from MEMORY_ID: {memory_id}") + return memory_client, {"id": memory_id} + + # Fallback: Search for existing memory + logger.info("No MEMORY_ID set, searching for existing memory...") + try: + existing_memories = memory_client.list_memories() + + # Look for any memory with "InsuranceAgentMemory" in the name + for memory in existing_memories: + memory_name = memory.get('name', '') + if 'InsuranceAgentMemory' in memory_name: + memory_id = memory.get('id') + logger.info(f"✓ Found existing memory: {memory_name} ({memory_id})") + logger.info(f"💡 Add to .env to avoid search: MEMORY_ID=\"{memory_id}\"") + return memory_client, {"id": memory_id} + + logger.info("No existing InsuranceAgentMemory found") + except Exception as list_error: + logger.warning(f"⚠ Could not list memories: {list_error}") + + # Create new memory if none exists + logger.info("Creating new memory resource...") + try: + memory_resource = memory_client.create_memory_and_wait( + name="InsuranceAgentMemory", + description="Insurance agent conversation memory", + strategies=[{ + "userPreferenceMemoryStrategy": { # Tracks user preferences + "name": "CustomerPreferences", + "description": "Customer insurance preferences and history", + "namespaces": ["/insurance/customers/{actorId}"] # Per-customer storage + } + }] + ) + memory_id = memory_resource.get('id') + logger.info(f"✓ Created memory: {memory_id}") + logger.info(f"💡 Add to .env to avoid recreation: MEMORY_ID=\"{memory_id}\"") + _memory_client = memory_client + _memory_resource = memory_resource + return memory_client, memory_resource + except Exception as create_error: + if "already exists" in str(create_error): + # Race condition - another instance created it + logger.info("Memory created by another instance, searching...") + try: + existing_memories = memory_client.list_memories() + for memory in existing_memories: + if 'InsuranceAgentMemory' in memory.get('name', ''): + memory_id = memory.get('id') + logger.info(f"✓ Found memory: {memory_id}") + _memory_client = memory_client + _memory_resource = {"id": memory_id} + return memory_client, {"id": memory_id} + except: + pass + logger.warning(f"⚠ Could not create memory: {create_error}") + return None, None + + except Exception as e: + logger.warning(f"⚠ Memory setup failed: {e}") + return None, None + +# Memory will be initialized lazily on first use (not at module load time) + +# ============================================================================ +# AGENTCORE GATEWAY - MCP tool integration +# ============================================================================ +# Gateway exposes your APIs as MCP tools that agents can use. +# +# How it works: +# 1. Gateway reads your OpenAPI spec +# 2. Converts API endpoints to MCP tools +# 3. Handles OAuth authentication +# 4. Provides tools to your agent +# +# Benefits: +# - No custom tool code needed +# - Automatic API-to-tool conversion +# - Built-in authentication +# - Centralized API management + +def get_mcp_token(): + """Get OAuth token for Gateway authentication""" + token = os.getenv("MCP_ACCESS_TOKEN") + if not token: + logger.warning("⚠ MCP_ACCESS_TOKEN not set") + return token + +# Connect to Gateway to access insurance API tools +insurance_mcp_client = MCPClient( + lambda: streamablehttp_client( + MCP_SERVER_URL, + headers={"Authorization": f"Bearer {get_mcp_token()}"} + ) +) + +# ============================================================================ +# AGENT CONFIGURATION - System prompt and behavior +# ============================================================================ +# Define how your agent behaves and what it can do + +SYSTEM_PROMPT = """ You are an auto insurance assistant that helps customers understand their insurance options. Your goal is to provide helpful, accurate information about auto insurance products, @@ -82,134 +236,153 @@ Remember previous context from the conversation when responding. """ -def log_conversation(role: str, content: str, tool_calls: Optional[List] = None) -> None: - """Log each conversation turn with timestamp and optional tool calls""" - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - logger.info(f"[{timestamp}] {role}: {content[:100]}..." if len(content) > 100 else f"[{timestamp}] {role}: {content}") - - if tool_calls: - for call in tool_calls: - logger.info(f" Tool used: {call['name']} with args: {json.dumps(call['args'])}") +# ============================================================================ +# CORE AGENT LOGIC - Memory retrieval and agent execution +# ============================================================================ -def insurance_quote_agent(question: str): - """ - Creates a Strands agent that answers questions about auto insurance - using the MCP tools from our local MCP server. +def get_memory_context(actor_id: str, query: str) -> str: + """Retrieve relevant conversation history from Memory""" + memory_client, memory_resource = setup_memory() - Args: - question: The customer's question or request - history: Chat history for context + if not memory_client or not memory_resource: + return "" + + try: + memories = memory_client.retrieve_memories( + memory_id=memory_resource.get("id"), + namespace=f"/insurance/customers/{actor_id}", + query=query, + max_results=3 + ) - Returns: - The agent's response - """ - log_conversation("User", question) + if memories: + context = "\n".join([m.get("content", "") for m in memories]) + logger.info(f"✓ Retrieved {len(memories)} memories") + return f"\n\nPrevious context:\n{context}" + + except Exception as e: + logger.warning(f"⚠ Memory retrieval failed: {e}") - with insurance_client: - try: - # Get the list of available tools from the MCP server - tools = insurance_client.list_tools_sync() - logger.info(f"Connected to MCP server, found {len(tools)} tools") - - # Get model name from environment or use default - model_name = os.getenv("MODEL_NAME", "us.anthropic.claude-3-7-sonnet-20250219-v1:0") - - # Create an agent with our tools - agent = Agent( - model=model_name, - tools=tools, - system_prompt=INSURANCE_SYSTEM_PROMPT, - callback_handler=None - ) - - # Add context using previous conversation - prompt = question - - start_time = time.time() - # Process the question and return the response - response = agent(prompt) - end_time = time.time() - - logger.info(f"Request processed in {end_time - start_time:.2f} seconds") - - return response - except Exception as e: - logger.error(f"Error processing request: {str(e)}") - # Return a graceful error response - return {"message": {"content": f"I'm sorry, I encountered an error: {str(e)}. Please try again later."}} + return "" -def process_single_input(user_input: str, history: List[Dict[str, str]] = None): - """ - Process a single user input and return the response +def save_to_memory(actor_id: str, session_id: str, user_input: str, response: str): + """Save conversation turn to Memory for future context""" + memory_client, memory_resource = setup_memory() - Args: - user_input: The user's question or request - history: Optional chat history for context - - Returns: - The agent's response as a string - """ - if history is None: - history = [] - - logger.info(f"Processing single input: {user_input}") + if not memory_client or not memory_resource: + logger.warning(f"⚠ Memory not configured - skipping save") + return - # Get response from agent - response = insurance_quote_agent(user_input) + try: + logger.info(f"💾 Saving to memory: actor={actor_id}, session={session_id}") + memory_client.create_event( + memory_id=memory_resource.get("id"), + actor_id=actor_id, + session_id=session_id, + messages=[ + (user_input, "USER"), + (response, "ASSISTANT") + ] + ) + logger.info("✓ Saved to memory") + except Exception as e: + logger.warning(f"⚠ Memory save failed: {e}") + +def run_agent(user_input: str, actor_id: str, session_id: str) -> str: + """Execute the agent with Memory context and Gateway tools""" - # Format the response for display - if isinstance(response, dict): - if "content" in response: - return response["content"] - elif "message" in response and "content" in response["message"]: - return response["message"]["content"] + # 1. MEMORY: Get relevant conversation history + memory_context = get_memory_context(actor_id, user_input) + enhanced_prompt = SYSTEM_PROMPT + memory_context - # Default return the full response - return str(response) + # 2. GATEWAY: Connect and get available tools + with insurance_mcp_client: + tools = insurance_mcp_client.list_tools_sync() + logger.info(f"✓ Connected to MCP server ({len(tools)} tools)") + + # 3. Create agent with tools and context + agent = Agent( + model=MODEL_NAME, + tools=tools, + system_prompt=enhanced_prompt + ) + + # 4. Get agent response + response = agent(user_input) + + # Extract response text + if isinstance(response, dict): + response_text = response.get("content") or response.get("message", {}).get("content", str(response)) + else: + response_text = str(response) + + # 5. MEMORY: Save conversation for future context + save_to_memory(actor_id, session_id, user_input, response_text) + + return response_text + +# ============================================================================ +# AGENTCORE RUNTIME - Request handler +# ============================================================================ +# The @app.entrypoint decorator marks this as the Lambda handler. +# Runtime automatically: +# - Deploys as Lambda function +# - Handles request/response +# - Integrates with CloudWatch +# - Enables distributed tracing -# ADDED: BEDROCK_AGENTCORE - APP ENTRYPOINT DECLARATION @app.entrypoint -def main(payload): +def main(payload: Dict) -> str: """ - Main function to run the insurance agent + Main request handler - invoked by AgentCore Runtime Args: - payload: Input payload from AgentCore, which may contain the user's message + payload: {"user_input": str, "actor_id": str, "session_id": str} + + Returns: + Agent response string """ - logger.info("Starting Insurance Agent") - logger.info(f"Received payload: {payload}") - logger.info(f"Is payload string? {isinstance(payload, str)}") + logger.info("=" * 60) + logger.info("Insurance Agent Request") + logger.info("=" * 60) try: - # Extract the user input from the payload - logger.info(f"Input Payload: {payload}") - user_input = payload.get("user_input") + # Extract request parameters + user_input = payload.get("user_input", "") + actor_id = payload.get("actor_id", "anonymous") + session_id = payload.get("session_id", str(uuid.uuid4())) + + logger.info(f"User: {user_input}") + logger.info(f"Actor: {actor_id}") + logger.info(f"Session: {session_id}") - # Add explicit check - if "user_input" not in payload: - logger.error("No 'user_input' key found in payload, using default") + # IDENTITY: Verify authentication (if configured) + if workload_identity_arn: + logger.info(f"✓ Authenticated via: {workload_identity_arn}") - logger.info(f"Extracted user_input: {user_input}") - logger.info("\n🚀 Processing request...") + # OBSERVABILITY: Set session context for distributed tracing + ctx = baggage.set_baggage("session.id", session_id) + context.attach(ctx) - # Process the request - response = process_single_input(user_input) - logger.info(f"\n🤖 Assistant: {response}") + # Execute agent + response = run_agent(user_input, actor_id, session_id) + + logger.info(f"Response: {response[:100]}...") + logger.info("=" * 60) return response + except Exception as e: - error_msg = f"Error processing request: {str(e)}" + error_msg = f"Error: {str(e)}" logger.error(error_msg) - logger.info(f"\n❌ {error_msg}") - - # Format error response for AgentCore - return f"I'm sorry, I encountered an error: {str(e)}. Please try again later." - - finally: - logger.info("Insurance Agent request processed") + return f"I'm sorry, I encountered an error. Please try again later." + +# ============================================================================ +# AGENTCORE RUNTIME - Start the application +# ============================================================================ +# app.run() starts the Lambda handler +# When deployed: Handles Lambda events +# When local: Runs development server for testing if __name__ == "__main__": - # REMOVED: PREVIOUS CODE FOR LOCAL PROCESSING - # ADDED: BEDROCK_AGENTCORE - RUN APP app.run() - diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/identity_config.json b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/identity_config.json new file mode 100644 index 000000000..873bc578b --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/identity_config.json @@ -0,0 +1,18 @@ +{ + "workload_identity": { + "arn": "arn:aws:bedrock-agentcore:us-east-1:200937443282:workload-identity-directory/default/workload-identity/insurance-agent-workload", + "id": "insurance-agent-workload", + "name": "insurance-agent-workload" + }, + "oauth2_provider": { + "type": "cognito", + "client_id": "40rcaa68pv99q3f25nu73reac", + "token_endpoint": "https://agentcore-481a0ddf.auth.us-east-1.amazoncognito.com/oauth2/token", + "note": "Using Cognito OAuth2 - tokens managed via gateway_info.json" + }, + "api_key_provider": { + "name": "InsuranceAPIKeyProvider", + "arn": "arn:aws:bedrock-agentcore:us-east-1:200937443282:token-vault/default/apikeycredentialprovider/InsuranceAPIKeyProvider" + }, + "region": "us-east-1" +} \ No newline at end of file diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/requirements.txt b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/requirements.txt index bc14e910d..01340e52b 100644 --- a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/requirements.txt +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/requirements.txt @@ -1,10 +1,24 @@ -mcp>=0.1.0 -strands-agents>=0.1.8 +# Core dependencies with explicit versions to avoid conflicts +pydantic>=2.11.0,<3.0.0 +starlette>=0.46.2 + +# AgentCore and MCP +mcp>=1.16.0 +bedrock-agentcore>=0.1.0 +bedrock-agentcore-starter-toolkit>=0.1.0 + +# Strands Agent Framework +strands-agents[otel]>=0.1.8 strands-agents-tools>=0.1.6 + +# AWS SDK +boto3>=1.39.7 +botocore>=1.39.7 + +# Utilities typing-extensions>=4.0.0 python-dateutil>=2.8.2 python-dotenv>=1.0.0 -boto3>=1.39.7 -botocore>=1.39.7 -bedrock-agentcore>=0.1.0 -bedrock-agentcore-starter-toolkit>=0.1.0 \ No newline at end of file + +# Observability +aws-opentelemetry-distro \ No newline at end of file diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/PATH_FIX_SUMMARY.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/PATH_FIX_SUMMARY.md new file mode 100644 index 000000000..140c2bffe --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/PATH_FIX_SUMMARY.md @@ -0,0 +1,56 @@ +# Path Fix Summary + +## Issue +After moving scripts to the `scripts/` directory, the `setup_identity.sh` script was looking for files in the wrong locations. + +## Files Fixed + +### `scripts/setup_identity.sh` +Updated to handle being called from the parent directory: + +1. **Script location detection** + ```bash + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + PARENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" + ``` + +2. **Python script path** + - Old: `python identity_setup.py` + - New: `python "$SCRIPT_DIR/identity_setup.py"` + +3. **.env file path** + - Old: `source .env` + - New: `source "$PARENT_DIR/.env"` + +4. **identity_config.json path** + - Old: `identity_config.json` + - New: `"$PARENT_DIR/identity_config.json"` + +5. **All .env updates** + - Old: `.env` + - New: `"$PARENT_DIR/.env"` + +## How It Works Now + +``` +cloud_strands_insurance_agent/ +├── .env ← Loaded from here +├── identity_config.json ← Created here +└── scripts/ + ├── setup_identity.sh ← Called from parent dir + └── identity_setup.py ← Executed from here +``` + +## Usage + +From the `cloud_strands_insurance_agent` directory: +```bash +./scripts/setup_identity.sh +``` + +Or from the root directory via `deploy_all.sh`: +```bash +./deploy_all.sh +``` + +Both work correctly now! diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/README.md b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/README.md new file mode 100644 index 000000000..33c18e145 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/README.md @@ -0,0 +1,141 @@ +# Scripts Directory + +This directory contains utility scripts for setting up and managing the insurance agent. + +## Scripts + +### Identity Setup + +#### `setup_identity.sh` +Automated bash script for setting up AgentCore Identity infrastructure. + +**Usage:** +```bash +./scripts/setup_identity.sh +``` + +**What it does:** +- Validates environment variables +- Runs `identity_setup.py` to create identity resources +- Updates `.env` file with generated values +- Creates `identity_config.json` with configuration + +**Prerequisites:** +- `.env` file with required variables (AWS_REGION, GATEWAY_INFO_FILE) +- MCP Gateway already deployed +- AWS credentials configured + +#### `identity_setup.py` +Python script that creates AgentCore Identity resources. + +**Usage:** +```bash +python scripts/identity_setup.py +``` + +**What it creates:** +- Workload Identity for the agent (Phase 2: Inbound) +- API Key Credential Provider (Phase 1: Outbound) +- Stores API key securely in Identity service + +**Output:** +- `identity_config.json` - Configuration file with created resources +- Updates `.env` with WORKLOAD_IDENTITY_ARN and other values + +### Utility Scripts + +#### `cleanup_duplicate_memories.py` +Cleans up duplicate memory resources in AgentCore Memory service. + +**Usage:** +```bash +python scripts/cleanup_duplicate_memories.py +``` + +**What it does:** +- Lists all memory resources +- Identifies duplicates by name +- Optionally deletes duplicate memories +- Keeps the most recent version + +**When to use:** +- After multiple deployments that created duplicate memories +- To clean up test memory resources +- Before production deployment + +#### `fix_env_path.sh` +Fixes path issues in `.env` file. + +**Usage:** +```bash +./scripts/fix_env_path.sh +``` + +**What it does:** +- Corrects relative paths in `.env` file +- Updates GATEWAY_INFO_FILE path +- Ensures paths are relative to the agent directory + +## Running Scripts + +All scripts should be run from the `cloud_strands_insurance_agent` directory: + +```bash +# From the agent directory +cd cloud_strands_insurance_agent + +# Run identity setup +./scripts/setup_identity.sh + +# Or run Python scripts directly +python scripts/identity_setup.py +python scripts/cleanup_duplicate_memories.py +``` + +## Script Dependencies + +### Identity Setup Scripts +- Requires: `bedrock-agentcore` package +- Requires: `.env` file with AWS_REGION +- Requires: `gateway_info.json` from MCP server setup + +### Cleanup Scripts +- Requires: `bedrock-agentcore` package +- Requires: AWS credentials with appropriate permissions + +## Troubleshooting + +### "Permission denied" errors +Make scripts executable: +```bash +chmod +x scripts/*.sh +``` + +### "Module not found" errors +Ensure you're in a virtual environment with dependencies installed: +```bash +python -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +pip install -r requirements.txt +``` + +### "Gateway info file not found" +Deploy the MCP gateway first: +```bash +cd ../cloud_mcp_server +./setup.sh +``` + +## Integration with deploy_all.sh + +The main deployment script (`deploy_all.sh`) calls these scripts in order: +1. Insurance API deployment +2. MCP Gateway setup +3. **Identity setup** (`scripts/setup_identity.sh`) +4. Agent deployment + +## See Also + +- [IDENTITY_README.md](../md_files/IDENTITY_README.md) - Complete identity documentation +- [IDENTITY_QUICK_START.md](../md_files/IDENTITY_QUICK_START.md) - Quick start guide +- [README.md](../README.md) - Main project README diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/cleanup_duplicate_memories.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/cleanup_duplicate_memories.py new file mode 100644 index 000000000..dfe9ebb90 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/cleanup_duplicate_memories.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +""" +Cleanup script to delete duplicate InsuranceAgentMemory resources + +This script will: +1. List all memories +2. Find all "InsuranceAgentMemory" instances +3. Keep the oldest one +4. Delete the duplicates +""" + +import boto3 +import sys +from datetime import datetime + +def cleanup_duplicate_memories(region='us-east-1', dry_run=True): + """ + Clean up duplicate memory resources + + Args: + region: AWS region + dry_run: If True, only show what would be deleted without actually deleting + """ + client = boto3.client('bedrock-agentcore-control', region_name=region) + + print(f"Listing memories in region: {region}") + print("=" * 60) + + try: + response = client.list_memories() + memories = response.get('memories', []) + + print(f"Found {len(memories)} total memories") + + # Filter for InsuranceAgentMemory + insurance_memories = [m for m in memories if m.get('name') == 'InsuranceAgentMemory'] + + if len(insurance_memories) <= 1: + print(f"\n✓ Only {len(insurance_memories)} InsuranceAgentMemory found - no cleanup needed") + return + + print(f"\n⚠️ Found {len(insurance_memories)} duplicate InsuranceAgentMemory resources") + print("\nMemories:") + for i, memory in enumerate(insurance_memories, 1): + memory_id = memory.get('id', 'unknown') + created = memory.get('createdAt', 'unknown') + status = memory.get('status', 'unknown') + print(f" {i}. ID: {memory_id}") + print(f" Created: {created}") + print(f" Status: {status}") + + # Sort by creation time (oldest first) + insurance_memories.sort(key=lambda x: x.get('createdAt', '')) + + # Keep the first (oldest) one + keep_memory = insurance_memories[0] + delete_memories = insurance_memories[1:] + + print(f"\n📌 Will KEEP: {keep_memory.get('id')} (oldest)") + print(f"🗑️ Will DELETE: {len(delete_memories)} duplicate(s)") + + if dry_run: + print("\n" + "=" * 60) + print("DRY RUN MODE - No memories will be deleted") + print("To actually delete, run: python cleanup_duplicate_memories.py --delete") + print("=" * 60) + print(f"\nTo use the kept memory, add this to your .env file:") + print(f"MEMORY_ID={keep_memory.get('id')}") + return keep_memory.get('id') + + # Actually delete the duplicates + print("\n" + "=" * 60) + print("DELETING DUPLICATES...") + print("=" * 60) + + for memory in delete_memories: + memory_id = memory.get('id') + try: + print(f"\nDeleting: {memory_id}") + client.delete_memory(id=memory_id) + print(f" ✓ Deleted successfully") + except Exception as e: + print(f" ✗ Failed to delete: {str(e)}") + + print("\n" + "=" * 60) + print("CLEANUP COMPLETE") + print("=" * 60) + print(f"\nKept memory ID: {keep_memory.get('id')}") + print(f"\nAdd this to your .env file to prevent future duplicates:") + print(f"MEMORY_ID={keep_memory.get('id')}") + + return keep_memory.get('id') + + except Exception as e: + print(f"\n✗ Error: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='Cleanup duplicate InsuranceAgentMemory resources') + parser.add_argument('--region', default='us-east-1', help='AWS region (default: us-east-1)') + parser.add_argument('--delete', action='store_true', help='Actually delete duplicates (default is dry-run)') + + args = parser.parse_args() + + if not args.delete: + print("\n⚠️ Running in DRY RUN mode - no memories will be deleted") + print("Use --delete flag to actually delete duplicates\n") + + memory_id = cleanup_duplicate_memories(region=args.region, dry_run=not args.delete) + + if memory_id and args.delete: + print(f"\n✓ Cleanup complete! Use MEMORY_ID={memory_id} in your .env file") diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/create_memory.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/create_memory.py new file mode 100644 index 000000000..38fc47ab1 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/create_memory.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Create AgentCore Memory resource for the insurance agent + +Run this once to create the memory, then add the ID to .env +""" + +import os +from dotenv import load_dotenv +from bedrock_agentcore.memory import MemoryClient + +# Load environment +load_dotenv() + +AWS_REGION = os.getenv("AWS_REGION", "us-east-1") + +def create_memory(): + """Create a new memory resource""" + try: + client = MemoryClient(region_name=AWS_REGION) + + print(f"\n{'='*60}") + print(f"Creating AgentCore Memory in {AWS_REGION}") + print(f"{'='*60}\n") + + print("Creating InsuranceAgentMemory...") + memory_resource = client.create_memory_and_wait( + name="InsuranceAgentMemory", + description="Insurance agent conversation memory", + strategies=[{ + "userPreferenceMemoryStrategy": { + "name": "CustomerPreferences", + "description": "Customer insurance preferences and history", + "namespaces": ["/insurance/customers/{actorId}"] + } + }] + ) + + memory_id = memory_resource.get('id') + print(f"✓ Memory created successfully!") + print(f"\nMemory ID: {memory_id}") + print(f"\nAdd this to your .env file:") + print(f'MEMORY_ID="{memory_id}"') + print() + + # Optionally update .env file + env_file = "../.env" if os.path.exists("../.env") else ".env" + if os.path.exists(env_file): + response = input(f"\nUpdate {env_file} automatically? (y/n): ") + if response.lower() == 'y': + with open(env_file, 'a') as f: + f.write(f'\n# AgentCore Memory (auto-added)\nMEMORY_ID="{memory_id}"\n') + print(f"✓ Updated {env_file}") + + except Exception as e: + print(f"Error creating memory: {e}") + if "already exists" in str(e): + print("\nMemory already exists! Use list_memories.py to find it.") + +if __name__ == "__main__": + create_memory() diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/fix_env_path.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/fix_env_path.sh new file mode 100755 index 000000000..ee70d8e0a --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/fix_env_path.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +echo "Fixing GATEWAY_INFO_FILE path in .env..." + +if [ -f .env ]; then + # Update the path + sed -i.bak 's|GATEWAY_INFO_FILE=.*|GATEWAY_INFO_FILE="../cloud_mcp_server/gateway_info.json"|g' .env + echo "✓ Updated .env file" + echo "" + echo "Old path: ../cloud_mcp_server/1_pre_req_setup/gateway_info.json" + echo "New path: ../cloud_mcp_server/gateway_info.json" +else + echo "❌ .env file not found" + echo "Please create it first: cp .env_example .env" +fi diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/identity_setup.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/identity_setup.py new file mode 100644 index 000000000..e3efe3abe --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/identity_setup.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +""" +AgentCore Identity Setup for Insurance Agent + +This script sets up AgentCore Identity components: +1. Workload Identity for the insurance agent (Inbound) +2. OAuth2 Credential Provider for MCP Gateway access (Outbound) +3. API Key Credential Provider for Insurance API (Outbound) + +Run this script once to set up identity infrastructure. +""" + +import os +import json +import logging +from dotenv import load_dotenv +from bedrock_agentcore.services.identity import IdentityClient + +# Load environment variables +load_dotenv() + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger("IdentitySetup") + +def get_account_id(): + """Get AWS account ID""" + import boto3 + sts = boto3.client('sts') + return sts.get_caller_identity()['Account'] + +def setup_identity_infrastructure(): + """Set up all identity components for the insurance agent""" + + # Get AWS region from environment + aws_region = os.getenv("AWS_REGION", "us-east-1") + logger.info(f"Setting up Identity infrastructure in region: {aws_region}") + + # Initialize Identity client + identity_client = IdentityClient(aws_region) + + # ========================================================================= + # PHASE 2: INBOUND IDENTITY - Create Workload Identity for the agent + # ========================================================================= + logger.info("\n=== Phase 2: Creating Workload Identity ===") + + try: + workload_identity = identity_client.create_workload_identity( + name="insurance-agent-workload" + ) + + logger.info(f"✓ Workload Identity created successfully") + logger.info(f" ARN: {workload_identity['workloadIdentityArn']}") + logger.info(f" Name: {workload_identity['name']}") + + workload_identity_arn = workload_identity['workloadIdentityArn'] + # Use the name as the ID (the API doesn't return a separate ID field) + workload_identity_id = workload_identity['name'] + + except KeyError as e: + logger.error(f"Missing expected field in workload identity response: {str(e)}") + logger.error(f"Available fields: {list(workload_identity.keys())}") + raise + except Exception as e: + error_msg = str(e) + # Check if identity already exists + if "already exists" in error_msg: + logger.warning(f"Workload identity 'insurance-agent-workload' already exists") + logger.info("Retrieving existing workload identity...") + + # Get the existing identity using boto3 directly + try: + import boto3 + client = boto3.client('bedrock-agentcore-control', region_name=aws_region) + response = client.get_workload_identity(name='insurance-agent-workload') + + workload_identity_arn = response['workloadIdentityArn'] + workload_identity_id = response['name'] + + logger.info(f"✓ Using existing Workload Identity") + logger.info(f" ARN: {workload_identity_arn}") + logger.info(f" Name: {workload_identity_id}") + except Exception as get_error: + logger.error(f"Failed to retrieve existing workload identity: {str(get_error)}") + logger.info("\nTo delete the existing identity, run:") + logger.info(f" python -c \"import boto3; boto3.client('bedrock-agentcore-control', region_name='{aws_region}').delete_workload_identity(name='insurance-agent-workload'); print('✓ Deleted')\"") + raise + else: + logger.error(f"Failed to create workload identity: {error_msg}") + raise + + # ========================================================================= + # PHASE 1: OUTBOUND IDENTITY - Create OAuth2 Credential Provider + # ========================================================================= + logger.info("\n=== Phase 1: Creating OAuth2 Credential Provider for MCP Gateway ===") + + # Load gateway info to get OAuth2 credentials + gateway_info_file = os.getenv("GATEWAY_INFO_FILE", "../cloud_mcp_server/gateway_info.json") + + try: + with open(gateway_info_file, 'r') as f: + gateway_info = json.load(f) + + client_id = gateway_info['auth']['client_id'] + client_secret = gateway_info['auth']['client_secret'] + token_endpoint = gateway_info['auth']['token_endpoint'] + + logger.info(f"Loaded gateway OAuth2 configuration from {gateway_info_file}") + + except FileNotFoundError: + logger.error(f"Gateway info file not found: {gateway_info_file}") + logger.error("Please run the MCP gateway setup first") + raise + except KeyError as e: + logger.error(f"Missing required field in gateway info: {e}") + raise + + # Note: AgentCore Identity currently supports specific OAuth2 providers + # For custom OAuth2 (like Cognito), we'll document the pattern but may need + # to use the token directly until custom OAuth2 provider support is added + + logger.info("Note: Using Cognito OAuth2 - storing configuration for reference") + logger.info(f" Token Endpoint: {token_endpoint}") + logger.info(f" Client ID: {client_id}") + + # ========================================================================= + # PHASE 1: OUTBOUND IDENTITY - Create API Key Credential Provider + # ========================================================================= + logger.info("\n=== Phase 1: Creating API Key Credential Provider for Insurance API ===") + + # Get API key from environment or gateway info + api_key = os.getenv("API_KEY") + if not api_key and 'api' in gateway_info and 'credentials' in gateway_info['api']: + # Try to extract from gateway info if available + logger.info("API key not in environment, will be configured separately") + + if api_key: + try: + api_key_provider = identity_client.create_api_key_credential_provider(req={ + "name": "InsuranceAPIKeyProvider", + "apiKey": api_key + }) + + logger.info(f"✓ API Key Credential Provider created successfully") + logger.info(f" Provider ARN: {api_key_provider['credentialProviderArn']}") + logger.info(f" Provider Name: {api_key_provider['name']}") + + api_key_provider_name = api_key_provider['name'] + api_key_provider_arn = api_key_provider['credentialProviderArn'] + + except Exception as e: + error_msg = str(e) + # Check if provider already exists + if "already exists" in error_msg: + logger.warning(f"API Key Provider 'InsuranceAPIKeyProvider' already exists") + logger.info("Using existing API Key Provider") + + # Use the existing provider + api_key_provider_name = "InsuranceAPIKeyProvider" + # Construct the ARN (we don't have a get method, so we construct it) + api_key_provider_arn = f"arn:aws:bedrock-agentcore:{aws_region}:{get_account_id()}:credential-provider/InsuranceAPIKeyProvider" + + logger.info(f"✓ Using existing API Key Credential Provider") + logger.info(f" Provider Name: {api_key_provider_name}") + else: + logger.error(f"Failed to create API key provider: {error_msg}") + raise + else: + logger.warning("API_KEY not found in environment. Skipping API key provider creation.") + logger.warning("Set API_KEY in .env file and re-run to create the provider.") + api_key_provider_name = None + api_key_provider_arn = None + + # ========================================================================= + # Save Identity Configuration + # ========================================================================= + logger.info("\n=== Saving Identity Configuration ===") + + identity_config = { + "workload_identity": { + "arn": workload_identity_arn, + "id": workload_identity_id, + "name": "insurance-agent-workload" + }, + "oauth2_provider": { + "type": "cognito", + "client_id": client_id, + "token_endpoint": token_endpoint, + "note": "Using Cognito OAuth2 - tokens managed via gateway_info.json" + }, + "api_key_provider": { + "name": api_key_provider_name, + "arn": api_key_provider_arn + } if api_key else None, + "region": aws_region + } + + # Save to file + identity_config_file = "identity_config.json" + with open(identity_config_file, 'w') as f: + json.dump(identity_config, f, indent=2) + + logger.info(f"✓ Identity configuration saved to {identity_config_file}") + + # ========================================================================= + # Update .env file with identity information + # ========================================================================= + logger.info("\n=== Updating .env file ===") + + env_additions = f""" +# ============================================================================= +# AgentCore Identity Configuration (Auto-generated) +# ============================================================================= +# WORKLOAD_IDENTITY_ARN: ARN of the workload identity for this agent +WORKLOAD_IDENTITY_ARN={workload_identity_arn} + +# WORKLOAD_IDENTITY_ID: ID of the workload identity +WORKLOAD_IDENTITY_ID={workload_identity_id} +""" + + if api_key_provider_name: + env_additions += f""" +# API_KEY_PROVIDER_NAME: Name of the API key credential provider +API_KEY_PROVIDER_NAME={api_key_provider_name} +""" + + logger.info("Add the following to your .env file:") + logger.info(env_additions) + + # ========================================================================= + # Summary + # ========================================================================= + logger.info("\n" + "="*80) + logger.info("IDENTITY SETUP COMPLETE") + logger.info("="*80) + logger.info("\nPhase 1 (Outbound Identity):") + logger.info(f" ✓ OAuth2 configuration documented (using Cognito)") + if api_key_provider_name: + logger.info(f" ✓ API Key Provider created: {api_key_provider_name}") + else: + logger.info(f" ⚠ API Key Provider not created (set API_KEY in .env)") + + logger.info("\nPhase 2 (Inbound Identity):") + logger.info(f" ✓ Workload Identity created: {workload_identity_arn}") + + logger.info("\nNext Steps:") + logger.info("1. Update your .env file with the values shown above") + logger.info("2. The agent code will automatically use these identity components") + logger.info("3. Test the agent with: agentcore invoke --bearer-token $BEARER_TOKEN '{\"user_input\": \"test\"}'") + + return identity_config + +if __name__ == "__main__": + try: + config = setup_identity_infrastructure() + print("\n✓ Identity setup completed successfully!") + except Exception as e: + logger.error(f"\n✗ Identity setup failed: {str(e)}") + exit(1) diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/list_memories.py b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/list_memories.py new file mode 100644 index 000000000..1e9ec8970 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/list_memories.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +List and manage AgentCore Memory resources + +This script helps you find and manage memory resources. +""" + +import os +from dotenv import load_dotenv +from bedrock_agentcore.memory import MemoryClient + +# Load environment +load_dotenv() + +AWS_REGION = os.getenv("AWS_REGION", "us-east-1") + +def list_memories(): + """List all memory resources""" + try: + client = MemoryClient(region_name=AWS_REGION) + memories = client.list_memories() + + print(f"\n{'='*60}") + print(f"AgentCore Memory Resources in {AWS_REGION}") + print(f"{'='*60}\n") + + if not memories: + print("No memory resources found.") + return + + for i, memory in enumerate(memories, 1): + print(f"{i}. Name: {memory.get('name')}") + print(f" ID: {memory.get('id')}") + print(f" Description: {memory.get('description', 'N/A')}") + print(f" Created: {memory.get('createdAt', 'N/A')}") + print() + + print(f"Total: {len(memories)} memory resource(s)\n") + + # Find InsuranceAgentMemory + insurance_memory = None + for memory in memories: + if memory.get('name') == 'InsuranceAgentMemory': + insurance_memory = memory + break + + # Find any InsuranceAgentMemory (including ones with suffixes) + insurance_memories = [] + for memory in memories: + if 'InsuranceAgentMemory' in memory.get('name', ''): + insurance_memories.append(memory) + + if insurance_memories: + print(f"✓ Found {len(insurance_memories)} InsuranceAgentMemory resource(s):") + for mem in insurance_memories: + print(f" - {mem.get('name')}: {mem.get('id')}") + + # Use the first one + selected = insurance_memories[0] + print(f"\n💡 Recommended: Use the first one") + print(f"\nAdd this to your .env file:") + print(f'MEMORY_ID="{selected.get("id")}"') + + if len(insurance_memories) > 1: + print(f"\n⚠ Warning: {len(insurance_memories)} duplicate memories found!") + print(f" Consider deleting the extras to avoid confusion.") + print(f" Keep: {selected.get('name')} ({selected.get('id')})") + else: + print("⚠ No InsuranceAgentMemory found") + print("\nAvailable memory names:") + for memory in memories: + print(f" - {memory.get('name')}") + + print() + + except Exception as e: + print(f"Error listing memories: {e}") + +if __name__ == "__main__": + list_memories() diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/setup_identity.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/setup_identity.sh new file mode 100755 index 000000000..67e5ceba3 --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/cloud_strands_insurance_agent/scripts/setup_identity.sh @@ -0,0 +1,157 @@ +#!/bin/bash +set -e + +# Setup script for AgentCore Identity integration +# This script automates the identity setup process + +echo "==========================================" +echo "AgentCore Identity Setup" +echo "==========================================" +echo "" + +# Get the parent directory (where .env should be) +PARENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" + +# Check if .env file exists in parent directory +if [ ! -f "$PARENT_DIR/.env" ]; then + echo "❌ .env file not found in $PARENT_DIR" + echo "Please create .env file from .env_example:" + echo " cp .env_example .env" + echo " nano .env # Edit with your values" + exit 1 +fi + +# Load environment variables +source "$PARENT_DIR/.env" + +# Check required variables +if [ -z "$AWS_REGION" ]; then + echo "❌ AWS_REGION not set in .env file" + exit 1 +fi + +if [ -z "$GATEWAY_INFO_FILE" ]; then + echo "⚠️ GATEWAY_INFO_FILE not set, using default: ../cloud_mcp_server/gateway_info.json" + export GATEWAY_INFO_FILE="../cloud_mcp_server/gateway_info.json" +fi + +# Check if gateway_info.json exists +if [ ! -f "$GATEWAY_INFO_FILE" ]; then + echo "❌ Gateway info file not found: $GATEWAY_INFO_FILE" + echo "Please run the MCP gateway setup first:" + echo " cd ../cloud_mcp_server" + echo " python agentcore_gateway_setup_openapi.py" + exit 1 +fi + +echo "✓ Environment configuration validated" +echo "" + +# Run the identity setup script +echo "Running identity setup..." +echo "" + +# Get the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Run identity_setup.py from the scripts directory +python "$SCRIPT_DIR/identity_setup.py" + +if [ $? -ne 0 ]; then + echo "" + echo "❌ Identity setup failed" + exit 1 +fi + +echo "" +echo "==========================================" +echo "Identity Setup Complete!" +echo "==========================================" +echo "" + +# Check if identity_config.json was created +if [ -f "$PARENT_DIR/identity_config.json" ]; then + echo "✓ Identity configuration saved to identity_config.json" + echo "" + + # Extract values from identity_config.json + WORKLOAD_IDENTITY_ARN=$(jq -r '.workload_identity.arn' "$PARENT_DIR/identity_config.json") + WORKLOAD_IDENTITY_ID=$(jq -r '.workload_identity.id' "$PARENT_DIR/identity_config.json") + API_KEY_PROVIDER_NAME=$(jq -r '.api_key_provider.name // empty' "$PARENT_DIR/identity_config.json") + + echo "Generated Identity Configuration:" + echo " Workload Identity ARN: $WORKLOAD_IDENTITY_ARN" + echo " Workload Identity ID: $WORKLOAD_IDENTITY_ID" + if [ ! -z "$API_KEY_PROVIDER_NAME" ] && [ "$API_KEY_PROVIDER_NAME" != "null" ]; then + echo " API Key Provider: $API_KEY_PROVIDER_NAME" + fi + echo "" + + # Ask if user wants to update .env file automatically + read -p "Do you want to automatically update your .env file? (y/n) " -n 1 -r + echo "" + + if [[ $REPLY =~ ^[Yy]$ ]]; then + # Check if values already exist in .env + if grep -q "WORKLOAD_IDENTITY_ARN=" "$PARENT_DIR/.env"; then + echo "Updating existing WORKLOAD_IDENTITY_ARN in .env..." + sed -i.bak "s|WORKLOAD_IDENTITY_ARN=.*|WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN|" "$PARENT_DIR/.env" + else + echo "Adding WORKLOAD_IDENTITY_ARN to .env..." + echo "" >> "$PARENT_DIR/.env" + echo "# AgentCore Identity Configuration (Auto-generated)" >> "$PARENT_DIR/.env" + echo "WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN" >> "$PARENT_DIR/.env" + fi + + if grep -q "WORKLOAD_IDENTITY_ID=" "$PARENT_DIR/.env"; then + sed -i.bak "s|WORKLOAD_IDENTITY_ID=.*|WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID|" "$PARENT_DIR/.env" + else + echo "WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID" >> "$PARENT_DIR/.env" + fi + + if [ ! -z "$API_KEY_PROVIDER_NAME" ] && [ "$API_KEY_PROVIDER_NAME" != "null" ]; then + if grep -q "API_KEY_PROVIDER_NAME=" "$PARENT_DIR/.env"; then + sed -i.bak "s|API_KEY_PROVIDER_NAME=.*|API_KEY_PROVIDER_NAME=$API_KEY_PROVIDER_NAME|" "$PARENT_DIR/.env" + else + echo "API_KEY_PROVIDER_NAME=$API_KEY_PROVIDER_NAME" >> "$PARENT_DIR/.env" + fi + fi + + echo "✓ .env file updated successfully" + echo "" + else + echo "" + echo "Please manually add these values to your .env file:" + echo "" + echo "WORKLOAD_IDENTITY_ARN=$WORKLOAD_IDENTITY_ARN" + echo "WORKLOAD_IDENTITY_ID=$WORKLOAD_IDENTITY_ID" + if [ ! -z "$API_KEY_PROVIDER_NAME" ] && [ "$API_KEY_PROVIDER_NAME" != "null" ]; then + echo "API_KEY_PROVIDER_NAME=$API_KEY_PROVIDER_NAME" + fi + echo "" + fi +fi + +echo "==========================================" +echo "Next Steps:" +echo "==========================================" +echo "" +echo "1. Verify your .env file has all required values" +echo "2. Deploy the agent with identity configuration:" +echo "" +echo " agentcore launch \\" +echo " -env MCP_SERVER_URL=\$MCP_SERVER_URL \\" +echo " -env MCP_ACCESS_TOKEN=\$MCP_ACCESS_TOKEN \\" +echo " -env MODEL_NAME=\$MODEL_NAME \\" +echo " -env AWS_REGION=\$AWS_REGION \\" +echo " -env WORKLOAD_IDENTITY_ARN=\$WORKLOAD_IDENTITY_ARN \\" +echo " -env WORKLOAD_IDENTITY_ID=\$WORKLOAD_IDENTITY_ID" +if [ ! -z "$API_KEY_PROVIDER_NAME" ] && [ "$API_KEY_PROVIDER_NAME" != "null" ]; then + echo " -env API_KEY_PROVIDER_NAME=\$API_KEY_PROVIDER_NAME" +fi +echo "" +echo "3. Test the agent:" +echo " agentcore invoke --bearer-token \$BEARER_TOKEN '{\"user_input\": \"test\"}'" +echo "" +echo "For more information, see IDENTITY_INTEGRATION.md" +echo "" diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/deploy_all.sh b/02-use-cases/local-prototype-to-agentcore/agentcore_app/deploy_all.sh new file mode 100755 index 000000000..bb00bff0a --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/deploy_all.sh @@ -0,0 +1,249 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}==========================================" +echo "AgentCore Insurance App - Full Deployment" +echo -e "==========================================${NC}\n" + +# Check prerequisites +echo -e "${YELLOW}Checking prerequisites...${NC}" + +# Check AWS CLI +if ! command -v aws &> /dev/null; then + echo -e "${RED}✗ AWS CLI not found. Please install it first.${NC}" + exit 1 +fi +echo -e "${GREEN}✓ AWS CLI found${NC}" + +# Check Python +if ! command -v python3 &> /dev/null; then + echo -e "${RED}✗ Python 3 not found. Please install it first.${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Python 3 found${NC}" + +# Check jq +if ! command -v jq &> /dev/null; then + echo -e "${RED}✗ jq not found. Please install it first.${NC}" + exit 1 +fi +echo -e "${GREEN}✓ jq found${NC}" + +# Check Docker +if ! command -v docker &> /dev/null; then + echo -e "${YELLOW}⚠ Docker not found. You'll need it for local testing.${NC}" +else + echo -e "${GREEN}✓ Docker found${NC}" +fi + +echo "" + +# Step 1: Deploy Insurance API +echo -e "${BLUE}==========================================" +echo "Step 1: Deploying Insurance API" +echo -e "==========================================${NC}\n" + +cd cloud_insurance_api/deployment +chmod +x ./deploy.sh +./deploy.sh + +if [ $? -ne 0 ]; then + echo -e "${RED}✗ Insurance API deployment failed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Insurance API deployed successfully${NC}\n" + +# Step 2: Setup MCP Gateway +echo -e "${BLUE}==========================================" +echo "Step 2: Setting up MCP Gateway" +echo -e "==========================================${NC}\n" + +cd ../../cloud_mcp_server + +# Check if .env exists +if [ ! -f .env ]; then + echo -e "${YELLOW}Creating .env file from example...${NC}" + cp .env_example .env + + # Get API Gateway URL from CloudFormation + STACK_NAME="insurance-api-dev" + API_URL=$(aws cloudformation describe-stacks \ + --stack-name $STACK_NAME \ + --query 'Stacks[0].Outputs[?OutputKey==`ApiUrl`].OutputValue' \ + --output text 2>/dev/null || echo "") + + if [ ! -z "$API_URL" ]; then + sed -i.bak "s|API_GATEWAY_URL=.*|API_GATEWAY_URL=$API_URL|" .env + echo -e "${GREEN}✓ Auto-configured API Gateway URL${NC}" + fi +fi + +# Run automated setup +chmod +x ./setup.sh +./setup.sh + +if [ $? -ne 0 ]; then + echo -e "${RED}✗ MCP Gateway setup failed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ MCP Gateway setup successfully${NC}\n" + +# Step 3: Setup AgentCore Identity +echo -e "${BLUE}==========================================" +echo "Step 3: Setting up AgentCore Identity" +echo -e "==========================================${NC}\n" + +cd ../cloud_strands_insurance_agent + +# Create .env if it doesn't exist +if [ ! -f .env ]; then + echo -e "${YELLOW}Creating .env file from example...${NC}" + cp .env_example .env + + # Set demo API key + echo 'API_KEY="demo-insurance-api-key-12345"' >> .env +fi + +# Always update MCP settings from gateway_info.json +if [ -f ../cloud_mcp_server/gateway_info.json ]; then + echo -e "${YELLOW}Updating MCP settings from gateway_info.json...${NC}" + MCP_URL=$(jq -r '.gateway.mcp_url' ../cloud_mcp_server/gateway_info.json) + MCP_TOKEN=$(jq -r '.auth.access_token' ../cloud_mcp_server/gateway_info.json) + + # Update .env file + sed -i.bak "s|MCP_SERVER_URL=.*|MCP_SERVER_URL=\"$MCP_URL\"|" .env + sed -i.bak "s|MCP_ACCESS_TOKEN=.*|MCP_ACCESS_TOKEN=\"$MCP_TOKEN\"|" .env + + echo -e "${GREEN}✓ Updated MCP_SERVER_URL: $MCP_URL${NC}" + echo -e "${GREEN}✓ Updated MCP_ACCESS_TOKEN (expires in 1 hour)${NC}" +fi + +# Run identity setup +chmod +x ./scripts/setup_identity.sh +./scripts/setup_identity.sh + +if [ $? -ne 0 ]; then + echo -e "${RED}✗ Identity setup failed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Identity setup completed${NC}\n" + +# Step 4: Deploy Agent +echo -e "${BLUE}==========================================" +echo "Step 4: Deploying Strands Agent" +echo -e "==========================================${NC}\n" + +# Setup IAM role +echo -e "${YELLOW}Setting up IAM role...${NC}" +cd 1_pre_req_setup/iam_roles_setup +chmod +x ./setup_role.sh +./setup_role.sh + +# Setup Cognito +echo -e "${YELLOW}Setting up Cognito authentication...${NC}" +cd ../cognito_auth +chmod +x ./setup_cognito.sh +./setup_cognito.sh + +cd ../.. + +# Get role ARN +ROLE_ARN=$(aws iam get-role --role-name BedrockAgentCoreExecutionRole --query 'Role.Arn' --output text 2>/dev/null) + +if [ -z "$ROLE_ARN" ]; then + echo -e "${RED}✗ Could not get IAM role ARN${NC}" + exit 1 +fi + +# Configure agent +echo -e "${YELLOW}Configuring agent...${NC}" +agentcore configure -e "agentcore_strands_insurance_agent.py" \ + --name insurance_agent_strands \ + -er $ROLE_ARN + +# Load environment +source .env + +# Update .bedrock_agentcore.yaml with latest MCP settings +if [ -f .bedrock_agentcore.yaml ]; then + echo -e "${YELLOW}Updating .bedrock_agentcore.yaml with latest MCP settings...${NC}" + + # Check if environment section exists + if grep -q "environment:" .bedrock_agentcore.yaml; then + # Update existing environment section + sed -i.bak "s|MCP_SERVER_URL:.*|MCP_SERVER_URL: \"$MCP_SERVER_URL\"|" .bedrock_agentcore.yaml + sed -i.bak "s|MCP_ACCESS_TOKEN:.*|MCP_ACCESS_TOKEN: \"$MCP_ACCESS_TOKEN\"|" .bedrock_agentcore.yaml + echo -e "${GREEN}✓ Updated environment section in .bedrock_agentcore.yaml${NC}" + else + echo -e "${YELLOW}⚠ No environment section found in .bedrock_agentcore.yaml${NC}" + echo -e "${YELLOW} Using --env flags for deployment${NC}" + fi +fi + +# Deploy agent +echo -e "${YELLOW}Deploying agent to AWS...${NC}" +agentcore launch \ + -env MCP_SERVER_URL="$MCP_SERVER_URL" \ + -env MCP_ACCESS_TOKEN="$MCP_ACCESS_TOKEN" \ + -env MODEL_NAME="$MODEL_NAME" \ + -env AWS_REGION="$AWS_REGION" \ + -env WORKLOAD_IDENTITY_ARN="$WORKLOAD_IDENTITY_ARN" \ + -env WORKLOAD_IDENTITY_ID="$WORKLOAD_IDENTITY_ID" \ + -env API_KEY_PROVIDER_NAME="$API_KEY_PROVIDER_NAME" + +if [ $? -ne 0 ]; then + echo -e "${RED}✗ Agent deployment failed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Agent deployed successfully${NC}\n" + +# Step 5: Test deployment +echo -e "${BLUE}==========================================" +echo "Step 5: Testing Deployment" +echo -e "==========================================${NC}\n" + +# Refresh Cognito token +cd 1_pre_req_setup/cognito_auth +./refresh_token.sh +export BEARER_TOKEN=$(jq -r '.bearer_token' cognito_config.json) +cd ../.. + +# Test invocation +echo -e "${YELLOW}Testing agent invocation...${NC}" +agentcore invoke --bearer-token $BEARER_TOKEN \ + '{"user_input": "Can you help me get a quote for auto insurance?", "actor_id": "test-user"}' + +if [ $? -eq 0 ]; then + echo -e "\n${GREEN}✓ Agent invocation successful!${NC}" +else + echo -e "\n${YELLOW}⚠ Agent invocation had issues. Check logs for details.${NC}" +fi + +# Final summary +echo -e "\n${BLUE}==========================================" +echo "Deployment Complete!" +echo -e "==========================================${NC}\n" + +echo -e "${GREEN}✓ Insurance API deployed${NC}" +echo -e "${GREEN}✓ MCP Gateway configured${NC}" +echo -e "${GREEN}✓ AgentCore Identity setup${NC}" +echo -e "${GREEN}✓ Strands Agent deployed${NC}" +echo -e "${GREEN}✓ Deployment tested${NC}\n" + +echo -e "${YELLOW}Next steps:${NC}" +echo "1. View logs: agentcore logs --tail 50" +echo "2. Invoke agent: agentcore invoke --bearer-token \$BEARER_TOKEN '{\"user_input\": \"your question\"}'" +echo "3. Monitor in AWS Console: CloudWatch > GenAI Observability" +echo "" +echo -e "${BLUE}For more information, see README.md${NC}" diff --git a/02-use-cases/local-prototype-to-agentcore/agentcore_app/package-lock.json b/02-use-cases/local-prototype-to-agentcore/agentcore_app/package-lock.json new file mode 100644 index 000000000..ffc5b794e --- /dev/null +++ b/02-use-cases/local-prototype-to-agentcore/agentcore_app/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "agentcore_app", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}