Build MCP tools for Amazon Bedrock AgentCore Gateway without the boilerplate.
Normally, exposing a Lambda function as an MCP gateway tool means wiring up tool schema JSON, uploading it to S3, registering a gateway target, handling the AgentCore context format, and routing multi-tool invocations — all before you write a single line of business logic.
This template eliminates that overhead. You write a decorated Python function, and make deploy handles the rest: schema generation, S3 upload, gateway registration, and Lambda deployment in one command.
This project uses the same @tool decorator from Strands Agents. If you already have tools running in-process on a Strands agent in AgentCore Runtime, you can copy them directly into this project as-is and deploy them as an out-of-process Lambda behind AgentCore Gateway, exposed via MCP - same decorator, same docstring, same code:
from strands import tool
@tool
def get_weather(location: str) -> dict:
"""Get the current weather for a location.
Args:
location: City name or zip code
"""
return {"location": location, "temperature": "72F", "condition": "sunny"}No schema files to write. The tool name, description, parameter types, and descriptions are all inferred from your code and docstring.
These commands setup a python virtual environment and install dependencies. This is optional if you want to do it another way.
make init
make installmake start# First time: creates Lambda + registers as gateway target
make create function=my-tools gateway_id=gw-abc123
# Test the deployed function
make invoke function=my-tools tool=get_weather
# After code changes: redeploys + updates gateway target
make deploy function=my-tools gateway_id=gw-abc123make delete function=my-tools gateway_id=gw-abc123Edit tools.py and add a decorated function:
@tool
def search_products(query: str, limit: int = 10) -> dict:
"""Search the product catalog.
Args:
query: Search query
limit: Max results to return
"""
results = do_search(query, limit)
return {"results": results}That's it. The tool name, description, parameter types, required/optional, and parameter descriptions are all inferred from the function. Run make deploy to push the changes.
tools.py (define tools here — same @tool decorator as Strands)
|
├──> lambda_function.py (routes tool calls at runtime)
└──> registry.py (collects @tool functions)
|
deploy.py (boto3 CLI: build, deploy, schema, gateway registration)
|
└──> tools.json → S3 → AgentCore Gateway
- One Lambda handles multiple tools, routed by tool name from the gateway context
- Tool schemas are auto-generated from your function signatures and docstrings
deploy.pyautomates the full lifecycle (role, image, Lambda, schema, gateway target); the Makefile is a thin set of aliases over it
The Makefile is a thin wrapper over deploy.py (a boto3 CLI). Use either.
init create a python virtualenv
install install dependencies
start run the tools locally with a fake gateway context
start-container run the packaged image locally
schema generate tools.json from the @tool functions
create create lambda + register gateway target - make create function=X gateway_id=G
deploy update code + gateway target - make deploy function=X gateway_id=G
delete delete lambda + role + gateway target - make delete function=X gateway_id=G
invoke invoke a tool on the lambda - make invoke function=X [tool=get_weather]
Equivalent direct calls: python deploy.py create --function X --gateway-id G, etc.
Run python deploy.py --help for all options.
| Parameter | Default | Description |
|---|---|---|
arch |
arm64 |
arm64 or x86_64 |
function |
(required) | Lambda function name |
gateway_id |
(required for create/deploy) | AgentCore Gateway ID |
target |
(function name) | Gateway target name |
role |
<function>-role |
Execution role name |
packaging |
container |
container or zip |
tool |
get_weather |
Tool name for make invoke |
make create / make deploy:
- Ensure a per-function IAM role (
<function>-role) with basic execution + yourpolicy.json(if present) - Build & push the container image (or build the zip) and create/update the Lambda
- Apply
env.json(if present) to the Lambda, and grant the gateway invoke permission - Generate
tools.json, upload it tos3://agentcore-tools-{account}-{region}/{function}/tools.json - Register or update the gateway target (waiting for the Lambda to be ready)
Two optional files let a tool declare what it needs. Both support ${VAR}
expansion from the current shell environment, so dynamic values (ARNs resolved
by direnv/.envrc, CI variables, etc.) flow straight through. Deploy fails
loudly if a referenced variable is unset, so you never ship a literal ${...}.
env.json — Lambda environment variables (AWS format). Applied on every
create/deploy. See env.json.example:
{
"Variables": {
"LOG_LEVEL": "INFO",
"TABLE_ARN": "${MY_TABLE_ARN}"
}
}policy.json — an IAM policy document attached inline to the function's
role, so each tool grants exactly the AWS access it needs. See
policy.json.example:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query"],
"Resource": "${MY_TABLE_ARN}" }
]
}Delete either file to drop the corresponding configuration on the next deploy
(the inline policy is removed automatically when policy.json is absent).
| Requirement | Notes |
|---|---|
AWS_REGION |
Region for deployment (defaults to us-east-1) |
| AWS credentials | Via environment, profile, or IAM role |
| Docker | Required for the default container packaging |
make startRuns main.py which invokes each tool with test parameters using a fake AgentCore context.
make start-container# In another terminal
curl -X POST "http://localhost:8080/2015-03-31/functions/function/invocations" \
-d '{"location": "Seattle, WA"}'Note: Container invocation doesn't pass client context, so the handler will raise an error about missing context. For full local testing with routing, use
make start.
- Gateway expects raw JSON responses (not
{"statusCode": 200, ...}envelope)