AWS just solved the biggest AI agent bottleneck
When Anthropic announced MCP in late November 2024, i immediately saw the potential. But there was a problem - no good way to run MCP servers on AWS Lambda. Then AWS quietly released AgentCore Gateway. Here's how to build it in 8 steps.
Nima CEO & Solution Architect

15 min read

4 months ago

πŸš€ AWS just solved the biggest AI agent bottleneck

When Anthropic announced MCP in late November 2024, i immediately saw the potential. But there was a problem - no good way to run MCP servers on AWS Lambda.

Spent weeks trying different approaches. Built a custom AppSync solution that simulated streamable HTTP and Server-Sent Events. It technically worked but required too much plumbing.

Meanwhile, Cloudflare Workers had native MCP support. Google Cloud Run made it simple too. But if you've built on AWS, you know Lambda and API Gateway are the gold standard for serverless.

Then in early 2025, AWS quietly released AgentCore Gateway.

Game over.

No more custom glue code.
No more M×N tool chaos.
No more protocol headaches.

Here's how to build it in 8 steps:


🀯 The Problem

AI agents need tools. APIs, databases, Lambda functions, other agents.

But connecting each agent to each tool gets messy fast.

100 agents × 1,000 tools = 100,000 connections.

Companies are hitting this wall right now.


✨ The Solution

AgentCore Gateway fixes this.

It's a managed service that gives every agent the same tool interface.

Lambda FunctionAgentCore GatewayMCP ProtocolAI Agents


πŸ› οΈ What We're Building

βœ… Serverless math API via Lambda
βœ… Secure JWT authentication via Cognito
βœ… MCP-compatible gateway via AgentCore
βœ… AI agent integration via Strands/Claude

Time to complete: ~30 minutes
Lines of code: <100 per step


πŸ“‹ Prerequisites

  • βœ… AWS CLI configured with admin permissions
  • βœ… Python 3.10+ (required for Strands agents)
  • βœ… AWS Account with Bedrock access in us-east-1

Step 1: Create Lambda Execution Role

What we're doing: Setting up permissions for our serverless function.

# Create trust policy
cat > lambda-trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "lambda.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

# Create the role
aws iam create-role \
  --role-name lambda-execution-role \
  --assume-role-policy-document file://lambda-trust-policy.json

# Attach execution policy
aws iam attach-role-policy \
  --role-name lambda-execution-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

βœ… Result: Lambda can now execute and write logs


Step 2: Deploy Math Lambda Function

What we're doing: Creating the serverless function that becomes our AI tool.

File: create-lambda.py

import boto3
import json
import zipfile
import io

# The actual Lambda function code
lambda_code = '''
import json

def handler(event, context):
    operation = event.get('operation', 'add')
    a = event.get('a', 0)
    b = event.get('b', 0)
    
    if operation == 'add':
        result = a + b
    elif operation == 'multiply':
        result = a * b
    else:
        result = 0
    
    return {
        'result': result,
        'operation': operation,
        'inputs': {'a': a, 'b': b}
    }
'''

# Deploy it
lambda_client = boto3.client('lambda', region_name='us-east-1')
sts = boto3.client('sts')
account_id = sts.get_caller_identity()['Account']

# Package and create
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
    zip_file.writestr('index.py', lambda_code)
zip_buffer.seek(0)

response = lambda_client.create_function(
    FunctionName='math-tools-function',
    Runtime='python3.12',
    Role=f'arn:aws:iam::{account_id}:role/lambda-execution-role',
    Handler='index.handler',
    Code={'ZipFile': zip_buffer.read()}
)

print(f"βœ… Lambda deployed: {response['FunctionArn']}")

Test it:

python3 create-lambda.py
aws lambda invoke --function-name math-tools-function \
  --payload '{"operation":"add","a":15,"b":25}' response.json
cat response.json  # Should show: {"result": 40, ...}

βœ… Result: Working serverless math API


Step 3: Setup Cognito Authentication

What we're doing: Creating JWT token authentication (no AWS credentials needed for agents).

File: setup-cognito.py

import boto3
import time

cognito = boto3.client('cognito-idp', region_name='us-east-1')

# 1. User Pool
pool_response = cognito.create_user_pool(
    PoolName='agentcore-gateway-pool',
    Policies={'PasswordPolicy': {'MinimumLength': 8}}
)
user_pool_id = pool_response['UserPool']['Id']

# 2. Resource Server with scopes
cognito.create_resource_server(
    UserPoolId=user_pool_id,
    Identifier='agentcore-gateway',
    Name='AgentCore Gateway',
    Scopes=[
        {'ScopeName': 'read', 'ScopeDescription': 'Read access'},
        {'ScopeName': 'write', 'ScopeDescription': 'Write access'}
    ]
)

# 3. M2M Client (machine-to-machine)
client_response = cognito.create_user_pool_client(
    UserPoolId=user_pool_id,
    ClientName='agentcore-gateway-client',
    GenerateSecret=True,
    AllowedOAuthFlows=['client_credentials'],
    AllowedOAuthScopes=['agentcore-gateway/read', 'agentcore-gateway/write'],
    AllowedOAuthFlowsUserPoolClient=True
)

