Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JayDoubleu/9b0b9d4d80f177ff70c9058d0ed45a93 to your computer and use it in GitHub Desktop.
Save JayDoubleu/9b0b9d4d80f177ff70c9058d0ed45a93 to your computer and use it in GitHub Desktop.
Step-by-step guide to deploy FastMCP (MCP protocol) servers on AWS Bedrock AgentCore. Non-interactive deployment with full transparency into AWS resources created. Includes complete deployment script.

FastMCP on AWS Bedrock AgentCore - Step by Step Guide

Important Notice

Amazon Bedrock AgentCore is in preview release and is subject to change.

Note: This guide is based on the official AWS documentation but uses a non-interactive deployment method and provides visibility into the IAM roles and ECR repositories created, making the process more transparent. The original guide uses interactive prompts which this guide avoids.

Platform Requirements

This guide has been tested on Linux x86_64/amd64 systems. While Bedrock AgentCore runs on ARM64, the deployment process uses CodeBuild for cross-platform compilation.

Recommended environments:

  • Linux x86_64/amd64 machines
  • GitHub Codespaces
  • WSL2 on Windows
  • EC2 instances (x86_64)

Note: Running this guide on ARM64 machines (e.g., Apple Silicon Macs, ARM EC2 instances) may produce different results and some scripts might require modifications. The agentcore CLI automatically handles the ARM64 build process via CodeBuild regardless of your local architecture.

Supported AWS Regions

Amazon Bedrock AgentCore is supported in the following AWS Regions:

  • US East (N. Virginia) - us-east-1
  • US West (Oregon) - us-west-2
  • Europe (Frankfurt) - eu-central-1
  • Asia Pacific (Sydney) - ap-southeast-2

Reference: AWS Documentation (as of 02/09/2025)

Architecture Overview

This guide demonstrates deploying a FastMCP server to AWS Bedrock AgentCore with OAuth authentication via AWS Cognito.

FastMCP Server (Python with MCP protocol)
    ↓
AgentCore CLI (configures deployment)
    ↓
AWS CodeBuild (cross-platform ARM64 build)
    ↓
Docker Container (ARM64 Linux)
    ↓
ECR Repository (stores container image)
    ↓
Bedrock AgentCore Runtime (hosts MCP server)
    ↓
JWT Authentication (Cognito User Pool)
    ↓
MCP Client (VSCode or custom Python client)

MCP Server Compatibility

This guide uses a sample my_mcp_server.py built with FastMCP for demonstration. However:

  • Any MCP server that supports the streamable-http transport mode should work with Bedrock AgentCore
  • Off-the-shelf MCP servers using FastMCP library can be deployed following the same process
  • MCP servers in other languages (Go, TypeScript, etc.) may also be compatible if they support HTTP streaming transport
  • Note: Hosting non-Python MCP servers like terraform-mcp-server (Go) hasn't been tested yet but should theoretically work with appropriate Dockerfile modifications

Prerequisites

Before starting, ensure you have the following tools installed:

  • AWS CLI configured with appropriate credentials
  • Python 3.8 or later
  • pip package manager
  • Docker (optional, only for local builds)

Install the Amazon Bedrock AgentCore CLI:

pip install bedrock-agentcore-starter-toolkit

⚠️ IMPORTANT:

  • All steps must be run in sequence with the same AGENT_NAME
  • If you restart, you must begin from Step 1 with a new unique name
  • Each step builds on the previous one and saves variables to deployment.env
  • If you close your terminal, run source deployment.env to reload variables

Step 1: Set up basic environment

Create a unique agent name with timestamp and set AWS region. Save these to a file so they persist across terminal sessions.

# Generate unique agent name with timestamp
export AGENT_NAME="mcp_$(date +%Y%m%d_%H%M%S)"
export AWS_REGION="eu-central-1"

# Save to env file for persistence
cat > deployment.env << EOF
export AGENT_NAME="$AGENT_NAME"
export AWS_REGION="$AWS_REGION"
EOF

echo "Agent name: $AGENT_NAME"
echo "AWS Region: $AWS_REGION"
echo ""
echo "Environment saved to deployment.env"
echo "To reload in new session: source deployment.env"

Expected output:

  • Should display a unique agent name like: mcp_20250902_190000
  • Should display AWS Region: eu-central-1

Step 1 Verified: Working


Step 2: Create MCP Server Files

Create the FastMCP server and requirements file.

# Create FastMCP server
cat > my_mcp_server.py << 'EOF'
# my_mcp_server.py

