Introduction
In this part 1 of this article series, we introduced Amazon Bedrock AgentCore and specifically Amazon Bedrock AgentCore Gateway which transforms existing APIs and AWS Lambda functions into agent-ready tools, offering unified access across protocols, including into Model Context Protocol (MCP), and runtime discovery. In the part 2 of the series, we used Amazon Bedrock AgentCore Gateway to convert the existing Amazon API Gateway REST API into MCP compatible tools and made it available to agents through Gateway endpoint. We also used Strands MCP Client to talk to this AgentCore Gateway endpoint.
In this part of this article series, we'll explain how to expose Lambda function(s) as an AgentCore Target. We'll use the same Lambda functions (GetOrderByID and GetOrdersByCreatedDates) which we exposed in the part 2 via Open API through the existing Amazon API Gateway API.
Transform existing AWS Lambda functions into MCP tools using Bedrock AgentCore Gateway
For the demonstration purpose, we re-use our sample application and the Amazon Gateway API described in the article Serverless applications with Java and Aurora DSQL - Part 1 Introduction and sample application. You can find the full source code of our sample application here.
We re-use generic the Python Jupyter Notebook provided in the official AWS GitHub repository with the amazon-bedrock-agentcore-samples but adopt it to our needs. I have published the final version of this playbook in my here.
Here is the architecture of how to transform the existing AWS Lambda into MCP tools using Bedrock AgentCore Gateway, see the source.
The Gateway workflow involves the following steps to connect your agents to external tools:
- Create the tools for your Gateway - Define your tools using schemas and include Lambda function ARN, tool parameters and their types
- Create a Gateway endpoint - Create the gateway that will serve as the MCP entry point with inbound authentication.
- Add targets to your Gateway - Configure the Lambda function targets that define how the gateway routes requests to specific tools. All these Lambda functions will become an MCP-compatible tool, and will be made available through your Gateway endpoint URL. Configure outbound IAM authorization for each Lambda function target.
- Update your agent code - Connect your agent to the Gateway endpoint to access all configured tools through the unified MCP interface.
To execute this tutorial you will need:
- Jupyter notebook (Python kernel)
- uv
- AWS credentials (it's enough to configure them in .aws\credentials)
- Amazon Cognito
Let's go step by step through the relevant parts of the Python Jupyter Notebook. To execute it, you'll to also check out agent_core_utils.py and store it in the same directory as your notebook. I copied this file from here to later adopt further for the needs of my sample use case.
1) The first steps are quite obvious: we check whether uv is installed, which is the prerequisite, and then use it to install botocore and boto3.
2) Then we set the environment AWS_DEFAULT_REGION to 'us-east-1' (you can use another AWS region where AgentCore is currently supported). You can also set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables, but I have them already configured in .aws\credentials, so AWS clients will pick them from there.
import os
import agent_core_utils
os.environ['AWS_DEFAULT_REGION'] = 'us-east-1'
3) Then we use agentcore_utils to create an IAM role for the AgentCore Gateway to assume (or re-use already created one in part 2) :
import agent_core_utils
agentcore_gateway_iam_role = agent_core_utils.create_agentcore_gateway_role("sample-lambdagateway")
print("Agentcore gateway role ARN: ", agentcore_gateway_iam_role['Role']['Arn'])
After executing this step IAM role and policy look like this in AWS:
4) The next step is to create a Cognito User Pool by the pool name (if you configure the already existing one, like already created in part 2, it will be re-used) by using helper functions from agentcore_utils. We also create or retrieve the Cognito Machine-to-machine (M2M) client id and client_secret which we later use to create a token which is required to talk to the agent.
import os
import boto3
import requests
import time
from botocore.exceptions import ClientError
REGION = os.environ['AWS_DEFAULT_REGION']
USER_POOL_NAME = "sample-agentcore-gateway-pool"
RESOURCE_SERVER_ID = "sample-agentcore-gateway-id"
RESOURCE_SERVER_NAME = "sample-agentcore-gateway-name"
CLIENT_NAME = "sample-agentcore-gateway-client"
SCOPES = [
{"ScopeName": "gateway:read", "ScopeDescription": "Read access"},
{"ScopeName": "gateway:write", "ScopeDescription": "Write access"}
]
scopeString = f"{RESOURCE_SERVER_ID}/gateway:read {RESOURCE_SERVER_ID}/gateway:write"
cognito = boto3.client("cognito-idp", region_name=REGION)
print("Creating or retrieving Cognito resources...")
user_pool_id = agent_core_utils.get_or_create_user_pool(cognito, USER_POOL_NAME)
print(f"User Pool ID: {user_pool_id}")
agent_core_utils.get_or_create_resource_server(cognito, user_pool_id, RESOURCE_SERVER_ID, RESOURCE_SERVER_NAME, SCOPES)
print("Resource server ensured.")
client_id, client_secret = agent_core_utils.get_or_create_m2m_client(cognito, user_pool_id, CLIENT_NAME, RESOURCE_SERVER_ID)
print(f"Client ID: {client_id}")
# Get discovery URL
cognito_discovery_url = f'https://cognito-idp.{REGION}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration'
print(cognito_discovery_url)
After executing this step, our created Cognito Pool looks like this in AWS (I removed the sensitive information like ARN and token signing key URL from the picture):
5) The next step is to AgenCore CreateGateway with Cognito authorizer without CMK using the Cognito user pool created in the previous step (we reuse client_id and cognito_discovery_url from the step before). We set LambdaOrderGateway as a Gateway name and MCP as a protocol :
# CreateGateway with Cognito authorizer without CMK. Use the Cognito user pool created in the previous step
import boto3
gateway_client = boto3.client('bedrock-agentcore-control', region_name = os.environ['AWS_DEFAULT_REGION'])
auth_config = {
"customJWTAuthorizer": {
"allowedClients": [client_id], # Client MUST match with the ClientId configured in Cognito. Example: 7rfbikfsm51j2fpaggacgng84g
"discoveryUrl": cognito_discovery_url
}
}
create_response = gateway_client.create_gateway(name='LambdaOrderGateway',
roleArn = agentcore_gateway_iam_role['Role']['Arn'], # The IAM Role must have permissions to create/list/get/delete Gateway
protocolType='MCP',
authorizerType='CUSTOM_JWT',
authorizerConfiguration=auth_config,
description='AgentCore Gateway with AWS Lambda target type'
)
print(create_response)
# Retrieve the GatewayID used for GatewayTarget creation
gatewayID = create_response["gatewayId"]
gatewayURL = create_response["gatewayUrl"]
print(gatewayID)
After executing this step, this is how created AgentCore Gateway looks like in AWS:
6) The next step is to create an AgentCore Gateway Targets (in our case 2 existing AWS Lambda function targets). For this we need to configure 2 pieces: Lambda target config for each Lambda function and credentials config.
Let's start with the Lambda target config for the GetOrderByIdWithJava21Lambda function. We need to provide a Lambda function ARN (please provide your own) and in the MCP tools schema name and description of the tool to be exposed as MCP and the input schema with the parameters (in our case id for order id and mark this parameter as required). Here is the source code piece for it:
order_by_id_lambda_target_config = {
"mcp": {
"lambda": {
"lambdaArn": '${YourGetOrderByIdLambdaFunctionARN}', # Replace this with your AWS Lambda function ARN
"toolSchema": {
"inlinePayload": [
{
"name": "get_order_by_id_tool",
"description": "tool to get the order by id",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": ["id"]
}
}
]
}
}
}
}
The Lambda function configuration for the GetOrdersByCreatedDates Lambda looks similar, but we have 2 required parameters: startDate and endDate.
Credential config looks simple as we only set the credential provider type to be GATEWAY_IAM_ROLE :
credential_config = [
{
"credentialProviderType" : "GATEWAY_IAM_ROLE"
}
]
Then we create two AgentCore Targets (one for each Lambda function) by providing Lambda target config for each Lambda function and credentials config.
Here is the example for the GetOrderByIdLambda Lambda target:
order_by_id_targetname='GetOrderByIdLambda'
response = gateway_client.create_gateway_target(
gatewayIdentifier=gatewayID,
name=order_by_id_targetname,
description='Order by id Lambda Target',
targetConfiguration=order_by_id_lambda_target_config,
credentialProviderConfigurations=credential_config)
Here is the complete code to execute:
order_by_id_lambda_target_config = {
"mcp": {
"lambda": {
"lambdaArn": '${YourGetOrderByIdLambdaFunctionARN}', # Replace this with your AWS Lambda function ARN
"toolSchema": {
"inlinePayload": [
{
"name": "get_order_by_id_tool",
"description": "tool to get the order by id",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": ["id"]
}
}
]
}
}
}
}
order_by_created_dates_lambda_target_config = {
"mcp": {
"lambda": {
"lambdaArn": '${YourGetOrdersByCreatedDatesLambdaFunctionARN}', # Replace this with your AWS Lambda function ARN
"toolSchema": {
"inlinePayload": [
{
"name": "get_orders_by_created_dates_tool",
"description": "tool to get the order by created dates",
"inputSchema": {
"type": "object",
"properties": {
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
}
},
"required": ["startDate","endDate"]
}
}
]
}
}
}
}
credential_config = [
{
"credentialProviderType" : "GATEWAY_IAM_ROLE"
}
]
order_by_id_targetname='GetOrderByIdLambda'
response = gateway_client.create_gateway_target(
gatewayIdentifier=gatewayID,
name=order_by_id_targetname,
description='Order by id Lambda Target',
targetConfiguration=order_by_id_lambda_target_config,
credentialProviderConfigurations=credential_config)
order_by_id_targetname='GetOrderByCreatedDatesLambda'
response = gateway_client.create_gateway_target(
gatewayIdentifier=gatewayID,
name=order_by_id_targetname,
description='Orders by created dates Lambda Target',
targetConfiguration=order_by_created_dates_lambda_target_config,
credentialProviderConfigurations=credential_config)
After executing this step, AgentCore Gateway Targets will be created like this:
For the GetOrderByIdLambda Lambda Target name:
For the GetOrderByCreatedDatesLambda Lambda Target name:
So now we have completely created and configured AgentCore Gateway, let's talk with the agent. You can copy the code for "Python with Requests", "MCP Python SDK", or "Strands MCP Client" from the "View invocation code" section of the AgentCore Gateway, see :
In case we use the notebook here, where we use Strands MCP Client, we need to repeat the execution of the following steps defined above: step 2) to set the default AWS Region and import agent_core_utils and step 4) to retrieve the existing Cognito Pool to set some variables (client_id, client_secret and cognito_discovery_url).
We'll run the agent locally and explore AgentCore Runtime in another article series. Amazon Bedrock AgentCore Runtime provides a secure, serverless and purpose-built hosting environment for deploying and running AI agents or tools. Here is an example of how to deploy Strands Agents to Amazon Bedrock AgentCore Runtime.
We need first to obtain the authentication token :
import agent_core_utils
print("Requesting the access token from Amazon Cognito authorizer...May fail for some time till the domain name propagation completes")
token_response = agent_core_utils.get_token(user_pool_id, client_id, client_secret,scopeString,REGION)
token = token_response["access_token"]
print("Token response:", token)
Now let's create an MCP client and agent. Here we create
Amazon Bedrock model using us.amazon.nova-pro-v1:0 model id and temperature=0.7. You can experiment with both settings, and Strands Agent supports all models, even which run locally like Ollama. But you need to ensure that in case you use Amazon Bedrock, the model is enabled in the Model Access.
model = BedrockModel(
model_id="us.amazon.nova-pro-v1:0",
temperature=0.7)
Then we create an MCP client and ask it to get the full list of tools (client.list_tools_sync invocation). you also need to set your AgentCore Gateway URL created before (see the variable gateway_url below). Here is the complete code:
from strands import Agent
import logging
from strands.models import BedrockModel
from strands.tools.mcp.mcp_client import MCPClient
from mcp.client.streamable_http import streamablehttp_client
import os
import requests
import json
def fetch_access_token():
return token;
def create_streamable_http_transport(mcp_url: str, access_token: str):
return streamablehttp_client(mcp_url, headers={"Authorization": f"Bearer {token}"})
def get_full_tools_list(client):
"""
List tools w/ support for pagination
"""
more_tools = True
tools = []
pagination_token = None
while more_tools:
tmp_tools = client.list_tools_sync(pagination_token=pagination_token)
tools.extend(tmp_tools)
if tmp_tools.pagination_token is None:
more_tools = False
else:
more_tools = True
pagination_token = tmp_tools.pagination_token
return tools
def run_agent(mcp_url: str, access_token: str):
model = BedrockModel(
model_id="us.amazon.nova-pro-v1:0",
temperature=0.7)
mcp_client = MCPClient(lambda: create_streamable_http_transport(mcp_url, access_token))
with mcp_client:
tools = get_full_tools_list(mcp_client)
print(f"Found the following tools: {[tool.tool_name for tool in tools]}")
gateway_url = "${YOUR_Gateway_resource_URL}"
run_agent(gateway_url, fetch_access_token())
Now let's execute it (run_agent function will be invoked).
The output is correct:
Found the following tools:
['GetOrderByCreatedDatesLambda_get_orders_by_created_dates_tool', 'GetOrderByIdLambda_get_order_by_id_tool']
Now we'll do the same as in the part 2, as we'll be talking to the same Lambda function but now directly and not through the Open API.
I previously created some orders and order items in the database. Now let's create the Strands Agent and ask questions about the existing order with id = 12345 :
Here is the relevant additional code. We pass the information about already created Bedrock model and MCP retrieved MCP tools to create the Strands Agent :
with mcp_client:
tools = get_full_tools_list(mcp_client)
print(f"Found the following tools: {[tool.tool_name for tool in tools]}")
agent = Agent(model=model, tools=tools)
agent("Give me the information about order with id 12345")
Let's execute it. The output is ... nothing, the agent hangs. Let's look into the CloudWatch : Lambda function (written in Java) was executed (so the mapping worked) but the following exception was thrown:
The reason for that is, that requestEvent.getPathParameters() is not set at all (it is null) where I expect to have the "id" as parameter :
@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent requestEvent, Context context) {
String id = requestEvent.getPathParameters().get("id");
...
Even though in this case APIGatewayProxyRequestEvent requestEvent object was constructed it is completely empty (no properties are set: no path parameters, no query string and so on). So what's happened? Our existing Lambda function was triggered by the API Gateway HTTP GET Event, but AgentCore Gateway can't know what the trigger is configured (it could be all possible triggers like SQSEvent) and can't set the parameters correctly. As I would like to completely re-use my Lambda function without any modification, I'd prefer some configuration to pass for such a case (what is the event type and where to put the tool properties: as HTTP path or query parameters, maybe in HTTP body and so on). As AgentCore is currently in preview, I'd suggest this type of the change before GA. But for now unfortunately I haven't found yet the way to re-use the Lambda function having some specific trigger as a parameter. What I found is the Lambda function input format which says that the input parameters will be passed to the Lambda function as a key-value pair in the map/dictionary. I assume that the described problem may only be relevant for the certain Lambda managed runtimes like Java and be irrelevant for others like Python, where Lambda function already receives the event object of type map or dictionary.
So, I wrote 2 new Lambda functions GetOrderById2Handler and GetOrdersByCreatedDates2Handler to use the Map in Java as Lambda input event type. The adjustments needed to be made to the initial Lambda functions are fairly small. Here is an example how it works:
@Override
public APIGatewayProxyResponseEvent handleRequest(Map<String, String> params, Context context) {
String id = params.get("id");
.....
So, please change the ARNs of the Lambda function to those mentioned above when you create AgentCore Gateway Lambda Targets in step 6).
Now let's talk to the agent again:
with mcp_client:
tools = get_full_tools_list(mcp_client)
print(f"Found the following tools: {[tool.tool_name for tool in tools]}")
agent = Agent(model=model, tools=tools)
agent("Give me the information about order with id 12345")
The correct tool getOrderById was identified and we've got the correct information about the order as an output of the Lambda function.
Let's do the same as in the part 2 and ask a different questions to the agent:
agent('Can you list orders created between 1 of August 2025 5am and 5 of August 2025 3am. '
'Please use the following date format, for example: 2025-08-02T19:50:55')
Here we'd like to get the order list created between the specified day and time range and give the agent a hint about how to format the dates. Let's execute it:
Once again the correct tool getOrdersByCreatedDates was identified and the dates were correctly formatted (which I saw in the logs) and we've got the correct information about the orders. I artificially limited the number of orders to be returned to 10 in the SQL statement (not to spend many output token), see getOrdersByCreatedDatesPreparedStatement function. And I also re-set the created timestamp for the order to some value. But the answer is completely correct.
Let's ask the agent one more question:
agent('What is the total value of all these orders together?')
The answer is completely correct :
Yes, the agent calculated the total value of all 10 orders correctly (10*350) on its own.
Let's ask a question about a not existing order:
agent("Give me the information about order with id 1234589090")
we got the correct result, as Lambda function handles this type of request without throwing any error :
One thing I noticed and is similar to part I described in the part 2 with exposing Open API as AgentCore Gateway target was the case where Lambda function ran into the error (like Nullpointer exception). In such a case the Agent didn't deliver anything and continued thinking for minutes, so I needed to stop it.
Once again there is a lot more to experiment with and to explore. I leave it to the reader. But this gives you a first impression of the functionality of the AgentCore Gateway with Lambda function as a Target.
In the end let's look into some useful CloudWatch Metrics delivered for Amazon Bedrock AgentCore Gateway :
- For the ListMemory and InitializeMcp operations:
- For the ListToolsMcp operation:
- For the CallToolMcp operation:
Conclusion
In this part of this article series, we explained how to expose the Lambda function as an AgentCore Target. We used the same Lambda functions (GetOrderByID and GetOrdersByCreatedDates) which we exposed in the part 2 via Open API through the existing Amazon API Gateway API. We also used Strands MCP Client to talk to this AgentCore Gateway endpoint.
Update from 28 August 2025
You can find our how our run this agent on Amazon Bedrock AgentCore Runtime which provides a secure, serverless and purpose-built hosting environment for deploying and running AI agents or tools in my article series about this topic.
Top comments (0)