# 4. Domain for token endpoint
domain_name = f"agentcore-{int(time.time())}"
cognito.create_user_pool_domain(Domain=domain_name, UserPoolId=user_pool_id)

# Save config
with open('cognito-config.txt', 'w') as f:
    f.write(f"USER_POOL_ID={user_pool_id}\n")
    f.write(f"CLIENT_ID={client_response['UserPoolClient']['ClientId']}\n")
    f.write(f"CLIENT_SECRET={client_response['UserPoolClient']['ClientSecret']}\n")
    f.write(f"TOKEN_URL=https://{domain_name}.auth.us-east-1.amazoncognito.com/oauth2/token\n")

print("βœ… Cognito setup complete - JWT tokens ready!")

βœ… Result: Secure JWT authentication system


Step 4: Create AgentCore Gateway

What we're doing: Building the MCP endpoint that transforms Lambda into agent tools.

File: create-gateway.py

import boto3
import json

# Load Cognito config
config = {}
with open('cognito-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        config[key] = value

# Create AgentCore role first
iam = boto3.client('iam')
sts = boto3.client('sts')
account_id = sts.get_caller_identity()['Account']

trust_policy = {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }]
}

iam.create_role(
    RoleName='AgentCoreGatewayRole',
    AssumeRolePolicyDocument=json.dumps(trust_policy)
)

# Attach Lambda invoke permission
iam.attach_role_policy(
    RoleName='AgentCoreGatewayRole',
    PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaRole'
)

# Create the gateway
agentcore = boto3.client('bedrock-agentcore-control', region_name='us-east-1')

gateway_response = agentcore.create_gateway(
    name='MathToolsGateway',
    description='Math operations via MCP',
    roleArn=f'arn:aws:iam::{account_id}:role/AgentCoreGatewayRole',
    protocolType='MCP',
    authorizerType='CUSTOM_JWT',
    authorizerConfiguration={
        'customJWTAuthorizer': {
            'discoveryUrl': f'https://cognito-idp.us-east-1.amazonaws.com/{config["USER_POOL_ID"]}/.well-known/openid-configuration',
            'allowedClients': [config['CLIENT_ID']]
        }
    }
)

print(f"βœ… Gateway created: {gateway_response['gatewayUrl']}")

# Save gateway info
with open('gateway-config.txt', 'w') as f:
    f.write(f"GATEWAY_ID={gateway_response['gatewayId']}\n")
    f.write(f"GATEWAY_URL={gateway_response['gatewayUrl']}\n")

βœ… Result: MCP-compatible gateway endpoint


Step 5: Connect Lambda as MCP Tool

What we're doing: Defining the tool schema that tells AI agents how to use our math functions.

File: add-lambda-target.py

import boto3