from mcp.server.fastmcp import FastMCP
from starlette.responses import JSONResponse

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers together"""
    return a + b

@mcp.tool()
def multiply_numbers(a: int, b: int) -> int:
    """Multiply two numbers together"""
    return a * b

@mcp.tool()
def greet_user(name: str) -> str:
    """Greet a user by name"""
    return f"Hello, {name}! Nice to meet you."

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
EOF

# Create requirements file
cat > requirements.txt << 'EOF'
fastmcp
mcp
pydantic
httpx
botocore
EOF

# Verify files were created
ls -la my_mcp_server.py requirements.txt

Expected output:

  • Should show two files created with non-zero size

Step 2 Verified: Working


Step 3: Create Cognito User Pool

Create a Cognito user pool for OAuth authentication.

# Load our environment
source deployment.env

# Create Cognito pool
POOL_NAME="${AGENT_NAME}_pool"
POOL_ID=$(aws cognito-idp create-user-pool --pool-name "$POOL_NAME" --region $AWS_REGION --query 'UserPool.Id' --output text)

# Create client WITHOUT secret (simpler authentication)
CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id $POOL_ID \
  --client-name "${AGENT_NAME}_client" \
  --no-generate-secret \
  --explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
  --region $AWS_REGION \
  --query 'UserPoolClient.ClientId' \
  --output text)

# Save to env file
cat >> deployment.env << EOF
export POOL_ID="$POOL_ID"
export CLIENT_ID="$CLIENT_ID"
EOF

# Display results
echo "Pool ID: $POOL_ID"
echo "Client ID: $CLIENT_ID"
echo ""
echo "Cognito credentials saved to deployment.env"

Expected output:

  • Should display a Pool ID starting with region like: eu-central-1_XXXXXXXX
  • Should display a Client ID (alphanumeric string)

Step 3 Verified: Working


Step 4: Create Test User

Create a test user in the Cognito pool.

# Load our environment
source deployment.env

# Create test user
aws cognito-idp admin-create-user \
  --user-pool-id $POOL_ID \
  --username "[email protected]" \
  --user-attributes Name=email,[email protected] Name=email_verified,Value=true \
  --temporary-password "TempPass123!" \
  --message-action SUPPRESS \
  --region $AWS_REGION > /dev/null

# Set permanent password
aws cognito-idp admin-set-user-password \
  --user-pool-id $POOL_ID \
  --username "[email protected]" \
  --password "TestPass123!" \
  --permanent \
  --region $AWS_REGION

echo "✅ Test user created: [email protected]"

Expected output:

Step 4 Verified: Working


Step 5: Create IAM Execution Role

Create an IAM role for Bedrock AgentCore with necessary permissions.

# Load our environment
source deployment.env

ROLE_NAME="BedrockAgentCoreRole-${AGENT_NAME}"

# Create trust policy with security conditions
TRUST_POLICY=$(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AssumeRolePolicy",
      "Effect": "Allow",
      "Principal": {
        "Service": "bedrock-agentcore.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "657579860855"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:bedrock-agentcore:eu-central-1:657579860855:*"
        }
      }
    }
  ]
}
EOF
)

# Create the role
aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document "$TRUST_POLICY" \
  --region $AWS_REGION > /dev/null 2>&1

# Create comprehensive inline policy (matching auto-generated roles)
POLICY_DOCUMENT=$(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ECRImageAccess",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Resource": [
        "arn:aws:ecr:${AWS_REGION}:657579860855:repository/*"
      ]
    },
    {
      "Sid": "ECRTokenAccess",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogStreams",
        "logs:CreateLogGroup",
        "logs:DescribeLogGroups"
      ],
      "Resource": [
        "arn:aws:logs:${AWS_REGION}:657579860855:log-group:/aws/bedrock-agentcore/runtimes/*",
        "arn:aws:logs:${AWS_REGION}:657579860855:log-group:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:${AWS_REGION}:657579860855:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "xray:PutTraceSegments",
        "xray:PutTelemetryRecords",
        "xray:GetSamplingRules",
        "xray:GetSamplingTargets"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Resource": "*",
      "Action": "cloudwatch:PutMetricData",
      "Condition": {
        "StringEquals": {
          "cloudwatch:namespace": "bedrock-agentcore"
        }
      }
    },
    {
      "Sid": "BedrockAgentCoreRuntime",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:InvokeAgentRuntime"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:657579860855:runtime/*"
      ]
    },
    {
      "Sid": "BedrockAgentCoreMemory",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:CreateMemory",
        "bedrock-agentcore:CreateEvent",
        "bedrock-agentcore:GetEvent",
        "bedrock-agentcore:GetMemory",
        "bedrock-agentcore:GetMemoryRecord",
        "bedrock-agentcore:ListActors",
        "bedrock-agentcore:ListEvents",
        "bedrock-agentcore:ListMemoryRecords",
        "bedrock-agentcore:ListSessions",
        "bedrock-agentcore:DeleteEvent",
        "bedrock-agentcore:DeleteMemoryRecord",
        "bedrock-agentcore:RetrieveMemoryRecords"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:657579860855:memory/*",
        "*"
      ]
    },
    {
      "Sid": "BedrockAgentCoreIdentity",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:GetResourceApiKey",
        "bedrock-agentcore:GetResourceOauth2Token",
        "bedrock-agentcore:GetWorkloadAccessToken",
        "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
        "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:657579860855:token-vault/*",
        "arn:aws:bedrock-agentcore:${AWS_REGION}:657579860855:workload-identity-directory/*"
      ]
    },
    {
      "Sid": "BedrockModelInvocation",
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream",
        "bedrock:ApplyGuardrail"
      ],
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/*",
        "arn:aws:bedrock:${AWS_REGION}:657579860855:*"
      ]
    }
  ]
}
EOF
)

# Attach the inline policy
aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name "BedrockAgentCoreExecutionPolicy" \
  --policy-document "$POLICY_DOCUMENT" \
  --region $AWS_REGION 2>/dev/null

EXECUTION_ROLE_ARN="arn:aws:iam::657579860855:role/$ROLE_NAME"

# Save to env file
cat >> deployment.env << EOF
export ROLE_NAME="$ROLE_NAME"
export EXECUTION_ROLE_ARN="$EXECUTION_ROLE_ARN"
EOF

echo "✅ Created execution role: $ROLE_NAME"
echo "Role details saved to deployment.env"

Expected output:

  • Should display: ✅ Created execution role: BedrockAgentCoreRole-mcp_XXXXXXXXX
  • Should display: Role details saved to deployment.env

Step 5 Verified: Working


Step 6: Create ECR Repository

Create an ECR repository for the Docker image.

# Load our environment
source deployment.env

ECR_REPO_NAME="bedrock-agentcore-${AGENT_NAME}"

# Create ECR repository
aws ecr create-repository \
  --repository-name "$ECR_REPO_NAME" \
  --region $AWS_REGION > /dev/null 2>&1

ECR_URI="657579860855.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}"

# Save to env file
cat >> deployment.env << EOF
export ECR_REPO_NAME="$ECR_REPO_NAME"
export ECR_URI="$ECR_URI"
EOF

echo "✅ Created ECR repository: $ECR_REPO_NAME"
echo "   URI: $ECR_URI"
echo "ECR details saved to deployment.env"

Expected output:

  • Should display: ✅ Created ECR repository: bedrock-agentcore-mcp_XXXXXXXXX
  • Should display the full ECR URI
  • Should display: ECR details saved to deployment.env

Step 6 Verified: Working


Step 7: Configure Bedrock AgentCore

Configure agentcore with all the resources we created. This step prepares the configuration but doesn't deploy yet.

# Load our environment with all variables from previous steps
source deployment.env

# Build the discovery URL
DISCOVERY_URL="https://cognito-idp.${AWS_REGION}.amazonaws.com/${POOL_ID}/.well-known/openid-configuration"

# Configure agentcore (no interactive prompts)
agentcore configure \
  -e my_mcp_server.py \
  --name $AGENT_NAME \
  --protocol MCP \
  --region $AWS_REGION \
  --execution-role "$EXECUTION_ROLE_ARN" \
  --ecr "$ECR_URI" \
  --requirements-file requirements.txt \
  --authorizer-config "{\"customJWTAuthorizer\":{\"discoveryUrl\":\"$DISCOVERY_URL\",\"allowedClients\":[\"$CLIENT_ID\"]}}"

echo "✅ AgentCore configured"

# Check if .bedrock_agentcore.yaml was created
if [ -f ".bedrock_agentcore.yaml" ]; then
  echo "✅ Configuration file created"
else
  echo "❌ Configuration file NOT created - something went wrong"
fi

Expected output:

  • Should show configuration summary with agent details
  • May show platform mismatch warning (linux/amd64 vs linux/arm64) - this is normal
  • Should display: ✅ AgentCore configured
  • Should display: ✅ Configuration file created
  • Will generate Dockerfile and .dockerignore files
  • Will set the agent as default

Step 7 Verified: Working


Step 8: Launch to Bedrock AgentCore

Deploy the configured agent to Bedrock AgentCore.

Important Architecture Note: Bedrock AgentCore runs on ARM64 architecture. If your deployment machine is x86_64/amd64:

  • The agentcore CLI will automatically use CodeBuild for cross-platform building
  • Your local files will be packaged and uploaded to an S3 bucket
  • CodeBuild will build the ARM64 Docker image in the cloud
  • The ARM64 image will be pushed to ECR and deployed to Bedrock AgentCore
  • This process typically takes 1-2 minutes

⚠️ KNOWN ISSUES:

  1. If the IAM role was just created, it may take 10-15 seconds to propagate
  2. If you see "invalid trust policy" error, wait a moment and retry
  3. Build failures may occur if ECR repository wasn't fully created
  4. Platform mismatch warning (linux/amd64 vs linux/arm64) is expected and doesn't prevent deployment
# Load our environment
source deployment.env

# Launch the agent (this takes 1-2 minutes)
agentcore launch --agent $AGENT_NAME

# Extract the agent ARN from the yaml file
AGENT_ARN=$(grep "agent_arn:" .bedrock_agentcore.yaml | tail -1 | awk '{print $2}')

# Save final ARN to env file
echo "export AGENT_ARN=\"$AGENT_ARN\"" >> deployment.env

echo "✅ Deployed to Bedrock AgentCore"
echo "   Agent ARN: $AGENT_ARN"
echo ""
echo "Complete environment saved to deployment.env"
echo "You can now test the deployment or use it in other applications"

Expected output:

  • Shows "🚀 Launching Bedrock AgentCore (codebuild mode - RECOMMENDED)..."
  • Creates CodeBuild project and uploads source to S3
  • Shows CodeBuild progress stages (QUEUED, PROVISIONING, BUILD, etc.)
  • Build typically completes in 1-2 minutes
  • Shows "✅ Agent created/updated" with the Agent ARN
  • Displays deployment summary with:
    • Agent Name and ARN
    • CodeBuild ID
    • ECR URI
    • CloudWatch log locations
    • Commands for checking status and invoking the agent

Step 8 Verified: Working


Step 9: Test the Deployed MCP Server

Test that your MCP server is working by authenticating with Cognito and calling the deployed agent.

9.1: Get Bearer Token from Cognito

First, authenticate with Cognito to get a bearer token.

⚠️ Important: Use the AccessToken (not IdToken) as it contains the required client_id claim that Bedrock AgentCore expects.

# Load our environment
source deployment.env

# Get the ACCESS token from Cognito (no secret hash needed)
TOKEN_RESPONSE=$(aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id $CLIENT_ID \
  --auth-parameters [email protected],PASSWORD=TestPass123! \
  --region $AWS_REGION \
  --query 'AuthenticationResult.AccessToken' \
  --output text)

export BEARER_TOKEN=$TOKEN_RESPONSE

echo "✅ Bearer token obtained"
echo "Token (first 50 chars): ${BEARER_TOKEN:0:50}..."

9.2: Create and Run Test Script

Create a Python test script that connects to your MCP server:

# Create the test script
cat > my_mcp_client_remote.py << 'EOF'
import asyncio
import os
import sys

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    agent_arn = os.getenv('AGENT_ARN')
    bearer_token = os.getenv('BEARER_TOKEN')
    region = os.getenv('AWS_REGION', 'eu-central-1')

    if not agent_arn or not bearer_token:
        print("Error: AGENT_ARN or BEARER_TOKEN environment variable is not set")
        sys.exit(1)

    encoded_arn = agent_arn.replace(':', '%3A').replace('/', '%2F')
    mcp_url = f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"
    headers = {
        "authorization": f"Bearer {bearer_token}",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
    }
    print(f"Invoking: {mcp_url}\n")

    async with streamablehttp_client(mcp_url, headers, timeout=120, terminate_on_close=False) as (
        read_stream,
        write_stream,
        _,
    ):
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()

            # List available tools
            print("Available tools:")
            tool_result = await session.list_tools()
            for tool in tool_result.tools:
                print(f"  - {tool.name}: {tool.description}")

            # Test calling a tool
            print("\nTesting add_numbers(5, 3):")
            result = await session.call_tool("add_numbers", {"a": 5, "b": 3})
            print(f"Result: {result.content[0].text}")

            print("\nTesting greet_user('Alice'):")
            result = await session.call_tool("greet_user", {"name": "Alice"})
            print(f"Result: {result.content[0].text}")

asyncio.run(main())
EOF

# Install required dependencies
pip install mcp httpx --quiet

# Export the environment variables and run the test
export AGENT_ARN
export BEARER_TOKEN
export AWS_REGION
python3 my_mcp_client_remote.py

Expected output:

  • Should display the MCP URL being invoked
  • Should list 3 available tools: add_numbers, multiply_numbers, greet_user
  • Should show "Result: 8" for add_numbers(5, 3)
  • Should show "Result: Hello, Alice! Nice to meet you." for greet_user('Alice')

Step 9 Verified: Working - MCP server successfully tested with all tools functioning correctly.


Step 10: Generate VSCode MCP Configuration

Create a helper script to generate VSCode MCP configuration with fresh bearer tokens:

# Create VSCode config generator for test user
cat > generate_vscode_config.sh << 'EOF'
#!/bin/bash

# Load environment
if [ ! -f "deployment.env" ]; then
    echo "❌ Error: deployment.env not found. Please run deployment steps first."
    exit 1
fi

source deployment.env

# Check required variables
if [ -z "$CLIENT_ID" ] || [ -z "$AWS_REGION" ] || [ -z "$AGENT_ARN" ] || [ -z "$AGENT_NAME" ]; then
    echo "❌ Error: Missing required environment variables"
    exit 1
fi

echo "🔐 Authenticating test user..."

# Get fresh token for test user (no secret hash needed)
BEARER_TOKEN=$(aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id $CLIENT_ID \
  --auth-parameters [email protected],PASSWORD=TestPass123! \
  --region $AWS_REGION \
  --query 'AuthenticationResult.AccessToken' \
  --output text 2>/dev/null)

if [ -z "$BEARER_TOKEN" ]; then
    echo "❌ Error: Failed to get bearer token"
    exit 1
fi

# URL encode the ARN with double encoding for forward slash
ENCODED_ARN=$(echo $AGENT_ARN | sed 's/:/\%3A/g' | sed 's/\//\%252F/g')

# Generate config
cat > vscode_mcp_config.json << JSON
{
  "servers": {
    "${AGENT_NAME}": {
      "url": "https://bedrock-agentcore.${AWS_REGION}.amazonaws.com/runtimes/${ENCODED_ARN}/invocations?qualifier=DEFAULT",
      "type": "http",
      "headers": {
        "Authorization": "Bearer $BEARER_TOKEN",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
      }
    }
  }
}
JSON

echo "✅ Generated vscode_mcp_config.json"
echo "   User: [email protected]"
echo "   Agent: ${AGENT_NAME}"
echo "   Token expires at: $(date -d '+1 hour' 2>/dev/null || date -v +1H)"
echo ""
echo "📋 To use in VSCode:"
echo "   1. Copy the contents of vscode_mcp_config.json"
echo "   2. Add to your VSCode MCP settings"
echo "   3. Restart the MCP connection"
EOF
chmod +x generate_vscode_config.sh

To generate fresh VSCode configuration:

./generate_vscode_config.sh

Known Issues and Limitations

  1. FastMCP Transport Mode: FastMCP must run in streamable-http mode for Bedrock AgentCore. The default stdio mode will not work with HTTP requests.

  2. OAuth Token Type: Must use AccessToken (not IdToken) from Cognito as it contains the client_id claim required by Bedrock AgentCore.

  3. Simplified Authentication: The setup now uses a Cognito client without a secret, eliminating the need for SECRET_HASH calculation.

  4. IAM Permissions: The execution role requires comprehensive permissions including X-Ray, CloudWatch, and all bedrock-agentcore operations. The role must also include proper trust policy conditions for security.

  5. Accept Header Requirement: MCP connections require the Accept header to include both application/json and text/event-stream.

  6. URL Encoding: VSCode requires double-encoding of forward slashes in the ARN (%252F instead of %2F).


Monitoring and Logs

After deployment, you can monitor your MCP server using these commands:

Check Agent Status

# Load environment variables
source deployment.env

# Shows deployment status, endpoints, and configuration
agentcore status --agent $AGENT_NAME

View CodeBuild Logs

# Load environment variables
source deployment.env

# List build IDs for your project
aws codebuild list-builds-for-project \
  --project-name bedrock-agentcore-${AGENT_NAME}-builder \
  --region $AWS_REGION

# Get build details (replace BUILD_ID with actual ID)
aws codebuild batch-get-builds \
  --ids BUILD_ID \
  --region $AWS_REGION \
  --query 'builds[0].{Status:buildStatus,StartTime:startTime,EndTime:endTime,LogsLink:logs.deepLink}'

Check ECR Image

# Load environment variables
source deployment.env

# View pushed container image details
aws ecr describe-images \
  --repository-name bedrock-agentcore-${AGENT_NAME} \
  --region $AWS_REGION \
  --query 'imageDetails[0].{Tags:imageTags,Size:imageSizeInBytes,PushedAt:imagePushedAt}'

CloudWatch Logs

Runtime logs are created when the agent processes requests. The log group paths are shown in the deployment output and agentcore status command:

  • /aws/bedrock-agentcore/runtimes/{AGENT_NAME}-{AGENT_ID}-DEFAULT
  • /aws/bedrock-agentcore/runtimes/{AGENT_NAME}-{AGENT_ID}-DEFAULT/runtime-logs

Example commands to tail logs:

# Load environment and get agent ID from the ARN
source deployment.env
AGENT_ID=$(echo $AGENT_ARN | cut -d'/' -f2 | cut -d'-' -f2-)

# Tail the logs
aws logs tail /aws/bedrock-agentcore/runtimes/${AGENT_NAME}-${AGENT_ID}-DEFAULT --follow

Troubleshooting

Common Issues and Solutions

  1. "Execution role has invalid trust policy"

    • The IAM role may not have propagated yet
    • Wait 10-15 seconds and retry the launch command
    • Verify the role exists: aws iam get-role --role-name $ROLE_NAME
  2. Build fails during CodeBuild

    • Check CloudWatch logs for the specific error
    • Ensure all files (my_mcp_server.py, requirements.txt) exist
    • Verify ECR repository was created successfully
  3. "Agent already exists" error

    • An agent with this name was already deployed
    • Start over from Step 1 with a new unique AGENT_NAME
  4. Variables not found

    • Run source deployment.env to reload all variables
    • Check that deployment.env exists and contains all exports
  5. 403 Forbidden when testing

    • Token may have expired (tokens last 1 hour)
    • User password may not be set correctly
    • Verify Cognito pool and client IDs match
    • Ensure you're using AccessToken, not IdToken
    • Check IAM role has all required permissions
  6. Server not responding to HTTP requests

    • Verify FastMCP is running in streamable-http mode (check logs)
    • Confirm server is listening on port 8080
    • Check CloudWatch logs for startup errors

Artifacts Summary

The deployment process creates the following artifacts:

File Purpose
deployment.env Environment variables persistence across sessions
my_mcp_server.py FastMCP server implementation with tool definitions
requirements.txt Python dependencies for the MCP server
Dockerfile Auto-generated container definition for ARM64 deployment
.dockerignore Build exclusion patterns (auto-generated)
.bedrock_agentcore.yaml AgentCore configuration and deployment metadata
my_mcp_client_remote.py Test client for validating MCP server connectivity
generate_vscode_config.sh Script to generate fresh VSCode MCP configuration with auth tokens
vscode_mcp_config.json VSCode MCP client configuration (regenerated on each run)

AWS Resources Created:

  • Cognito User Pool with test user
  • IAM execution role for Bedrock AgentCore runtime
  • IAM execution role for CodeBuild (auto-created by agentcore CLI)
  • ECR repository with ARM64 container image
  • S3 bucket for CodeBuild source uploads (auto-created)
  • CodeBuild project for cross-platform builds
  • Bedrock AgentCore runtime deployment
  • CloudWatch log groups (created on first request)

Complete Deployment Script

For convenience, here's the entire deployment process in a single script:

#!/bin/bash
set -e

echo "Starting FastMCP deployment to AWS Bedrock AgentCore..."

# Step 1: Set up basic environment
export AGENT_NAME="mcp_$(date +%Y%m%d_%H%M%S)"
export AWS_REGION="eu-central-1"

cat > deployment.env << EOF
export AGENT_NAME="$AGENT_NAME"
export AWS_REGION="$AWS_REGION"
EOF

echo "Agent name: $AGENT_NAME"
echo "AWS Region: $AWS_REGION"

# Step 2: Create MCP Server Files
cat > my_mcp_server.py << 'EOF'
# my_mcp_server.py

from mcp.server.fastmcp import FastMCP
from starlette.responses import JSONResponse

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers together"""
    return a + b

