MCP云包装
获取任何stdio MCP服务器并将其部署到云端——可以从在线版本ChatGPT、Claude.ai以及任何与MCP兼容的web和移动客户端访问。
大多数MCP服务器在本地运行——它们在您的计算机上与Claude Desktop或Claude Code配合得很好,但它们不能从网络上的ChatGPT、Claude.ai或移动应用程序中使用。这个框架改变了这一点。您带来一个现有的stdio MCP服务器,此框架将其部署为任何客户端都可以连接的AWS托管的MCP端点,具有完整的用户身份验证和针对外部服务的每个用户的OAuth。
这有什么作用
- 大多数stdio MCP服务器 → 带有URL的云托管MCP端点
- 适用于ChatGPT、Claude.ai和任何MCP客户端 --网络、移动、桌面
- 每用户身份验证 --每个用户通过Cognito登录,然后根据MCP的需要连接自己的外部帐户(微软、谷歌等)
- 自动OAuth管理 --Secrets Manager中的每个用户存储的令牌交换、刷新
- 无需更改MCP服务器 基本服务;使用每个用户OAuth的服务的一行更改
- 在几分钟内添加新服务 --创建一个包含3-4个配置文件的目录,部署
运作原理
- 将您的MCP服务器捆绑到AWS Lambda部署包中
- 通过以下方式将其作为云MCP端点公开 亚马逊基岩代理核心网关
- 处理呼叫者身份验证(Cognito+动态客户端注册)
- 管理外部服务(微软、谷歌等)的每用户OAuth令牌
- 提供一个身份验证设置网页,用户可以在其中连接他们的外部帐户
您的MCP服务器不需要了解Lambda、AgentCore或Cognito的任何信息。唯一的调整是对框架注入的访问令牌环境变量进行一行检查(请参见 准备MCP服务器).
建筑
┌──────────────────────────────────────────┐
│ Shared Infrastructure │
│ (deployed once) │
MCP Client ──────► │ Cognito User Pool (caller auth) │
(ChatGPT, Claude, │ DCR API Gateway (.well-known, /register)
any MCP client)
│ OAuth Callback (/oauth/callback) │
│ DynamoDB tables (DCR + OAuth state) │
└───────────────┬──────────────────────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
┌─────────▼──────────┐ ┌──────────▼─────────┐ ┌───────────▼────────┐
│ Service A │ │ Service B │ │ Service C │
│ │ │ │ │ │
│ AgentCore Gateway │ │ AgentCore Gateway │ │ AgentCore Gateway │
│ │ │ │ │ │ │ │ │
│ MCP Server Lambda │ │ MCP Server Lambda │ │ MCP Server Lambda │
│ └─ subprocess: │ │ └─ subprocess: │ │ └─ subprocess: │
│ your_mcp_pkg │ │ another_pkg │ │ third_pkg │
└────────────────────┘ └────────────────────┘ └────────────────────┘每个MCP服务都有自己的Lambda+AgentCore网关。它们共享Cognito池、DCR端点和OAuth回调基础设施。
两层身份验证
| 层 | 目的 | 机制 |
|---|---|---|
| 呼叫者身份验证 | 控制谁可以呼叫MCP网关 | AgentCore网关验证的Cognito JWT |
| 后端身份验证 | 服务可以代表用户访问什么? | 框架管理的标准OAuth2(授权码+PKCE) |
三类环境变量
每个打包的MCP服务都可能需要这些的组合。该框架加载所有三个类别,并自动将它们合并到子流程环境中。
| 类别 | 示例 | 它住在哪里 | 谁管理它 |
|---|---|---|---|
| 1.MCP服务配置 | TENANT_ID, API_BASE_URL | service.env 服务目录中的文件 | 您,已提交git |
| 2.MCP服务机密 | CLIENT_ID, CLIENT_SECRET, API_KEY | Secrets Manager(每个服务一个JSON对象——每个键都变成一个env变量) | 您,通过AWS CLI创建一次 |
| 3.MCP每个用户凭据 | 访问令牌 | 秘密管理器(每个服务每个用户一个) | 框架,通过OAuth流 |
第1类用于非秘密配置——它存在于 service.env 一起 handler.py所有凭据(包括客户端ID)都属于类别2(Secrets Manager中的服务秘密)。
先决条件
- Python 3.11+
- 紫外线
- Node.js(CDK CLI在Node上运行;项目的其余部分是Python)
- AWS CLI已配置凭据
- CDK已引导:
make bootstrap(或参见 部署) - 基于stdio的MCP服务器代码,可以作为lambda运行
存储库结构
mcp-cloud-wrappers/
├── packages/
│ └── mcp-wrapper-runtime/ # Framework runtime (installed into each Lambda)
│ └── src/mcp_wrapper/
│ ├── config.py # ServiceConfig, OAuthProviderConfig, load_oauth_json
│ ├── credentials.py # CredentialManager (Secrets Manager)
│ ├── oauth.py # OAuthHelper (PKCE, exchange, refresh)
│ └── handler.py # McpServiceHandler (base Lambda handler)
│
├── infra/
│ ├── app.py # CDK app — defines all stacks
│ ├── cdk_constructs/ # Reusable CDK constructs
│ │ ├── bundler.py # Local pip/uv bundler for Lambda assets
│ │ ├── cognito.py # Cognito User Pool + resource server
│ │ ├── dcr_bridge.py # DCR Lambda + API Gateway
│ │ ├── oauth_bridge.py # OAuth callback Lambda
│ │ ├── mcp_lambda.py # Per-service MCP Lambda
│ │ └── mcp_gateway.py # AgentCore Gateway + GatewayTarget
│ ├── stacks/
│ │ ├── shared.py # SharedInfraStack (deploy once)
│ │ └── service.py # ServiceStack (one per wrapped service)
│ └── lambda/
│ ├── dcr/ # Shared: RFC 7591 Dynamic Client Registration
│ ├── oauth_callback/ # Shared: Generic OAuth2 callback (all providers)
│ └── services/
│ └── / # One directory per wrapped MCP service
│ ├── handler.py # config: what to wrap, how to authenticate
│ ├── service.env # non-secret config
│ ├── tools.json # tool definitions (generated via gen-tools)
│ ├── requirements.txt.example # dependency template
│ ├── requirements.txt # actual deps with your paths
│ └── service.local.env # local config overrides
│
├── scripts/
│ ├── gen_tools.py # Generate tools.json from MCP server
│ └── verify_deployment.py # Post-deploy smoke test
├── cdk.json
├── Makefile
└── pyproject.toml哪些MCP服务器使用此框架?
该框架将您的MCP服务器作为 AWS Lambda内部的子流程 并通过以下方式进行通信 标准。这意味着您的服务器必须:
- 使用stdio传输 --在stdin/stdout上读/写MCP JSON-RPC。仅支持SSE、流式HTTP或WebSocket传输的服务器将无法工作。
- 在通话中保持无状态 --每次工具调用都会生成一个新的子流程。没有持久进程,调用之间没有内存状态,也没有连接池。如果您的服务器需要状态,请使用外部存储(DynamoDB、S3等)。
- 不写入文件系统 --Lambda的
/var/task是只读的。Guard在env var检查后写入(请参阅 准备您的MCP服务器),或写信至/tmp(短暂的,在调用之间擦除)。 - 通过环境变量接受配置 -凭据、API密钥和令牌作为env变量注入。运行时不会从磁盘读取配置文件。
- 在Lambda超时内完成 --默认120秒,最大900秒。每个工具调用必须在一次调用中完成。
什么效果好
- Python服务器 (FastMCP、MCP Python SDK)——最佳支持路径
mcp_module - Node.js、Go、Rust或任何编译的二进制文件 --使用
command/args在ServiceConfig中(二进制文件必须以Linux ARM64为目标) - API-wrapping服务器 (REST、GraphQL)——完美契合;无状态请求响应
- 仅使用API密钥的服务器 --最简单的情况下,不需要OAuth
- 使用OAuth的服务器 --框架管理整个令牌生命周期;服务器只读取envvar
什么不起作用
- 仅SSE或仅HTTP传输 --没有stdio,无法与子进程通信
- 依赖于跨工具状态的服务器 --例如工具A创建工具B读取的会话。每次调用都是独立的。
- MCP资源或提示 --AgentCore网关仅路由工具调用,不路由资源或提示请求
- 服务器发起的通知或流媒体 --Lambda仅是请求响应
- 30多种工具 --AgentCore分页为30,当前客户端(Claude.ai、ChatGPT)不遵循分页
- 重型启动 --子进程冷启动(Python解释器+导入)通常会增加1-3秒;依赖性强的服务器(pandas、torch)将变慢
逐步包装新的MCP服务
本节将介绍整个过程。捆绑 msgraph 包裹的包装物https://github.com/jspv/msgraph-email-calendar-mcp是这里描述的每个步骤的具体示例。
1.准备您的MCP服务器
您的MCP服务器位于其自己的存储库、包注册表或您保存它的任何地方 任何语言 --该框架将其作为子进程启动,并通过stdio进行通信。
为了兼容Lambda,您的服务器需要调整两件事:
a.接受框架注入的访问令牌
如果您的服务使用每个用户的OAuth,您需要对其进行修改——它将访问令牌作为环境变量接收,并由框架传递。在MCP服务器中找到获取访问令牌的位置,并在顶部添加env-var检查。这使得相同的代码可以在本地(运行自己的身份验证)和Lambda内部(框架管理身份验证)工作:
python --在MCP服务器的auth或HTTP客户端模块中:
import os
def get_token():
# When running inside Lambda, the framework injects a valid token
token = os.environ.get("MY_SERVICE_ACCESS_TOKEN")
if token:
return token
# When running locally, use the normal auth flow
return local_auth_flow()Node.js --同样的想法,在服务器的auth模块中:
function getToken() {
return process.env.MY_SERVICE_ACCESS_TOKEN || localAuthFlow();
}环境变量名称(MY_SERVICE_ACCESS_TOKEN 上面)是你设置的任何值 access_token_env_var 在 ServiceConfig (下面的步骤2)。它只需要匹配。
如果您的服务不需要每个用户的OAuth(只需要API密钥),则不需要更改代码-框架直接将API密钥作为env-var注入。
b.不要写入Lambda文件系统(即,你的mcp需要能够作为Lambda运行)
AWS Lambda /var/task 目录是 只读。如果您的MCP服务器在启动时写入文件(令牌缓存、SQLite数据库、临时文件),这些写入将在Lambda中失败。
保护任何文件系统写入,以便在框架管理身份验证时跳过它们:
import os
def _framework_managed():
"""True when running inside the MCP Lambda wrapper framework."""
return bool(os.environ.get("SERVICE_NAME"))
# Before any file write:
if not _framework_managed():
cache_dir.mkdir(parents=True, exist_ok=True)
write_token_cache(...)要注意的文件系统写入的常见来源:
- 令牌缓存 (SOAP、googleauth等)--在框架管理时跳过缓存读/写
- 会话状态文件 --该框架通过DynamoDB/秘密管理器处理状态
- 数据库存储 --如果不可避免,请写信至
/tmp(Lambda中唯一可写的路径)
2.在infra/lambda/services下创建服务目录,用于服务器注册
在下面创建一个目录 infra/lambda/services// 使用这些文件:
infra/lambda/services/my-service/
├── handler.py # what to wrap, how to authenticate
├── service.env # non-secret config
├── oauth.json # OAuth provider config (if service uses OAuth)
├── tools.json # tool definitions (generated via gen-tools)
├── requirements.txt.example
└── requirements.txt # actual deps with your pathsservice.env --此服务的非秘密配置:
# service.env
MY_TENANT_ID=my-org-123
MY_API_BASE_URL=https://api.provider.com/v1handler.py --声明 *什么* 包装和 *怎么* 进行身份验证:
from mcp_wrapper import McpServiceHandler, ServiceConfig, load_oauth_json
config = ServiceConfig(
service_name="my-service",
mcp_module="my_service_mcp.server", # python -m my_service_mcp.server
passthrough_env_vars=["MY_TENANT_ID"], # from service.env
service_secret_name="{prefix}-my-service-service-secrets",
oauth=load_oauth_json(), # reads oauth.json (single source of truth)
access_token_env_var="MY_SERVICE_ACCESS_TOKEN",
)
_handler = McpServiceHandler(config)
def handler(event, context):
return _handler.handle(event, context)passthrough_env_vars 从中取值的名称 service.env 以转发到子流程。凭据属于服务机密(请参阅步骤3)。OAuth提供者配置(端点、作用域、客户端密钥)在中定义 oauth.json --不在此文件中。
对于一个 非Python MCP服务器,使用 command 和 args 而不是 mcp_module:
config = ServiceConfig(
service_name="my-node-service",
command="/var/task/node_modules/.bin/my-mcp-server",
args=["--stdio"],
service_secret_name="{prefix}-my-node-service-secrets",
oauth=load_oauth_json(),
access_token_env_var="MY_SERVICE_ACCESS_TOKEN",
)对于服务 没有OAuth,省略 oauth 和 access_token_env_var 领域:
config = ServiceConfig(
service_name="my-search",
mcp_module="my_search_mcp.server",
service_secret_name="{prefix}-my-search-secrets",
# API keys live in the service secret; no OAuth needed
)requirements.txt.example --显示依赖关系的模板。复制到 requirements.txt 并填写您的包裹来源:
# Copy this file to requirements.txt and update paths.
# Framework dependencies (always required)
mcp
run-mcp-servers-with-aws-lambda
boto3
httpx
# MCP server package — uncomment ONE of these:
# my-service-mcp # from PyPI
# my-service-mcp @ git+https://github.com/you/my-service-mcp.git # from Git
# my-service-mcp @ file:///path/to/local/checkout # from local pathmcp-wrapper-runtime 由bundler自动安装,无需列出。
对于 非Python MCP服务器,你仍然需要框架deps requirements.txt (Lambda处理程序本身就是Python)。通过以下方式将服务器二进制或节点模块捆绑到Lambda包中 handler_source_dir --把它们放在旁边 handler.py 邦德勒把它们抄了进去。
MCP服务器是 不 此存储库的一部分。它在构建时作为依赖项引入,就像任何Lambda捆绑其依赖项一样。
就是这样,没有其他文件可以编辑。CDK应用程序会自动发现下的每个目录 infra/lambda/services/ 包含a handler.py 并为其创建堆栈。
3.预部署设置
创建服务密钥 在机密管理器中(客户端ID和API密钥等凭据):
aws secretsmanager create-secret \
--name mcp-wrappers-my-service-service-secrets \
--secret-string '{"MY_CLIENT_ID": "your-client-id", "MY_CLIENT_SECRET": "your-secret"}'生成 tools.json --AgentCore需要知道MCP服务器公开了哪些工具。如果您有MCP服务器的本地签出(通过引用 file:// 路径在 requirements.txt),脚本可以自动进行自检:
make gen-tools SERVICE=my-service每当MCP服务器的工具定义发生变化时,请重新运行此程序。
如果没有本地签出(例如,包来自PyPI或git URL)或服务器不是Python,请创建 tools.json 手动。这是一个JSON数组,其中每个条目都有 name, description,以及可选 inputSchema 具有JSON模式属性。看 infra/lambda/services/msgraph/tools.json 作为一个工作示例。
4.部署
make deploy-shared # first time only (creates Cognito, DCR, OAuth callback)
make deploy-service SERVICE=my-service # deploy your service5.部署后设置
注册OAuth回调URL (如果您的服务使用OAuth):采取 OAuthCallbackUrl 从共享堆栈输出中提取,并将其作为重定向URI添加到OAuth提供商的应用程序注册中。
看 秘密和安全 有关秘密如何存储和作用域的详细信息。
就是这样。该框架处理Cognito、DCR、AgentCore网关、OAuth令牌生命周期和Lambda打包。
部署
# Install Python dependencies
uv sync
# Bootstrap CDK in your AWS account (first time only)
make bootstrap
# Deploy shared infrastructure (Cognito, DCR, OAuth callback)
make deploy-shared
# Create a Cognito user (needed to authenticate with the gateway)
aws cognito-idp admin-create-user \
--user-pool-id $(aws cloudformation describe-stacks --stack-name mcp-wrappers-shared \
--query 'Stacks[0].Outputs[?contains(OutputKey,`UserPoolId`)].OutputValue' --output text) \
--username your-email@example.com \
--user-attributes Name=email,Value=your-email@example.com Name=email_verified,Value=true \
--temporary-password 'TempPass123!'
# Deploy a specific service
make deploy-service SERVICE=my-service
# Deploy all services
make deploy-all
# Verify endpoints are responding
make verify第一 make 调用会自动将CDK CLI作为本地npm依赖项安装——您不需要全局安装它。
macOS用户:Lambda需要Linux ARM64二进制文件来编译Python包(pydantic、密码学等)。打包机会自动退回到集装箱内进行构建。集 CDK_DOCKER=podman 如果你使用Podman而不是Docker:
CDK_DOCKER=podman make deploy-service SERVICE=my-service或者将其导出到您的shell配置文件中,这样您就不必每次都通过它。
Cognito用户只需要创建一次。首次通过托管UI登录时,系统会提示您设置永久密码。
可用目标
| 目标 | 描述 |
|---|---|
make synth | 合成所有CloudFormation模板 |
make list | 列出所有堆栈 |
make bootstrap | 您的AWS帐户中的Bootstrap CDK(第一次) |
make gen-tools SERVICE=x | 从MCP服务器生成tools.json |
make deploy-shared | 部署共享基础设施 |
make deploy-service SERVICE=x | 部署特定服务 |
make deploy-all | 部署共享+所有发现的服务 |
make verify | 运行部署后烟雾测试 |
make auth | 在浏览器中打开身份验证设置页面 |
秘密和安全
秘密是如何存储的
该框架使用 AWS Secrets Manager --每个帐户都可以使用的托管AWS服务,无需设置。您不提供或部署任何东西;您只需通过AWS CLI或SDK存储和检索值。
每个服务有两种秘密:
| 类型 | 秘密名称模式 | 创建者 |
|---|---|---|
| 服务秘密 | {prefix}-{service}-service-secrets | 你,通过 aws secretsmanager create-secret (每次服务一次) |
| 用户凭据 | {prefix}-{service}-user-{cognito_sub} | 框架,当用户完成OAuth流程时自动 |
服务秘密 是一个包含JSON对象的单个Secrets Manager条目。JSON中的每个键在子流程中都成为一个单独的环境变量。以下是如何将多个机密传递给服务,而不创建多个机密管理器条目:
aws secretsmanager create-secret \
--name mcp-wrappers-my-service-service-secrets \
--secret-string '{
"CLIENT_SECRET": "abc123",
"API_KEY": "xyz789",
"WEBHOOK_SECRET": "def456"
}'子流程将看到 CLIENT_SECRET=abc123, API_KEY=xyz789,以及 WEBHOOK_SECRET=def456 在其环境中。
用户凭据 当用户完成OAuth流程时,框架会自动创建。每个用户都有自己的秘密,其中包含 access_token, refresh_token,以及 expires_at。您永远不会手动创建这些。
这两种类型的秘密都可以在部署堆栈之前或之后随时创建。Lambda仅在调用时读取它们,而不是在部署时读取。
IAM范围——每个Lambda可以访问的内容
每个服务Lambda的IAM角色仅限于其自己的机密。该政策限制访问 {prefix}-{service}-*:
# The service-a Lambda can access:
mcp-wrappers-service-a-service-secrets ✓
mcp-wrappers-service-a-user-abc123 ✓
# It CANNOT access:
mcp-wrappers-service-b-service-secrets ✗ (different service)
my-database-password ✗ (no prefix match)
production-api-key ✗ (no prefix match)服务彼此隔离。每个Lambda只能读取与其自身匹配的机密 {prefix}-{service}-* 图案。同样的作用域也适用于OAuth回调Lambda(仅限于 {prefix}-*).
Lambda也有 CreateSecret 其范围内的权限——这是必要的,因为当用户第一次完成OAuth时,会动态创建新的用户凭据秘密(您事先不知道Cognito用户ID)。
部署顺序
堆栈和秘密有这样的依赖链:
Deploy shared stack ──► Get OAuthCallbackUrl ──► Register URL with OAuth provider
│ (before first OAuth flow)
│
└──► Deploy service stack ──► Service is live
│
Create service secret ──────────────────┘ (before first tool invocation)实践中:设置 service.env、服务机密,以及 tools.json 首先(步骤3),然后部署(步骤4),然后注册OAuth回调URL(步骤5)。CDK自动解析堆栈间的依赖关系。
OAuth流程是如何工作的
当用户首次与需要OAuth的服务交互时:
1. Agent calls a tool (e.g., list_messages)
└─ Interceptor injects _cognito_sub from JWT
└─ Handler finds no credentials for this user in Secrets Manager
└─ Sets OAUTH_AUTH_URL to the auth setup page, launches subprocess
└─ MCP server returns "not authenticated, call start_auth"
2. Agent calls start_auth
└─ MCP server reads OAUTH_AUTH_URL from env, returns it
└─ Agent presents URL to user: "Open this link to connect your account"
3. User opens URL in browser → auth setup page
└─ Logs into Cognito (establishes identity)
└─ Sees available services, clicks "Connect"
└─ Redirects to external provider login (Microsoft, Google, etc.)
└─ Provider redirects to /oauth/callback
└─ Callback stores tokens in Secrets Manager under {prefix}-{service}-user-{cognito_sub}
└─ Redirects back to auth setup page showing "Connected"
4. User returns to chat, tells agent to try again
└─ Handler loads token from Secrets Manager
└─ Injects access token as env var
└─ MCP server tools work normally令牌刷新是透明的——处理程序在每次调用时检查到期时间,并使用存储的刷新令牌自动刷新。
连接外部服务
部署后,用户需要连接其外部帐户(Microsoft、Google等) 在MCP工具能够访问其数据之前。
身份验证设置页面
运行:
make auth这将打开一个网页,您可以在其中:
- 使用您的Cognito帐户登录(与MCP网关的凭据相同)
- 查看所有可用服务及其连接状态
- 点击“连接”以对每个外部服务进行身份验证
该页面处理完整的OAuth流程——您只需点击提供商的登录即可。
适用于聊天用户(Claude.ai、ChatGPT等)
当您首次使用需要身份验证的工具时,代理将调用 start_auth 返回认证设置页面URL。在浏览器中打开它,完成登录, 然后告诉代理再试一次。
身份验证设置URL
部署后,URL显示在堆栈输出中:
aws cloudformation describe-stacks --stack-name mcp-wrappers-shared \
--query 'Stacks[0].Outputs[?contains(OutputKey,`AuthSetupUrl`)].OutputValue' \
--output text与需要连接其帐户的最终用户共享此URL。
捆绑示例:Microsoft Graph(msgraph)
这 infra/lambda/services/msgraph/ 目录封装了一个用于Outlook邮件和日历的MCP服务器(msgraph电子邮件日历MCP)。它展示了完整的模式:
设置和部署
# 1. Edit service.env with your tenant ID
# infra/lambda/services/msgraph/service.env:
# MICROSOFT_TENANT_ID=your-tenant-id
# 2. Create the service secret with your Azure app credentials
aws secretsmanager create-secret \
--name mcp-wrappers-msgraph-service-secrets \
--secret-string '{"MICROSOFT_CLIENT_ID": "your-azure-client-id"}'
# 3. Generate tools.json from the MCP server
make gen-tools SERVICE=msgraph
# 4. Deploy
make deploy-all
# 5. Register the OAuthCallbackUrl (from deploy output) in your Azure App Registration
# under Authentication > Web > Redirect URIs处理器
完整的处理程序——其他一切都是框架管理的:
# infra/lambda/services/msgraph/handler.py
from mcp_wrapper import McpServiceHandler, ServiceConfig, load_oauth_json
config = ServiceConfig(
service_name="msgraph",
mcp_module="msgraph_mcp.server",
passthrough_env_vars=["MICROSOFT_TENANT_ID"],
service_secret_name="{prefix}-msgraph-service-secrets",
oauth=load_oauth_json(), # reads oauth.json — single source of truth
access_token_env_var="GRAPH_ACCESS_TOKEN",
)
_handler = McpServiceHandler(config)
def handler(event, context):
return _handler.handle(event, context)MCP服务器包参考
复制 requirements.txt.example 到 requirements.txt (gitignored)并设置本地结账的路径:
mcp
run-mcp-servers-with-aws-lambda
boto3
httpx
msgraph-mcp @ file:///path/to/msgraph-email-calendar-mcpmcp-wrapper-runtime 由打包机自动安装。
使用外部Cognito池
如果你已经有一个Cognito用户池(例如,来自另一个项目),你可以重用它,而不是创建一个新的。这需要编辑 infra/app.py --该文件需要修改的唯一情况:
# infra/app.py — pass external pool details to SharedInfraStack
shared = SharedInfraStack(
app, f"{prefix}-shared", env=env,
external_user_pool_id="us-east-1_xxxxxx",
external_user_pool_arn="arn:aws:cognito-idp:us-east-1:123456789:userpool/us-east-1_xxxxxx",
external_hosted_ui_domain="auth.example.com",
external_resource_server_identifier="my-prefix",
)DCR桥和OAuth回调仍在创建中,只有Cognito池被重用。
CDK上下文参数
通过 -c key=value 在CDK命令行上,或在中设置默认值 cdk.json:
| 参数 | 默认值 | 说明 |
|---|---|---|
prefix | mcp-wrappers | 所有堆栈的资源名称前缀 |
domain_name | -- | Cognito托管UI的自定义域(可选) |
hosted_zone_name | -- | 自定义域的Route53托管区域(可选) |
google_client_id_ssm | -- | 谷歌社交联盟的SSM参数(可选) |
google_client_secret_ssm | -- | 谷歌社交联盟的SSM参数(可选) |
每个服务配置不使用CDK上下文。非秘密配置进入 service.env,凭证进入秘密管理器。看 三类环境变量.
框架内部
端到端请求流(详细)
这是从MCP客户端到MCP服务器再返回的单个工具调用的完整路径。了解此流程对于调试非常重要。
MCP Client (Claude.ai, ChatGPT, etc.)
│
│ MCP JSON-RPC over HTTPS
▼
AgentCore Gateway
│ Validates Cognito JWT (CUSTOM_JWT authorizer)
│ Rejects if invalid — tool call never reaches Lambda
│
│ Invokes request interceptor Lambda
▼
Interceptor Lambda (infra/lambda/interceptor/handler.py)
│ Receives: {interceptorInputVersion, mcp: {gatewayRequest: {headers, body}}}
│ Decodes JWT from Authorization header (base64, no verification — already validated)
│ Extracts Cognito "sub" claim
│ Injects _cognito_sub into body.params.arguments
│ Returns: {interceptorOutputVersion: "1.0", mcp: {transformedGatewayRequest: {body}}}
│
│ AgentCore forwards the modified request to the target Lambda
▼
MCP Server Lambda — handler entry point (handler.py in service directory)
│ Receives: event = tool arguments dict (e.g. {"folder": "inbox", "_cognito_sub": "abc123"})
│ context.client_context.custom = {bedrockAgentCoreToolName: "target___tool_name"}
│
│ Calls McpServiceHandler.handle(event, context)
▼
McpServiceHandler.handle() (packages/mcp-wrapper-runtime/src/mcp_wrapper/handler.py)
│
│ 1. Health check: if event has "ping"/"health", return status immediately
│
│ 2. Extract user ID: reads event.get("_cognito_sub")
│ Returns the Cognito sub or None
│
│ 3. Strip _cognito_sub from event: event.pop("_cognito_sub", None)
│ FastMCP/Pydantic would reject it as an unexpected tool argument
│
│ 4. Build subprocess environment (three categories):
│ Category 1: passthrough env vars from service.env (e.g. TENANT_ID)
│ Category 2: service secrets from Secrets Manager (e.g. CLIENT_ID)
│ Category 3: per-user OAuth credentials:
│ - If user_id is available AND credentials exist in Secrets Manager:
│ Load tokens, refresh if expired, set access_token_env_var + OAUTH_AUTHENTICATED=true
│ - If user_id is available but NO credentials:
│ Set OAUTH_AUTHENTICATED=false, OAUTH_AUTH_URL=AUTH_SETUP_URL
│ - If user_id is None (interceptor not working):
│ Set OAUTH_AUTHENTICATED=false (no OAUTH_AUTH_URL — can't do per-user lookup)
│
│ 5. Launch MCP subprocess: python -m {mcp_module}
│ Subprocess receives the merged env vars
│ StdioServerAdapterRequestHandler bridges JSON-RPC to stdio
│ BedrockAgentCoreGatewayTargetHandler handles the AgentCore protocol
▼
MCP Server subprocess (e.g. msgraph_mcp.server)
│ Receives tool call via stdio (JSON-RPC)
│ Reads GRAPH_ACCESS_TOKEN (or equivalent) from env — uses it for API calls
│ If not authenticated: reads OAUTH_AUTH_URL from env, returns it via start_auth tool
│ Executes tool, returns result via stdio
▼
Response flows back: subprocess → handler → AgentCore Gateway → MCP Client认证设置页面流程(详细)
当用户需要连接外部服务时(一次性设置):
User visits /auth/setup (via make auth, start_auth URL, or direct link)
│
▼
Auth Setup Lambda — /auth/setup route
│ No session → redirects to Cognito hosted UI login
▼
Cognito Hosted UI
│ User logs in (email/password, possibly MFA)
│ If already logged in (browser cookie), may auto-complete
│ Redirects to /auth/callback?code=xxx&state=yyy
▼
Auth Setup Lambda — /auth/callback route
│ Validates state from DynamoDB (prevents CSRF)
│ Exchanges Cognito auth code for tokens (POST to Cognito token endpoint)
│ Decodes ID token → extracts sub and email
│ Creates DynamoDB session (10-minute TTL, keyed by random session token)
│ Renders service connection page HTML
│ Each service card shows: display_name, connected/not connected, [Connect] button
│ Connect button URL: /auth/connect/{service}?session={token}
▼
User clicks [Connect]
│
▼
Auth Setup Lambda — /auth/connect/{service} route
│ Validates session token from DynamoDB → gets Cognito sub
│ Loads service OAuth config from SERVICE_OAUTH_CONFIGS env var
│ Reads client_id from Secrets Manager (service secret)
│ Generates PKCE code_verifier + code_challenge
│ Stores OAuth state in DynamoDB: {state, user_id, service_name, token_endpoint,
│ client_id, code_verifier, return_url=/auth/setup?session=xxx, ttl}
│ Redirects to external provider's OAuth authorization URL
▼
External Provider (Microsoft, Google, etc.)
│ User logs in and approves permissions
│ Redirects to /oauth/callback?code=xxx&state=yyy
▼
OAuth Callback Lambda — /oauth/callback route (existing, shared)
│ Validates state from DynamoDB
│ Exchanges authorization code for tokens (POST to provider's token endpoint)
│ With PKCE code_verifier if present
│ Stores tokens in Secrets Manager: {prefix}-{service}-user-{cognito_sub}
│ Checks for return_url in state record
│ If return_url present: redirects to /auth/setup?session=xxx&connected={service}
│ If no return_url: shows static "Authentication successful" HTML
▼
Auth Setup Lambda — /auth/setup route (return visit)
│ Session token present → loads session from DynamoDB
│ connected={service} param → shows success flash message
│ Re-checks connection status for all services
│ User sees "{display_name} — Connected ✓"身份传播——为什么注入然后剥离
AgentCore网关验证Cognito JWT,但 不 将索赔转发给Lambda目标。Lambda只接收以下工具参数 event 元数据 context.client_context.custom (工具名称、网关ID——无用户标识)。
该框架使用 请求拦截器 为了解决这个问题:
- 拦截器 从Authorization标头中解码JWT并注入
_cognito_sub进入JSON-RPCparams.arguments - 代理商核心 将修改后的参数传递为
event到目标Lambda - McpServiceHandler 读取
event.get("_cognito_sub")识别用户 - McpServiceHandler 移除
_cognito_sub从event转发之前BedrockAgentCoreGatewayTargetHandler
删除是必要的,因为FastMCP通过Pydantic根据函数签名验证工具参数。意外的 _cognito_sub 参数将导致验证错误。标识在参数中短暂存在,由处理程序提取,然后在到达MCP子流程之前被剥离。
拦截器响应 必须 包括 "interceptorOutputVersion": "1.0" 在顶层,AgentCore会在没有请求的情况下自动丢弃请求。
秘密经理
看 秘密和安全 用于命名约定、IAM范围和部署顺序。
30刀具限制
AgentCore网关分页 tools/list MCP以每页30个工具的速度响应,并返回 nextCursor 更多页面。然而,当前的MCP客户端(Claude.ai、ChatGPT)不遵循分页——它们只取第一页。这意味着 只有前30个工具(按字母顺序排列)对客户端可见。
这 gen-tools 脚本强制执行此限制,如果MCP服务器暴露超过30个工具,则会出错。如果您的服务需要更多,请减少MCP服务器中的工具数量(整合工具,删除很少使用的工具)或跨多个网关目标拆分。
AgentCore还支持 searchType: SEMANTIC 其用用于自然语言发现的单个搜索工具替换工具列表。然而,目前的MCP客户端不使用它——他们希望工具直接出现在 tools/list.
诊断日志
TODO:生产前删除。 拦截器、处理程序和凭据管理器当前发出 [interceptor] 和 [mcp-wrapper] 将日志行记录到stderr(CloudWatch),用于调试身份传播和凭据加载流。这些应该被移除或用门锁住 LOG_LEVEL env-var字段测试完成后。带有诊断日志记录的文件:
infra/lambda/interceptor/handler.pypackages/mcp-wrapper-runtime/src/mcp_wrapper/handler.pypackages/mcp-wrapper-runtime/src/mcp_wrapper/credentials.py
CDK结构组成
SharedInfraStack
├── CognitoPool (User Pool + resource server + hosted UI domain)
├── DcrBridge (DynamoDB table + DCR Lambda + API Gateway)
├── OAuthBridge (DynamoDB table + callback Lambda + /oauth/callback)
└── AuthSetup (Cognito client + auth setup Lambda + /auth/* routes)
ServiceStack (one per wrapped service)
├── McpServerLambda (Lambda + bundler + IAM role)
└── McpAgentCoreGateway (CfnGateway + CfnGatewayTarget + interceptor Lambda + gateway role)