Introduction
In 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 the 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 demonstration purposes, we reuse 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. But you can use any Open API specification to transform an existing REST API into MCP tools using Bedrock AgentCore Gateway.
We reuse the Python Jupyter Notebook provided in the official AWS GitHub repository with the amazon-bedrock-agentcore-samples but adapt it to our needs (use the 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 the Open API 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 also need to check out agent_core_utils.py and store it in the same directory as your notebook. I copied this file from here to later adapt 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 reused) by using the helper function get_or_create_user_pool function in agentcore_utils.py. Then we have to create a Coginto Resource Server for the user pool (see the helper function get_or_create_resource_server in agentcore_utils.py). When we create or retrieve the Cognito Machine-to-machine (M2M) client id and client_secret, which we later use for authorization (for this, we need to create a Cognito User Pool Client, see the helper function get_or_create_m2m_client in agentcore_utils.py), namely to create an authorization Bearer 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 the AgentCore Gateway looks in AWS:
6) In the next step, we create an API Key Credential Provider for the authentication of our Amazon API Gateway. As our API is secured with an 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 API, it might be different) when creating the 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, Outbound Auth AgentCore Identity with the "API Key" was created like this:
If we click on the "x-api-key" link, we see the detailed view of this identity:
In case of the "API Key" Identity (there is also an option to create it with the "OAuth Client"), the identity (key/value pair) is stored in the AWS Secrets Manager, where we can jump by clicking on the "Go to Secrets Manager" link:
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 the 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 2 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 the URL to the S3 bucket, so if you do this, please provide your own S3 URL, or you can provide the Open API specification as an inlined JSON.
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 reuse an already created Credential Provider (see the parameter credentialProviderARN). 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 configurations 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 to obtain the Cognito M2M authorization Bearer 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 the MCP client and agent. Here we create an 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 those that 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 passed the information about the already created Bedrock model, and MCP retrieved the 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 question 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 tokens), see getOrdersByCreatedDatesPreparedStatement function. And I also reset 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 the 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 nonexistent 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 the 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 a Gateway AgentCore Target.
Update from 28 August 2025
Find out how to 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 (1)
Great set of articles. What I'm struggling with on AgentCore Gateway is auto discovery. I am unable to have any of my mcp servers discoverable by url only (i.e. can't add them to Claude or ChatGPT). I'd be very appreciative if anyone has a working example that successfully can be added by URL to Claude or ChatGPT.