MCP(Model Context Protocol)让 AI 助手能调用外部工具和数据源,但一个裸跑的 MCP 服务等于把内部 API 暴露给任何能发请求的客户端。Amazon Bedrock AgentCore Gateway 提供了托管 MCP 服务的能力,同时支持把 OAuth Authorization Code Flow 挂到入站侧——每个 AI 助手请求必须携带由你组织 IdP 签发的用户身份令牌,否则直接拒绝。这篇文章把整套流程从零搭到可上线的状态。
为什么 MCP 服务不能裸跑
MCP 的设计初衷是让 LLM 动态发现和调用工具。客户端发一个 tools/list 或 tools/call,服务端就响应。如果没有任何认证层:
- 任何拿到服务端地址的客户端都能调用你的内部工具——数据库查询、文件操作、邮件发送,全都不设防。
- 你无法区分"这是张三的请求"还是"这是未授权脚本的请求",审计和合规无从谈起。
- 多租户场景下,不同用户应该看到不同工具集或不同数据范围,裸跑根本做不到。
OAuth Authorization Code Flow 解决的是"用户是谁"这个问题。AI 助手在调用 MCP 服务前,必须先通过组织 IdP(比如 Okta、Azure AD、Amazon Cognito)完成登录,拿到 access token,再在每次 MCP 请求里带上这个 token。AgentCore Gateway 在入站侧校验 token,合法才放行。
授权码流在 MCP 场景下的运转方式
传统 Web 应用里,授权码流的套路是:浏览器跳转 IdP → 用户登录 → IdP 回调带 code → 后端换 token → 拿 token 访 API。MCP 客户端不是浏览器,但流程本质一样,只是交互形式变了:
- MCP 客户端发现服务端要求认证(AgentCore Gateway 返回
401+ OAuth metadata)。 - 客户端引导用户完成 IdP 登录,拿到 authorization code。
- 客户端用 code 换 access token。
- 后续所有 MCP 请求(
initialize、tools/call等)在 header 里携带Authorization: Bearer <token>。 - AgentCore Gateway 校验 token 签名、过期时间、scope,通过后才把请求转发给后端 MCP 服务。
关键点:Gateway 是认证的执行点,MCP 服务本身不需要处理 OAuth。这把安全逻辑从业务逻辑里剥离了出来。
实战:从零搭建一套可上线的配置
下面用一个最小但完整的例子,把 Amazon Cognito 作为 IdP、AgentCore Gateway 作为认证入口,跑通整个流程。
第一步:创建 Cognito User Pool
用 AWS CLI 创建一个 User Pool 和 App Client,配置授权码流的回调 URL:
# 创建 User Pool
aws cognito-idp create-user-pool \
--pool-name "mcp-auth-pool" \
--policies "PasswordPolicy={MinimumLength=8,RequireUppercase=true}" \
--region us-east-1
# 记下返回的 UserPoolId,例如 us-east-1_ABC123def
# 创建 App Client,启用授权码流
aws cognito-idp create-user-pool-client \
--user-pool-id us-east-1_ABC123def \
--client-name "mcp-gateway-client" \
--generate-client-secret \
--allowed-o-auth-flows code \
--allowed-o-auth-scopes "openid profile mcp-tools/read" \
--callback-urls "https://your-app.example.com/callback" \
--supported-identity-providers COGNITO \
--region us-east-1
# 记下返回的 ClientId,例如 xyz789abc
Cognito 的 issuer URL 格式为 https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ABC123def,AgentCore Gateway 会用这个 URL 去 JWKS 端点拉公钥、验证 token 签名。
第二步:部署 MCP 服务到 AgentCore Gateway
假设你有一个 Python MCP 服务,提供内部工具查询。用以下 YAML 描述 Gateway 的入站认证配置:
# agentcore-gateway-config.yaml
GatewayName: mcp-internal-tools
Description: "Internal tools MCP server with OAuth inbound auth"
InboundAuth:
Type: OAuthCodeFlow
OAuthConfig:
IssuerUrl: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ABC123def"
Audience: "xyz789abc" # Cognito App Client ID
Scopes:
- "mcp-tools/read"
TokenValidation:
CheckSignature: true
CheckExpiry: true
CheckAudience: true
MCPServers:
- Name: internal-tools
Runtime: Python
EntryPoint: "server.main"
PackageUri: "s3://my-bucket/mcp-server-package.zip"
部署命令:
aws bedrock-agentcore create-gateway \
--gateway-name mcp-internal-tools \
--cli-input-json file://agentcore-gateway-config.yaml \
--region us-east-1
部署完成后,Gateway 会暴露一个 MCP endpoint,例如 https://agentcore.us-east-1.amazonaws.com/gateway/mcp-internal-tools。所有发到这个 endpoint 的请求,Gateway 先校验 Bearer token,合法才转发给后端 MCP 服务。
第三步:MCP 客户端集成 OAuth
MCP 客户端需要实现 OAuth discovery 和 token 管理。Python 客户端的最小集成示例:
"""MCP client with OAuth Authorization Code Flow integration."""
import httpx
import json
import webbrowser
from httpx_oauth.clients.cognito import CognitoOAuth2
# —— 配置 ——
COGNITO_POOL_ID = "us-east-1_ABC123def"
CLIENT_ID = "xyz789abc"
CLIENT_SECRET = "your-client-secret" # 从 AWS CLI 或 Secrets Manager 获取
REGION = "us-east-1"
GATEWAY_URL = "https://agentcore.us-east-1.amazonaws.com/gateway/mcp-internal-tools"
REDIRECT_URI = "https://your-app.example.com/callback"
SCOPE = ["openid", "profile", "mcp-tools/read"]
# —— OAuth 客户端 ——
oauth_client = CognitoOAuth2(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
user_pool_id=COGNITO_POOL_ID,
region=REGION,
)
async def authenticate_user():
"""引导用户完成 IdP 登录,获取 access token。"""
authorization_url = await oauth_client.get_authorization_url(
redirect_uri=REDIRECT_URI,
scope=SCOPE,
)
# 在浏览器中打开 IdP 登录页
webbrowser.open(authorization_url)
print(f"请在浏览器中完成登录,回调后将 code 粘贴到这里:")
code = input("Authorization code: ")
# 用 code 换 token
token = await oauth_client.get_access_token(
code=code,
redirect_uri=REDIRECT_URI,
)
return token
async def call_mcp_tool(tool_name: str, arguments: dict):
"""携带 Bearer token 调用 MCP 工具。"""
token = await authenticate_user()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments,
},
}
async with httpx.AsyncClient() as client:
response = await client.post(GATEWAY_URL, headers=headers, json=payload)
if response.status_code == 401:
print("Token 无效或已过期,请重新认证。")
return None
response.raise_for_status()
return response.json()
# —— 使用示例 ——
if __name__ == "__main__":
import asyncio
result = asyncio.run(call_mcp_tool("query_internal_db", {"table": "orders", "limit": 5}))
print(json.dumps(result, indent=2))
运行前需要安装依赖:
pip install httpx httpx-oauth
这段代码的核心逻辑:先通过 Cognito OAuth 拿到 access token,再在每次 MCP 请求的 Authorization header 里带上它。Gateway 校验通过后,请求才会到达你的 MCP 服务。
第四步:验证整条链路
# 1. 确认 Gateway 的 JWKS 端点可达
curl -s "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ABC123def/.well-known/jwks.json" | head -c 200
# 2. 不带 token 直接请求 Gateway,应返回 401
curl -s -o /dev/null -w "%{http_code}" \
-X POST "$GATEWAY_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# 预期输出: 401
# 3. 用合法 token 请求,应返回 200
# (token 从上面的 Python 客户端获取,或通过 Cognito hosted UI 手动获取)
curl -s -w "\n%{http_code}" \
-X POST "$GATEWAY_URL" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# 预期输出: 200 + 工具列表 JSON
生产环境上线前的检查清单
把这套配置推到生产之前,逐项确认:
| 检查项 | 要点 |
|---|---|
| Token 存储 | 客户端不要把 access token 存在 localStorage 或明文配置文件里。用 OS keychain 或加密的 session store。 |
| Scope 设计 | 按"最小权限"原则设计 scope。mcp-tools/read 和 mcp-tools/write 分开,不同角色给不同 scope。 |
| Token 过期与刷新 | Cognito access token 默认 1 小时过期。客户端必须实现 refresh token 流程,避免用户反复登录。 |
| 回调 URL 安全 | App Client 的 callback-urls 必须精确匹配你的应用域名,不要用 localhost 或通配符。 |
| Client Secret 保护 | 如果 MCP 客户端是后端服务,client secret 从 Secrets Manager 动态拉取,不要硬编码。如果是纯前端客户端,则不要启用 generate-client-secret,改用 PKCE 增强。 |
| Gateway 日志 | 开启 AgentCore Gateway 的访问日志和认证日志,401 事件要进 CloudWatch 告警。 |
| JWKS 缓存 | Gateway 内部会缓存 JWKS,但 Cognito 旋转密钥时(大约每 6-8 小时),确保缓存 TTL 不超过 1 小时,避免用已失效的公钥校验。 |
| 多租户隔离 | 如果多个组织共用一个 Gateway,每个组织用不同的 Cognito User Pool 或不同的 App Client,靠 audience claim 做隔离。 |
几个值得注意的边界
MCP 客户端不是浏览器。 传统 OAuth 授权码流依赖浏览器跳转,但 MCP 客户端可能是 CLI 工具、IDE 插件或后台 Agent。你需要为不同客户端形态设计不同的认证交互:CLI 用设备码流(Device Code Flow)可能更自然,IDE 插件用系统浏览器跳转更顺畅。AgentCore Gateway 目前文档聚焦授权码流,如果你的客户端不适合浏览器交互,需要评估是否改用 Device Code Flow 或 Client Credentials Flow(后者没有用户身份,只有应用身份)。
Gateway 是单点。 所有 MCP 请求经过 Gateway,这意味着 Gateway 的可用性直接决定服务的可用性。生产环境要配置 Gateway 的多 AZ 部署和自动扩缩容。
token 校验的"足够好"标准。 Gateway 默认校验签名、过期和 audience。如果你的安全要求更高(比如需要校验 token 里的自定义 claim、做 token introspection、或和外部 IdP 的 revocation endpoint 实时联动),需要确认 Gateway 是否支持这些扩展,或者自己在 MCP 服务侧加一层补充校验。
把 OAuth 授权码流挂到 AgentCore Gateway 的入站侧,核心收益是:MCP 服务专注业务逻辑,认证和授权由 Gateway 统一执行,用户身份从 IdP 签发到 token 里全程可追溯。这套配置从 Cognito User Pool 到 Gateway YAML 到客户端集成,每个环节都可以直接改造适配你自己的 IdP 和工具集。上线前逐项走一遍检查清单,把 scope、secret、日志这几件事做扎实,就能从"裸跑 MCP"切换到"每个请求都有合法用户身份背书"的状态。