Introduction
In the 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 this part of the article series, we use Amazon Bedrock AgentCore Gateway to convert the existing Amazon API Gateway REST API into MCP compatible tools and make it available to agents through Gateway endpoint. We also use Strands MCP Client to talk to this AgentCore Gateway endpoint.
Transform existing Amazon API Gateway REST API into MCP tools using Bedrock AgentCore Gateway
For the demonstration purpose, we re-use our sample application anf 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. But you can use any Open API spec to transform existing REST API into MCP tools using Bedrock AgentCore Gateway.
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 (use existing API Gateway REST API instead of NASA's Open APIs). I have published the final version of this playbook in my here.
Here is the architecture of how to transform OpenAPI apis 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 such as OpenAPI specifications for REST APIs. The OpenAPI specifications are then parsed by Amazon Bedrock AgentCore for creating the Gateway.
- 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 OpenAPI targets that define how the gateway routes requests to specific tools. All the APIs that are part of OpenAPI file will become an MCP-compatible tool, and will be made available through your Gateway endpoint URL. Configure outbound authorization for each OpenAPI Gateway 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 :
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 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 for authentication, namely 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 DemoAmazonAPIGatewayOrdersRestAPI 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='DemoAmazonAPIGatewayOrdersRestAPI',
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 OpenAPI target'
)
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) In the next step we create an API Key Credential Provider for the authentication of our Amazon API Gateway. As out API is secured with API Key, see the section below in AWS SAM template :
MyApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn:
- MyApiStage
Properties:
Name: "AWSLambdaJava21WithDSQLAndPgJDBCApiKey"
Enabled: true
GenerateDistinctId: false
Value: a6ZbcDefQW12BN56WEDS7
.....
we set the same value of the API Key (for my API it's a6ZbcDefQW12BN56WEDS7 for your it might be different) when creating API Key Credential Provider :
import boto3
from pprint import pprint
from botocore.config import Config
acps = boto3.client(service_name="bedrock-agentcore-control")
response=acps.create_api_key_credential_provider(
name="x-api-key",
apiKey="a6ZbcDefQW12BN56WEDS7",
)
pprint(response)
credentialProviderARN = response['credentialProviderArn']
pprint(f"Egress Credentials provider ARN, {credentialProviderARN}")
After executing this step, AWS Secrets Manager Secret will be created like this:
7) The next step is to create an AgentCore Gateway Target (in our case REST API target). For this we need to configure 2 pieces: Open API configuration and API Key credential configuration.
For our REST API we need an Open API spec. For the API from our sample application I provided Open-API-Order-API-prod The only thing you need to change is server URL
"servers" : [ {
"url" : "${YOUR_API_GATEWAY_URL}/prod"
} ],
and provide your own URL. For APIs deployed on Amazon API Gateway the URL looks like this https://XXX.execute-api.${AWS-REGION}.amazonaws.com/${STAGE}.
In the Open-API-Order-API-prod Open API specification I only exposed to paths: /orders/{id} and /ordersByCreatedDates/{startDate}/{endDate}, even though API Gateway provides access to more APIs, but I'd like to expose only those 2 via MCP. I uploaded the specification and provided URL to the S3 bucket, so if you do this, please provide your own S3 URL or you can provide Open API specification as json inline.
The next configuration piece is "API Key" credential configuration which we use in this example (see credentialProviderType : "API_KEY"). There is also "OAuth client" authentication. We re-use an already created Credential Provider (see the parameter credentialProviderARN). In order to authenticate Amazon API Gateway with API Key we need to pass its value as HTTP header with the name x-api-key. This is exactly what we configured with credentialParameterName: "x-api-key" and credentialLocation:"HEADER". Other APIs may require passing the API Key as a query parameter, for this use credentialLocation: QUERY_PARAMETER.
After having configured Open API and API Key credential we pass those configuration to create AgentCore Gateway Target along with the AgentCore Gateway name (DemoOpenAPITargetS3OrderAPI), gateway identifier (which we've already got when creating AgentCore Gateway) and some other parameters.
Here is the complete code to execute:
order_api_openapi_config = {
"mcp": {
"openApiSchema": {
"s3": {
"uri": "s3://{YOUR_BUCKET_URI_FOR_UPLOADING_ORDER_ID_OPEN_API}/Open-API-Order-API-prod.json"
}
}
}
}
# API Key credentials provider configuration
api_key_credential_config = [
{
"credentialProviderType" : "API_KEY",
"credentialProvider": {
"apiKeyCredentialProvider": {
"credentialParameterName": "x-api-key", # Replace this with the name of the api key name expected by the respective API provider. For passing token in the header, use "Authorization"
"providerArn": credentialProviderARN,
"credentialLocation":"HEADER", # Location of api key. Possible values are "HEADER" and "QUERY_PARAMETER".
#"credentialPrefix": " " # Prefix for the token. Valid values are "Basic". Applies only for tokens.
}
}
}
]
targetname='DemoOpenAPITargetS3OrderAPI'
response = gateway_client.create_gateway_target(
gatewayIdentifier=gatewayID,
name=targetname,
description='OpenAPI Order API Target with S3Uri',
targetConfiguration=order_api_openapi_config,
credentialProviderConfigurations=api_key_credential_config)
After executing this step, AgentCore Gateway Target will be created like this:
For each AgentCore Gateway you can create multiple targets.
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 Cognito M2M authentication token.
For more information, please read the following resources:
- Configuring machine to machine Authentication with Amazon Cognito and Amazon API Gateway – Part 1
- Configuring machine to machine Authentication with Amazon Cognito and Amazon API Gateway – Part 2
- Consider the pricing table for Amazon Cognito add-ons, especially Cognito M2M authorization
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 comes the funniest part : creating 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: ['DemoOpenAPITargetS3OrderAPI___getOrderById', 'DemoOpenAPITargetS3OrderAPI___getOrdersByCreatedDates']
We exposed those API in our Open-API-Order-API-prod as MCP tools.
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 correct tool getOrderById was identified and we've got the correct information about the order via the exposed API Gateway API.
Let's 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.
One thing I noticed was the case, where I asked the information about the not existing order where API Gateway which also returned HTTP status code 404 which is correctly described in the reponses part of Open-API-Order-API-prod. See for example GetOrderByIdHandler Lambda implementation. In such a case the Agent didn't deliver anything and continued thinking for minutes, so I needed to stop it. I'll need to dig deeper into this, but the "quick fix" was to change HTTP status code from NOT_FOUND (404) to OK (200) in the Lambda function like this:
if (optionalOrder.isEmpty()) {
return new APIGatewayProxyResponseEvent().withStatusCode(HttpStatusCode.OK)
.withBody("order with id = " + id + " not found");
}
Then by asking a question about a not existing order:
agent("Give me the information about order with id 123456789 ")
we got the correct result :
Amazing results! Here 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 Open API specification (in our case an existing Amazon API Gateway API) 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 the article 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 the next part of this article series, we'll explore using the Lambda function as an Gateway AgentCore Target.
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)