@mcp.tool()
def multiply_numbers(a: int, b: int) -> int:
    """Multiply two numbers together"""
    return a * b

@mcp.tool()
def greet_user(name: str) -> str:
    """Greet a user by name"""
    return f"Hello, {name}! Nice to meet you."

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
EOF

cat > requirements.txt << 'EOF'
fastmcp
mcp
pydantic
httpx
botocore
EOF

# Step 3: Create Cognito User Pool
POOL_NAME="${AGENT_NAME}_pool"
POOL_ID=$(aws cognito-idp create-user-pool --pool-name "$POOL_NAME" --region $AWS_REGION --query 'UserPool.Id' --output text)

CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id $POOL_ID \
  --client-name "${AGENT_NAME}_client" \
  --no-generate-secret \
  --explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
  --region $AWS_REGION \
  --query 'UserPoolClient.ClientId' \
  --output text)

cat >> deployment.env << EOF
export POOL_ID="$POOL_ID"
export CLIENT_ID="$CLIENT_ID"
EOF

echo "Pool ID: $POOL_ID"
echo "Client ID: $CLIENT_ID"

# Step 4: Create Test User
aws cognito-idp admin-create-user \
  --user-pool-id $POOL_ID \
  --username "[email protected]" \
  --user-attributes Name=email,[email protected] Name=email_verified,Value=true \
  --temporary-password "TempPass123!" \
  --message-action SUPPRESS \
  --region $AWS_REGION > /dev/null

