使用 MCPEngine 在 AWS Lambda 上部署 MCP 服务器
想要了解更多关于 Featureform 的信息吗? 今天就安排一个演示!
使用 MCPEngine 在 AWS Lambda 上部署 MCP 服务器
2025 年 4 月 23 日
分钟阅读
模型上下文协议 (MCP) 正迅速成为使 LLM 调用外部工具的标准。它围绕着清晰的、声明式的工具定义构建——但目前大多数实现都达不到生产就绪的要求。 例如,在 Anthropic 仓库中的每个官方 MCP 服务器都在本地运行并通过 stdio 进行通信。 即使是少数几个支持 HTTP 的服务器也依赖于服务器发送事件 (SSE) 进行流式传输。 这引入了有状态行为,需要持久的 TCP 连接,使重试变得复杂,并最终使其与诸如 AWS Lambda 之类的无状态环境不兼容。 我们已经写了更多关于这些限制的文章,以及我们如何使用 MCPEngine 解决这些限制的文章。
AWS Lambda 提供了即时可扩展性、无需服务器管理以及高效的事件驱动执行。 我们在 MCPEngine 中构建了对它的原生支持,以便 MCP 工具可以在无服务器环境中干净可靠地运行。
MCPEngine 是 MCP 的一个开源实现,除了 SSE 之外,还支持可流式传输的 HTTP,使其与 Lambda 兼容。 它还包括对身份验证、打包和其他功能的一流支持,以构建和部署生产级的 MCP 服务器。
本文将逐步介绍构建三个越来越真实的示例:
- 具有单个工具的无状态 MCP 服务器
- 使用 RDS 和上下文处理程序的有状态版本
- 使用 OIDC(Google 或 Cognito)进行身份验证的版本
所有这三个都在 Lambda 中完全运行,不需要自定义代理,并且符合 MCP 规范。
1. 构建和部署无状态的天气 MCP API
您可以在 GitHub 上 找到完整的项目。
1.1 定义 MCP 服务器
我们将从一个名为 get_weather 的工具开始。 它接收一个城市名称并返回一个预定义的字符串响应。 没有状态或外部 API 调用——只是足以通过实时 LLM 验证端到端行为。
安装 Python SDK:
pip install mcpengine[cli,lambda]
创建一个名为 app.py 的文件:
from mcpengine import MCPEngine
---
engine = MCPEngine()
@engine.tool()
defget_weather(city: str) -> str:
"""返回给定城市当前的天气。
"""
returnf"The weather in {city} is sunny and 72°F."
handler = engine.get_lambda_handler()
这个代码做了什么:
@engine.tool
将该函数注册到 MCP manifest。 函数名称 (get_weather
) 成为暴露给 LLM 的工具名称。- docstring 在 manifest 中公开,并在工具选择期间显示给 LLM。
- handler 是 AWS Lambda 兼容的入口点。 无需胶水代码——Lambda 配置为查找全局命名处理程序,MCPEngine 处理请求生命周期。
1.2 部署到 Lambda
您可以手动部署它,也可以使用 Terraform 来自动化设置。
选项 1:Terraform
如果您想跳过大部分样板代码,我们提供了 Terraform 脚本,可以:
- 创建一个 ECR 仓库来托管镜像
- 配置 Lambda 函数和 IAM 角色
- 通过 Function URL 公开它
您可以通过调用以下命令在目录中运行它:
terraform apply
从 Terraform 输出中获取 ECR 仓库 url 和 Lambda 函数名称:
export REPOSITORY_URL=$(terraform output -raw repository_url)
---
export FUNCTION_NAME=$(terraform output -raw lambda_name)
然后构建、标记和推送镜像:
docker build --platform=linux/amd64 --provenance=false -t mcp-lambda:latest .
---
docker tag mcp-lambda ${REPOSITORY_URL}:latest
docker push ${REPOSITORY_URL}:latest
最后,我们将使用这个新镜像更新 Lambda:
aws lambda update-function-code \
---
--function-name ${FUNCTION_NAME} \
--image-uri ${REPOSITORY_URL}:latest
应用程序将开始运行。 部署完成后,您可以使用以下命令将其删除:
terraform destroy
选项 2:从头开始
如果您更喜欢手动部署:
步骤 1:Dockerize 服务器
FROM public.ecr.aws/lambda/python:3.12# 在容器中设置工作目录WORKDIR /var/task# 复制应用程序代码COPY . .# 安装依赖项RUN pip install --system --no-cache-dir .# 公开服务器的端口EXPOSE8000# 运行 Web 服务器的命令CMD ["weather.server.handler"]
然后:
docker build --platform=linux/amd64 --provenance=false -t mcp-lambda .
步骤 2:推送到 ECR
docker tag mcp-lambda:latest <your-ecr-url>/mcp-lambda:latest
---
docker push <your-ecr-url>/mcp-lambda:latest
步骤 3:部署到 Lambda
aws lambda create-function \
---
--function-name mcp-lambda \
--package-type Image \
--code ImageUri=<your-ecr-url>/mcp-lambda:latest \
--role arn:aws:iam::<account-id>:role/<lambda-role>
步骤 4:为 Lambda 添加权限
aws iam create-role \
---
--role-name lambda-container-execution \
--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-container-execution \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
步骤 5:启用 Function URL,并添加允许所有权限来调用它:
aws lambda create-function-url-config \
---
--function-name mcp-lambda \
--auth-type NONE
aws lambda add-permission \
--function-name mcp-lambda \
--function-url-auth-type NONE \
--action lambda:InvokeFunctionUrl \
--statement-id PublicInvoke \
--principal '*'
1.3 通过 Claude 连接
部署完成后,您可以使用任何兼容的 LLM 连接到服务器。 例如,要从 Claude 连接:
mcpengine proxy <service-name> <your-lambda-function-url> --mode http --claude
打开 Claude,您的工具应该会出现在聊天气泡的底部。 当您问类似“东京的天气如何?”的问题时,Claude 会:
- 根据其 manifest 描述选择工具
- 提示用户授权请求
- 使用工具名称和参数调用 endpoint
就这样。 您现在拥有一个完全部署的、Lambda 托管的 MCP 服务器,通过 HTTP 响应真实的 LLM 调用。
2. 构建和部署类似 Slack 的有状态 MCP API
您可以在 GitHub 上 找到完整的项目。
无状态工具对于演示很有用,但大多数实际应用程序都需要持久化数据。 在本节中,我们将扩展最小的 MCP 服务器以包含状态。 具体来说,我们将构建一个基本的类似 Slack 的留言板,用于存储和检索关系数据库中的消息。
此版本使用:
- Amazon RDS 上的 Postgres 用于持久存储
- 一个上下文处理程序来管理连接池
- 两个工具:一个用于发布消息,一个用于列出消息
目标不是构建完整的聊天系统,只是展示如何在不放弃 Lambda 等无状态基础设施的情况下向 MCP 服务器添加状态。
2.1 定义数据模型
我们将每条消息存储为单个表中的一行。 为简单起见,所有消息都进入相同的全局时间线。
Schema 看起来像:
CREATETABLE messages (
---
id SERIAL PRIMARY KEY,
username TEXTNOTNULL,
text TEXTNOTNULL,
timestampTIMESTAMPDEFAULT now()
);
post_message()
将插入到此表中,get_messages()
将返回最近的条目。
2.2 使用上下文处理程序管理连接
您不应在工具函数内部打开数据库连接。 相反,MCPEngine 提供了一个上下文系统:您定义一个在服务器启动之前运行的设置函数,并且 MCPEngine 将结果作为 ctx
提供。
在这种情况下,上下文将:
- 打开到 RDS 的连接池
- 将其附加到请求上下文 (
ctx.db
) - 在工具完成后清理
这使您的工具专注于业务逻辑,而不是生命周期管理。
2.3 实现工具和上下文
假设 ctx.db
是有效的 psycopg2 连接,工具如下所示:
@engine.tool()
---
defpost_message(ctx: Context, username: str, text: str) -> str:
"""将消息发布到全局时间线。"""
with ctx.db.cursor() as cur:
cur.execute("INSERT INTO messages (username, text) VALUES (%s, %s)", (username, text))
ctx.db.commit()
return"消息已发布。"
@engine.tool()
defget_messages(ctx: Context) -> list[str]:
"""获取最近的消息。"""
with ctx.db.cursor() as cur:
cur.execute("SELECT username, text FROM messages ORDER BY timestamp DESC LIMIT 10")
return [f"{row[0]}: {row[1]}"for row in cur.fetchall()]
添加上下文处理程序:
@asynccontextmanager
---
defapp_lifespan():
import psycopg2
conn = psycopg2.connect(
host=os.environ["DB_HOST"],
user=os.environ["DB_USER"],
password=os.environ["DB_PASS"],
dbname=os.environ["DB_NAME"],
)
try:
yield {"db": conn}
finally:
conn.close()
然后,您更新 MCPEngine 的构造函数,以将此 lifespan 上下文构建器传递给它。
engine = MCPEngine(
---
lifespan=app_lifespan,
)
MCPEngine 将运行生命周期以在服务器启动时获取连接池,并将其作为上下文附加到每个传入的请求。 此外,当服务器停止并关闭时,它将运行清理(yield 语句之后的所有内容)。
2.4 部署服务器
我们建议在此处使用 Terraform,因为此版本涉及配置 RDS 实例、IAM 角色和安全组。 如果您更喜欢手动部署,可以将 Terraform 脚本用作参考。
terraform apply
这将:
- 创建数据库
- 使用正确的环境变量设置 Lambda 函数
从 Terraform 输出中获取 ECR 仓库 url 和 Lambda 函数名称:
export REPOSITORY_URL=$(terraform output -raw repository_url)
---
export FUNCTION_NAME=$(terraform output -raw lambda_name)
然后构建、标记和推送镜像:
docker build --platform=linux/amd64 --provenance=false -t mcp-lambda:latest .
---
docker tag mcp-lambda ${REPOSITORY_URL}:latest
docker push ${REPOSITORY_URL}:latest
最后,我们将使用这个新镜像更新 Lambda:
aws lambda update-function-code \
---
--function-name ${FUNCTION_NAME} \
--image-uri ${REPOSITORY_URL}:latest
完成后,您可以使用以下命令删除资源:
terraform destroy
2.5 连接和测试
部署完成后,再次使用以下命令连接 Claude:
mcpengine proxy <service-name> <your-lambda-function-url> --claude --mode http
打开 Claude,您现在应该看到两个工具:post_message
和 get_messages
。
您可以提示 Claude 发送或检索消息。 您还可以从另一个 Claude 窗口连接,使用相同的工具,并确认消息是共享的——即使跨用户和冷启动也是如此。
3. 使用 Google SSO 添加身份验证
您可以在 GitHub 上 找到完整的项目。
到目前为止,我们构建的工具可以工作,但是它们是开放的。 任何人都可以调用它们,模拟任何用户名,并且没有验证身份的机制。 这可能适合测试,但在任何类似于生产系统的东西中都是不可接受的。
MCPEngine 支持使用标准 OpenID Connect (OIDC) 的基于令牌的身份验证。 这意味着您可以与任何颁发 JWT 的身份提供商集成,包括 Google、AWS Cognito、Auth0 或您的内部身份验证堆栈。
在本节中,我们将使用 Google 作为身份提供商来保护我们现有的工具。 我们将:
- 注册一个 Google OAuth 应用程序
- 修改我们的 MCP 服务器以要求有效的令牌
- 通过 Claude(或任何其他客户端)传递令牌
- 从请求上下文中读取经过身份验证的用户
3.1 创建 Google OAuth 应用程序
首先,在 Google Cloud 中设置一个 OAuth 客户端:
- 转到 Google Cloud Console
- 选择或创建一个项目
- 导航到 APIs & Services > Credentials
- 单击 Create Credentials > OAuth client ID
- 将应用程序类型设置为 Web application
- 添加一个授权的重定向 URI(您可以使用 http://localhost 进行本地测试)
- 保存客户端并记下 Client ID
这就是服务器端令牌验证所需的全部内容——Client ID 和标准的 Google issuer (https://accounts.google.com)。
3.2 更新 MCPEngine 以进行身份验证
要启用身份验证,您需要:
- 构造引擎时设置
idp_config
:
from mcpengine import MCPEngine, GoogleIdpConfig
---
engine = MCPEngine(
lifespan=app_lifespan,
idp_config=GoogleIdpConfig(),
)
这告诉 MCPEngine 使用 Google 的公共 JWKS endpoint 来验证传入的令牌。
- 使用
@engine.auth()
限制对工具的访问:
@engine.auth()
---
@engine.tool()
defpost_message(text: str, ctx: Context) -> str:
"""将消息发布到全局时间线。"""
# 仅在令牌有效时运行
...
如果请求不包含有效的令牌,它将被自动拒绝。 如果包含有效的令牌,则可以通过上下文获得用户信息。
3.3 更新客户端
从客户端调用受保护的工具时,您需要传递一个有效的 Google 颁发的 ID 令牌。 Claude 在看到该工具需要身份验证后会自动处理此问题。
当您在 Claude 中安装该工具时,添加 client ID 和 client secret:
mcpengine proxy <service-name> <your-endpoint> --claude --client-id <google-client-id> --client-secret <google-client-secret> --mode http
这告诉 Claude 使用您注册的 client ID 从 Google 请求令牌。 当用户授予权限时,Claude 会在每次调用您的 MCP 服务器时都包含该令牌。
您无需手动验证任何内容; MCPEngine 在内部处理令牌验证和解码。
3.4 部署更新后的服务器
您不需要更改 Dockerfile 或工具定义——只需确保:
- 传递 issuer_url(无论是在代码中还是通过环境变量)
- 重新构建并推送您的 Docker 镜像
- 使用新版本重新部署到 Lambda
docker build --platform=linux/amd64 --provenance=false -t mcp-lambda .
---
docker tag mcp-lambda ${REPOSITORY_URL}/mcp-lambda
docker push ${REPOSITORY_URL}/mcp-lambda
aws lambda update-function-code \
--function-name mcp-lambda \
--image-uri ${REPOSITORY_URL}/mcp-lambda:latest
3.5 确认它可以工作
部署完成后:
- Claude 现在应该提示用户通过 Google 进行身份验证,然后再调用受保护的工具
- 只有存在有效的令牌时,调用才会成功
- 您可以通过
ctx
访问用户的身份
回顾
通过两项更改——将 idp_config
添加到引擎并使用 @engine.auth()
装饰工具——我们已将有效的身份验证添加到我们的 MCP 服务器。 Google 处理用户登录。 Claude 处理令牌流。 MCPEngine 处理验证并将身份暴露给您的工具代码。
从这里开始
此时,我们已经在 AWS Lambda 上部署了三个可工作的 MCP 服务器:
- 返回天气响应的无状态服务器
- 使用 RDS 持久化和检索聊天消息的有状态服务器
- 使用 Google SSO 进行身份验证的安全服务器
经过身份验证的示例更接近于实际用例。 它非常小,但证明您可以构建一些有状态的、Lambda 原生的、符合 MCP 规范的东西,而无需运行服务器或维护粘性连接。
我们在此处使用 Claude 作为客户端,但该接口是完全标准的 MCP。 您可以同样轻松地使用来自另一个 LLM 或 orchestrator 的 MCPEngine 客户端进行连接。 这为代理系统打开了大门。 例如,您可以:
- 启动一个模拟的产品经理和工程师
- 将他们放到同一个聊天室中
- 让他们交流想法、记录工单和修改规范——所有这些都是公开的
所有这些都不需要任何特殊的集成。 只是工具、schema 和令牌。
截至今天,MCPEngine 是唯一支持内置身份验证的 MCP 的 Python 实现。 在下一篇文章中,我们将介绍更复杂的身份验证模式,包括范围访问、将工具限制给特定用户以及在工具逻辑中显示身份。
准备好开始了吗?
了解虚拟 feature store 对您的组织意味着什么。