Modern AI workflows increasingly demand real-time, secure, and scalable ways for Large Language Models (LLMs) like Claude and ChatGPT to access external APIs and services. The Model Context Protocol (MCP) is emerging as a standard for defining structured tools that LLMs can use to fetch live data, execute actions, and interact with third-party systems beyond their static training data.
However, running MCP "servers" (which expose these tools to LLMs) in production can be challenging—especially in stateless, event-driven cloud platforms like AWS Lambda. Many early MCP implementations were built for local machines or persistent servers, relying on stateful protocols like stdio or Server-Sent Events (SSE), which don't natively fit the Lambda model.
This guide will show API and backend engineers how to deploy robust, production-ready MCP servers on AWS Lambda using MCPEngine, an open-source Python implementation designed for serverless environments. We’ll cover stateless tools, state management, authentication, and best practices for integrating with LLM clients.
💡 Tip: Want to make LLMs like Cursor, Cline, or Windsurf work directly with your live API documentation? Apidog MCP Server enables you to serve OpenAPI docs—online or from local files—so AI agents can generate code, search endpoints, and stay perfectly synced with your real API contract.
Why Serverless MCP? Challenges & Benefits
Common obstacles with traditional MCP servers:
- Designed for local execution (stdio, SSE) not cloud-native
- Rely on persistent connections, hard to scale horizontally
- Complicated state management for stateless platforms
Why use AWS Lambda for MCP?
- Auto-scaling by request volume
- Pay-per-use cost savings
- No server management or patching
- Native HTTP event model fits well with stateless APIs
MCPEngine bridges these gaps:
- Native HTTP streaming and SSE support
- AWS Lambda-compatible handler generation
- Built-in authentication (OIDC/JWT)
- Efficient context and state management
MCPEngine + Lambda: Core Components
Before jumping into code, let’s break down the essential MCPEngine concepts for Lambda deployment:
- MCPEngine: The main orchestrator—registers your tools, manages context, and handles MCP request/response flows.
- @engine.tool() Decorator: Turns a Python function into an MCP tool. Function names are tool names; docstrings are tool descriptions.
- engine.getlambdahandler(): Generates a handler function that AWS Lambda can invoke, translating Lambda events to MCP requests and formatting the responses.
Example 1: Building a Stateless MCP Tool on Lambda
Let’s start simple: Deploy a stateless greeting tool to Lambda.
Prerequisites:
- Python 3.8+
- AWS account with Lambda, ECR, IAM permissions
- Docker installed
- AWS CLI configured
Step 1: Install MCPEngine
bash
pip install mcpengine[cli,lambda]Step 2: Create Your Application (app.py)
python
from mcpengine import MCPEngine, Context
engine = MCPEngine()
@engine.tool()
def personalized_greeting(name: str) -> str:
"""
Generates a friendly greeting for the specified name.
Use this tool when asked to greet someone.
"""
return f"Hello, {name}! Welcome to the serverless MCP world."
The handler variable is what Lambda will invoke.
Step 3: Dockerize the Application
Dockerfile:
dockerfile
FROM public.ecr.aws/lambda/python:3.12
Note: List mcpengine[cli,lambda] in requirements.txt or install directly.
Step 4: Build and Push to ECR
Replace , , and with your values.
bash
aws ecr create-repository --repository-name <repo-name> --region <region>
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
Step 5: Create and Configure Lambda Function
Create an execution role:
aws iam create-role --role-name lambda-mcp-role --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "lambda.amazonaws.com"},"Action": "sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name lambda-mcp-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRoleDeploy Lambda:
aws lambda create-function \
--function-name mcp-greeter-function \
--package-type Image \
--code ImageUri=<account-id>.dkr.ecr.<region>.amazonaws.com/<repo-name>:latest \
--role arn:aws:iam::<account-id>:role/lambda-mcp-role \
--timeout 30 \
--memory-size 512 \
--region <region>Step 6: Expose Lambda via Function URL
bash
aws lambda create-function-url-config \
--function-name mcp-greeter-function \
--auth-type NONE \
--region <region>
Your stateless MCP server is now live via the returned Function URL!
Example 2: Managing State with MCPEngine Lifespan
Many real-world tools need to maintain connections to databases or other resources. While Lambda is stateless, MCPEngine’s lifespan argument provides an async context manager to handle this.
Scenario: Log and retrieve events in a Postgres database (e.g., AWS RDS).
Step 1: Update app.py for Stateful Operation
python
import os
import psycopg2
from contextlib import asynccontextmanager
from mcpengine import MCPEngine, Context
DBHOST = os.environ.get("DBHOST")
DBUSER = os.environ.get("DBUSER")
DBPASS = os.environ.get("DBPASS")
DBNAME = os.environ.get("DBNAME")
@asynccontextmanager
async def dbconnectionmanager():
conn = None
try:
print("Establishing DB connection...")
conn = psycopg2.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASS,
dbname=DB_NAME
)
with conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS events (
id SERIAL PRIMARY KEY,
event_name TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT now()
);
""")
conn.commit()
print("DB connection ready.")
yield {"db_conn": conn}
finally:
if conn:
print("Closing DB connection.")
conn.close()
engine = MCPEngine(lifespan=dbconnectionmanager)
@engine.tool()
def logevent(eventname: str, ctx: Context) -> str:
try:
with ctx.db_conn.cursor() as cur:
cur.execute("INSERT INTO events (eventname) VALUES (%s)", (eventname,))
ctx.db_conn.commit()
return f"Event '{event_name}' logged successfully."
except Exception as e:
ctx.db_conn.rollback()
return f"Error logging event: {e}"
@engine.tool()
def getlatestevents(limit: int = 5, ctx: Context) -> list[str]:
try:
with ctx.db_conn.cursor() as cur:
cur.execute("SELECT event_name, timestamp FROM events ORDER BY timestamp DESC LIMIT %s", (limit,))
events = [f"[{row[1].strftime('%Y-%m-%d %H:%M:%S')}] {row[0]}" for row in cur.fetchall()]
return events
except Exception as e:
return [f"Error retrieving events: {e}"]
Deployment Considerations:
- Ensure your Lambda has VPC access to RDS (configure Security Groups and Subnets).
- Pass DB credentials via environment variables.
- Install
psycopg2-binaryin your Docker image. - Update Lambda environment and VPC settings as needed.
Example 3: Securing MCP Tools with Authentication
Production APIs should be protected. MCPEngine supports OpenID Connect (OIDC) providers such as Google, AWS Cognito, and Auth0.
Step 1: Configure Your OIDC Provider
Set up an OAuth client with your provider (e.g., Google Cloud Console) and obtain the Client ID.
Step 2: Update app.py for Authenticated Endpoints
python
import os
from mcpengine import MCPEngine, Context, GoogleIdpConfig
... dbconnectionmanager ...
google_config = GoogleIdpConfig(
clientid=os.environ.get("GOOGLECLIENT_ID")
)
engine = MCPEngine(
lifespan=dbconnectionmanager,
idpconfig=googleconfig
)
@engine.auth()
@engine.tool()
def logevent(eventname: str, ctx: Context) -> str:
user_email = ctx.user.email if ctx.user else "unknown"
print(f"Authenticated user: {user_email}")
try:
# ... database logic ...
return f"Event '{eventname}' logged successfully by {useremail}."
except Exception as e:
return f"Error logging event for {user_email}: {e}"
@engine.tool()
def getlatestevents(limit: int = 5, ctx: Context) -> list[str]:
# ... logic unchanged ...
Key Points:
- Use
@engine.auth()above any@engine.tool()that needs protection. - Authenticated user info is available via
ctx.user. - Pass
GOOGLECLIENTID(or similar) as a Lambda environment variable.
Rebuild your image and redeploy as before.
Connecting LLM Clients to Your Lambda MCP Server
Once your MCP server is live, use mcpengine proxy to bridge LLMs like Claude:
bash
mcpengine proxy <service-name> <lambda-function-url> --mode http --claudeFor authenticated endpoints:
bash
mcpengine proxy <service-name> <lambda-function-url> \
--mode http \
--claude \
--client-id <google-client-id> \
--client-secret <google-client-secret>This proxy lets the LLM discover and invoke your MCP tools securely and seamlessly.
Apidog & MCP: Streamlining API-Driven AI Development
As LLM-augmented development becomes mainstream, connecting AI agents directly to live API documentation and contracts is essential for reliability and productivity.
How Apidog MCP Server Helps:
- Allows LLM tools (like Cursor) to read your API docs directly—either from published endpoints or local OpenAPI files.
- Ensures that code generation, endpoint search, and code modifications always align with your actual API contract, reducing “hallucinations” and misalignment.
- Integrates with your existing API design workflow, letting your teams move faster and with greater confidence.
Learn more: Apidog MCP Server Documentation.
Conclusion
Deploying MCP servers on AWS Lambda with MCPEngine offers API engineers a scalable, secure, and truly serverless way to expose tools for LLMs. Unlike traditional MCP setups, MCPEngine’s Lambda-ready design, streaming HTTP support, and built-in authentication make it a practical foundation for production use—whether you’re building stateless helpers or complex, stateful APIs.
For engineering teams seeking even greater alignment between their API designs and AI-driven development, Apidog’s MCP Server bridges the gap—making it easy for LLMs to access, understand, and reliably work with your actual API documentation.