aws cognito-idp admin-set-user-password \
  --user-pool-id $POOL_ID \
  --username "[email protected]" \
  --password "TestPass123!" \
  --permanent \
  --region $AWS_REGION

echo "✅ Test user created: [email protected]"

# Step 5: Create IAM Execution Role
ROLE_NAME="BedrockAgentCoreRole-${AGENT_NAME}"

TRUST_POLICY=$(cat <<EOFPOLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AssumeRolePolicy",
      "Effect": "Allow",
      "Principal": {
        "Service": "bedrock-agentcore.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "$(aws sts get-caller-identity --query Account --output text)"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:bedrock-agentcore:${AWS_REGION}:$(aws sts get-caller-identity --query Account --output text):*"
        }
      }
    }
  ]
}
EOFPOLICY
)

aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document "$TRUST_POLICY" \
  --region $AWS_REGION > /dev/null 2>&1

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
POLICY_DOCUMENT=$(cat <<EOFPOLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ECRImageAccess",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Resource": [
        "arn:aws:ecr:${AWS_REGION}:${ACCOUNT_ID}:repository/*"
      ]
    },
    {
      "Sid": "ECRTokenAccess",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogStreams",
        "logs:CreateLogGroup",
        "logs:DescribeLogGroups"
      ],
      "Resource": [
        "arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*",
        "arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "xray:PutTraceSegments",
        "xray:PutTelemetryRecords",
        "xray:GetSamplingRules",
        "xray:GetSamplingTargets"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Resource": "*",
      "Action": "cloudwatch:PutMetricData",
      "Condition": {
        "StringEquals": {
          "cloudwatch:namespace": "bedrock-agentcore"
        }
      }
    },
    {
      "Sid": "BedrockAgentCoreRuntime",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:InvokeAgentRuntime"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:runtime/*"
      ]
    },
    {
      "Sid": "BedrockAgentCoreMemory",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:CreateMemory",
        "bedrock-agentcore:CreateEvent",
        "bedrock-agentcore:GetEvent",
        "bedrock-agentcore:GetMemory",
        "bedrock-agentcore:GetMemoryRecord",
        "bedrock-agentcore:ListActors",
        "bedrock-agentcore:ListEvents",
        "bedrock-agentcore:ListMemoryRecords",
        "bedrock-agentcore:ListSessions",
        "bedrock-agentcore:DeleteEvent",
        "bedrock-agentcore:DeleteMemoryRecord",
        "bedrock-agentcore:RetrieveMemoryRecords"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:memory/*",
        "*"
      ]
    },
    {
      "Sid": "BedrockAgentCoreIdentity",
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:GetResourceApiKey",
        "bedrock-agentcore:GetResourceOauth2Token",
        "bedrock-agentcore:GetWorkloadAccessToken",
        "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
        "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:token-vault/*",
        "arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:workload-identity-directory/*"
      ]
    },
    {
      "Sid": "BedrockModelInvocation",
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream",
        "bedrock:ApplyGuardrail"
      ],
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/*",
        "arn:aws:bedrock:${AWS_REGION}:${ACCOUNT_ID}:*"
      ]
    }
  ]
}
EOFPOLICY
)

aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name "BedrockAgentCoreExecutionPolicy" \
  --policy-document "$POLICY_DOCUMENT" \
  --region $AWS_REGION 2>/dev/null

EXECUTION_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/$ROLE_NAME"

cat >> deployment.env << EOF
export ROLE_NAME="$ROLE_NAME"
export EXECUTION_ROLE_ARN="$EXECUTION_ROLE_ARN"
EOF

echo "✅ Created execution role: $ROLE_NAME"

# Step 6: Create ECR Repository
ECR_REPO_NAME="bedrock-agentcore-${AGENT_NAME}"

aws ecr create-repository \
  --repository-name "$ECR_REPO_NAME" \
  --region $AWS_REGION > /dev/null 2>&1

ECR_URI="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}"

cat >> deployment.env << EOF
export ECR_REPO_NAME="$ECR_REPO_NAME"
export ECR_URI="$ECR_URI"
EOF

echo "✅ Created ECR repository: $ECR_REPO_NAME"

# Step 7: Configure Bedrock AgentCore
DISCOVERY_URL="https://cognito-idp.${AWS_REGION}.amazonaws.com/${POOL_ID}/.well-known/openid-configuration"

agentcore configure \
  -e my_mcp_server.py \
  --name $AGENT_NAME \
  --protocol MCP \
  --region $AWS_REGION \
  --execution-role "$EXECUTION_ROLE_ARN" \
  --ecr "$ECR_URI" \
  --requirements-file requirements.txt \
  --authorizer-config "{\"customJWTAuthorizer\":{\"discoveryUrl\":\"$DISCOVERY_URL\",\"allowedClients\":[\"$CLIENT_ID\"]}}"

echo "✅ AgentCore configured"

# Step 8: Launch to Bedrock AgentCore
echo "Deploying to Bedrock AgentCore (this takes 1-2 minutes)..."
agentcore launch --agent $AGENT_NAME

# Extract the agent ARN
AGENT_ARN=$(grep "agent_arn:" .bedrock_agentcore.yaml | tail -1 | awk '{print $2}')
echo "export AGENT_ARN=\"$AGENT_ARN\"" >> deployment.env

echo "✅ Deployment complete!"
echo "   Agent ARN: $AGENT_ARN"

# Step 9: Create test client script
cat > my_mcp_client_remote.py << 'EOF'
import asyncio
import os
import sys

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    agent_arn = os.getenv('AGENT_ARN')
    bearer_token = os.getenv('BEARER_TOKEN')
    region = os.getenv('AWS_REGION', 'eu-central-1')

    if not agent_arn or not bearer_token:
        print("Error: AGENT_ARN or BEARER_TOKEN environment variable is not set")
        sys.exit(1)

    encoded_arn = agent_arn.replace(':', '%3A').replace('/', '%2F')
    mcp_url = f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"
    headers = {
        "authorization": f"Bearer {bearer_token}",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
    }
    print(f"Invoking: {mcp_url}\n")

    async with streamablehttp_client(mcp_url, headers, timeout=120, terminate_on_close=False) as (
        read_stream,
        write_stream,
        _,
    ):
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()

            # List available tools
            print("Available tools:")
            tool_result = await session.list_tools()
            for tool in tool_result.tools:
                print(f"  - {tool.name}: {tool.description}")

            # Test calling a tool
            print("\nTesting add_numbers(5, 3):")
            result = await session.call_tool("add_numbers", {"a": 5, "b": 3})
            print(f"Result: {result.content[0].text}")

            print("\nTesting greet_user('Alice'):")
            result = await session.call_tool("greet_user", {"name": "Alice"})
            print(f"Result: {result.content[0].text}")

asyncio.run(main())
EOF

# Step 10: Create VSCode config generator
cat > generate_vscode_config.sh << 'EOFSCRIPT'
#!/bin/bash

# Load environment
if [ ! -f "deployment.env" ]; then
    echo "❌ Error: deployment.env not found. Please run deployment steps first."
    exit 1
fi

source deployment.env

# Check required variables
if [ -z "$CLIENT_ID" ] || [ -z "$AWS_REGION" ] || [ -z "$AGENT_ARN" ] || [ -z "$AGENT_NAME" ]; then
    echo "❌ Error: Missing required environment variables"
    exit 1
fi

echo "🔐 Authenticating test user..."

# Get fresh token for test user (no secret hash needed)
BEARER_TOKEN=$(aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id $CLIENT_ID \
  --auth-parameters [email protected],PASSWORD=TestPass123! \
  --region $AWS_REGION \
  --query 'AuthenticationResult.AccessToken' \
  --output text 2>/dev/null)

if [ -z "$BEARER_TOKEN" ]; then
    echo "❌ Error: Failed to get bearer token"
    exit 1
fi

# URL encode the ARN with double encoding for forward slash
ENCODED_ARN=$(echo $AGENT_ARN | sed 's/:/\%3A/g' | sed 's/\//\%252F/g')

# Generate config
cat > vscode_mcp_config.json << JSON
{
  "servers": {
    "${AGENT_NAME}": {
      "url": "https://bedrock-agentcore.${AWS_REGION}.amazonaws.com/runtimes/${ENCODED_ARN}/invocations?qualifier=DEFAULT",
      "type": "http",
      "headers": {
        "Authorization": "Bearer $BEARER_TOKEN",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
      }
    }
  }
}
JSON

echo "✅ Generated vscode_mcp_config.json"
echo "   User: [email protected]"
echo "   Agent: ${AGENT_NAME}"
echo "   Token expires at: $(date -d '+1 hour' 2>/dev/null || date -v +1H)"
echo ""
echo "📋 To use in VSCode:"
echo "   1. Copy the contents of vscode_mcp_config.json"
echo "   2. Add to your VSCode MCP settings"
echo "   3. Restart the MCP connection"
EOFSCRIPT

chmod +x generate_vscode_config.sh

echo ""
echo "Environment saved to deployment.env"
echo "Run 'source deployment.env' to load variables in new sessions"
echo ""
echo "✅ Created helper scripts:"
echo "   - my_mcp_client_remote.py (test client)"
echo "   - generate_vscode_config.sh (VSCode config generator)"
echo ""
echo "To test your deployment:"
echo "  1. Run: source deployment.env"
echo "  2. Run: pip install mcp httpx"
echo "  3. Get token: export BEARER_TOKEN=\$(aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --client-id \$CLIENT_ID --auth-parameters [email protected],PASSWORD=TestPass123! --region \$AWS_REGION --query 'AuthenticationResult.AccessToken' --output text)"
echo "  4. Run: python3 my_mcp_client_remote.py"
echo ""
echo "To generate VSCode configuration:"
echo "  Run: ./generate_vscode_config.sh"

Save this script as deploy.sh and run with:

chmod +x deploy.sh
./deploy.sh

Note: This script uses dynamic account ID retrieval instead of hardcoded values for better portability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment