A reusable AWS SAM template and reference implementation for deploying a Model Context Protocol (MCP) server on AWS Lambda and API Gateway with token-based authorization. Use this generic blueprint to build and extend your own MCP tools, integrating custom business logic, data sources, or external APIs as needed.
- AWS SAM / CloudFormation infrastructure (API Gateway, Lambda, optional DynamoDB table, session store)
- pluggable token authorizer (simple bearer token)
- Session persistence table (TTL-based)
- Example tools (echo, add, utc_timestamp, optional DynamoDB sample)
- Minimal dependency footprint
mcp-lambda-server/
├── template.yml # SAM infrastructure definition
├── README.md # This guide
├── authorizer/
│ ├── app.py # Custom Lambda token authorizer
├── server/
│ ├── app.py # MCP server + example tools
│ └── requirements.txt # Python dependencies
└── examples/
├── simple_client.py # Minimal MCP call example
└── strands_client.py # Interactive Strands Agent chat example
Tool | Purpose |
---|---|
echo(text) | Returns the same text (connectivity sanity check) |
add(a, b) | Adds two numbers and returns a structured JSON result |
utc_timestamp() | Returns current UTC timestamp & ISO8601 string |
list_items(limit) | (Optional) Lists items from a demo DynamoDB table if configured |
health() | Basic server/config status |
You can remove or extend these freely.
┌─────────────┐ HTTPS (Bearer token) ┌──────────────┐
│ MCP Client │ ───────────────────────▶ │ API Gateway │
└─────────────┘ │ (Authorizer)│
└──────┬───────┘
│Invoke
┌───────▼────────┐
│ Lambda (MCP) │
└───────┬────────┘
│ (optional)
┌───────▼────────┐
│ DynamoDB Table │
└────────────────┘
- Simple bearer token (passed as
Authorization: Bearer <token>
) validated by a Lambda authorizer. - Token stored as a NoEcho CloudFormation parameter (not logged in plaintext).
- Session table uses TTL for automatic cleanup.
Name | Description | Default | Required |
---|---|---|---|
McpAuthToken | Bearer token clients must send | your-secure-token-here | Yes (change in prod) |
CreateDemoTable | Create a sample DynamoDB table for list_items tool | false | No |
DemoTableName | Name of existing or new table (if CreateDemoTable=true) | McpDemoItems | Conditionally |
# From repo root (where template.yml resides)
cd mcp-lambda-server
sam build
sam local invoke McpServerFunction --event events/sample.json
You can craft a minimal invoke event (POST body) — see the awslabs.mcp_lambda_handler
docs for request schema. Usually you'll deploy and call via HTTP instead.
cd mcp-lambda-server
sam build
sam deploy \
--stack-name generic-mcp-server \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
McpAuthToken="REPLACE_WITH_STRONG_TOKEN" \
CreateDemoTable="true" \
DemoTableName="McpDemoItems"
After deployment note the output McpApiUrl
, e.g.:
https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/mcp
Example entry:
{
"servers": {
"generic-mcp": {
"url": "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/mcp",
"requestInit": {
"headers": { "Authorization": "Bearer REPLACE_WITH_STRONG_TOKEN" }
}
}
}
}
In server/app.py
:
@mcp_server.tool()
def my_new_tool(arg1: str) -> str:
return json.dumps({"tool": "my_new_tool", "arg1": arg1})
Return plain strings or JSON-serialized strings. Keep responses concise & structured.
If CreateDemoTable=true
, a PAY_PER_REQUEST table with partition key pk
is created. You can manually insert sample items:
aws dynamodb put-item \
--table-name McpDemoItems \
--item '{"pk":{"S":"item#1"},"message":{"S":"Hello"}}'
Then call list_items(limit=25)
via MCP client.
from mcp.client.streamable_http import streamablehttp_client
from strands.tools.mcp import MCPClient
API_URL = "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/mcp"
TOKEN = "REPLACE_WITH_STRONG_TOKEN"
client = MCPClient(lambda: streamablehttp_client(
url=API_URL,
headers={"Authorization": f"Bearer {TOKEN}"}
))
with client:
tools = client.list_tools_sync()
print("Tools:", [t.name for t in tools])
res = client.call_tool_sync("utc_timestamp", {})
print(res.content[0].text)
(See examples/simple_client.py
)
from mcp.client.streamable_http import streamablehttp_client
from strands.tools.mcp import MCPClient
import json
API_URL = "https://your-api.execute-api.us-east-1.amazonaws.com/Prod/mcp"
TOKEN = "YOUR_TOKEN"
client = MCPClient(lambda: streamablehttp_client(
url=API_URL,
headers={"Authorization": f"Bearer {TOKEN}"}
))
with client:
for t in client.list_tools_sync():
print("Tool:", t.name, t.description)
# Call echo tool
resp = client.call_tool_sync("echo", {"text": "hello"})
print("echo →", resp.content[0].text)
(See examples/strands_client.py
) Uses a Bedrock model + discovered MCP tools.
Key steps:
- Build MCP client with bearer auth
- List tools
- Construct Agent(model=..., tools=tools)
- Read user input loop and invoke
agent(user_text)
Environment requirements:
strands
(and its Bedrock integration) & dependencies- Correct Bedrock model ID with region access
File | Purpose |
---|---|
examples/simple_client.py | Minimal list + call tool demonstration |
examples/strands_client.py | Interactive chat loop with Strands Agent |
Symptom | Check |
---|---|
403 Unauthorized | Ensure header uses correct bearer token & matches deployed parameter |
500 Error (tool) | CloudWatch logs for McpServerFunction for stack traces |
Timeout | Increase Lambda timeout in Globals.Function.Timeout |
Empty tool list | Confirm correct URL path /mcp and POST method |
Fetch logs quickly:
sam logs -n McpServerFunction --stack-name generic-mcp-server --tail
- Update code →
sam build && sam deploy
- Rotate token → deploy new parameter, update all clients
- Add tools → modify
server/app.py
and redeploy
Use freely in your org; validate security posture before production.
Feel free to tailor this template further for your specific domain logic (Jira, internal APIs, etc.).