# Load configs
gateway_config = {}
with open('gateway-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        gateway_config[key] = value

sts = boto3.client('sts')
account_id = sts.get_caller_identity()['Account']
lambda_arn = f'arn:aws:lambda:us-east-1:{account_id}:function:math-tools-function'

agentcore = boto3.client('bedrock-agentcore-control', region_name='us-east-1')

# Add Lambda as MCP tool
agentcore.create_gateway_target(
    gatewayIdentifier=gateway_config['GATEWAY_ID'],
    name='MathTarget',
    description='Math operations target',
    targetConfiguration={
        'mcp': {
            'lambda': {
                'lambdaArn': lambda_arn,
                'toolSchema': {
                    'inlinePayload': [
                        {
                            'name': 'add_numbers',
                            'description': 'Add two numbers together',
                            'inputSchema': {
                                'type': 'object',
                                'properties': {
                                    'a': {'type': 'number', 'description': 'First number'},
                                    'b': {'type': 'number', 'description': 'Second number'}
                                },
                                'required': ['a', 'b']
                            }
                        }
                    ]
                }
            }
        }
    },
    credentialProviderConfigurations=[
        {'credentialProviderType': 'GATEWAY_IAM_ROLE'}
    ]
)

print("βœ… Lambda connected as MCP tool!")

βœ… Result: Lambda function is now discoverable by AI agents


Step 6: Test with JWT Authentication

What we're doing: Verifying everything works with token-based auth (not AWS credentials).

File: test-gateway.py

import requests
import json

# Load configs
cognito_config = {}
with open('cognito-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        cognito_config[key] = value

gateway_config = {}
with open('gateway-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        gateway_config[key] = value

# Get JWT token (NO AWS credentials needed!)
def get_jwt_token():
    response = requests.post(
        cognito_config['TOKEN_URL'],
        data=f"grant_type=client_credentials&client_id={cognito_config['CLIENT_ID']}&client_secret={cognito_config['CLIENT_SECRET']}",
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )
    return response.json()['access_token']

# Test MCP protocol
token = get_jwt_token()
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {token}"
}

# List available tools
payload = {
    "jsonrpc": "2.0",
    "id": "test",
    "method": "tools/list"
}

response = requests.post(gateway_config['GATEWAY_URL'], headers=headers, json=payload)
result = response.json()

print("βœ… Available tools:")
print(json.dumps(result, indent=2))

# Call the math tool
if 'result' in result and 'tools' in result['result']:
    tool_name = result['result']['tools'][0]['name']
    call_payload = {
        "jsonrpc": "2.0",
        "id": "math-test",
        "method": "tools/call",
        "params": {
            "name": tool_name,
            "arguments": {"a": 15, "b": 25, "operation": "add"}
        }
    }
    
    call_response = requests.post(gateway_config['GATEWAY_URL'], headers=headers, json=call_payload)
    print(f"\nβœ… Math result: {call_response.json()}")

βœ… Result: Gateway working with JWT authentication


Step 7: Connect AI Agents

What we're doing: Connecting real AI agents using JWT tokens (not AWS profiles).

Option A: Strands Agents

File: strands-agent.py

from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp.mcp_client import MCPClient
from mcp.client.streamable_http import streamablehttp_client
import requests

# Load configs (same as test file)
cognito_config = {}
with open('cognito-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        cognito_config[key] = value

gateway_config = {}
with open('gateway-config.txt', 'r') as f:
    for line in f:
        key, value = line.strip().split('=', 1)
        gateway_config[key] = value

def get_jwt_token():
    response = requests.post(
        cognito_config['TOKEN_URL'],
        data=f"grant_type=client_credentials&client_id={cognito_config['CLIENT_ID']}&client_secret={cognito_config['CLIENT_SECRET']}",
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )
    return response.json()['access_token']

# Create MCP client with JWT auth
token = get_jwt_token()
mcp_client = MCPClient(
    lambda: streamablehttp_client(
        gateway_config['GATEWAY_URL'], 
        headers={"Authorization": f"Bearer {token}"}
    )
)

with mcp_client:
    # Get tools
    tools = []
    result = mcp_client.list_tools_sync()
    tools.extend(result)
    
    print(f"βœ… Found tools: {[tool.tool_name for tool in tools]}")
    
    # Create agent with Bedrock model
    model = BedrockModel(
        model_id="us.amazon.nova-pro-v1:0",
        temperature=0.7
    )
    
    agent = Agent(model=model, tools=tools)
    
    # Test the agent
    result = agent("Can you add 15 and 25 using the available math tools?")
    print(f"\nπŸ€– Agent result: {result}")

Install and run:

pip install strands-agents boto3 requests
python3 strands-agent.py

Option B: Claude Desktop

File: claude-config.json

{
  "mcpServers": {
    "agentcore-math": {
      "command": "python3",
      "args": ["./strands-agent.py"]
    }
  }
}
cp claude-config.json ~/Library/Application\ Support/Claude/claude_desktop_config.json

βœ… Result: AI agents can now use your Lambda functions as tools!


Step 8: Advanced Features

Semantic Search

Enable intelligent tool discovery:

search_config = {
    "mcp": {"searchType": "SEMANTIC", "supportedVersions": ["2025-03-26"]}
}

agentcore.create_gateway(
    # ... other params
    protocolConfiguration=search_config
)

Monitoring

Built-in CloudWatch metrics:

  • Invocations - Total requests
  • Latency - Response times
  • Errors - 4xx/5xx rates
  • TargetExecutionTime - Lambda performance

πŸŽ‰ What You Built

Lambda FunctionAgentCore GatewayMCP ProtocolAI Agents

βœ… Serverless math API - No infrastructure to manage
βœ… JWT Authentication - Enterprise-grade security
βœ… MCP Compatibility - Works with any agent framework
βœ… Auto Tool Discovery - Agents find tools automatically
βœ… Built-in Monitoring - CloudWatch integration


πŸš€ What Changed

Before:

  • Custom code for each agent-tool pair
  • Protocol translation problems
  • Complex security setup
  • M×N scaling issues

After:

  • βœ… One gateway, unlimited tools
  • βœ… Automatic MCP translation
  • βœ… Built-in JWT security
  • βœ… Linear scaling

πŸ’‘ What's Next

  1. Add More Tools - Connect more Lambda functions
  2. OpenAPI Integration - Connect existing REST APIs
  3. Multi-Agent Systems - Build agent teams
  4. Production Setup - Add monitoring and alerts
  5. Semantic Search - Enable smart tool discovery

🎯 Key Points

AgentCore Gateway is API Gateway for agents.

βœ… No custom code - Managed service
βœ… Built-in security - OAuth 2.0 with Cognito
βœ… Works everywhere - MCP protocol standard
βœ… Scales automatically - Serverless architecture

Your Lambda functions are now AI agent tools.

That's it.


Built with Amazon Bedrock AgentCore Gateway.

Want to build your own? Follow this tutorial.

#AWS #AI #Serverless #AgentCore #MCP #BedrockAgentCore