首页 / 文章

基于 Bedrock AgentCore 的企业 MCP,集成 Okta SSO 与 Trino 功能

Lambda OAuth 脑部处理模块、PKCE 中继功能、AgentCore 网关与运行时系统,以及无需泄露共享令牌的按用户分配的 Trino 连接。

发布者:REACTAPP.TOP阅读约 13 分钟2982 词

来源: Building Enterprise MCP on AWS Bedrock AgentCore: Okta SSO + Trino

本指南将逐步构建从原始材料到可运行系统的完整流程,用于在 AWS Bedrock AgentCore 上搭建企业级 MCP:Okta SSO + Trino。重点在于可操作的步骤、明确的检查点,以及可直接放入代码仓库的代码,无需猜测其用途。 在修改代码之前,应先明确输入参数、各步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行相应步骤,而无需猜测隐藏的状态。 除了功能结果外,还需记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在流程从演示环境过渡到共享环境时出现意外账单。

简而言之

简而言之,在修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放,以便操作人员无需查看整个流程即可进行审核。 提供具有严格结构定义和明确副作用标签的工具。主机在自动批准之前需要知道哪些调用会修改状态。

MCP Client → Lambda OAuth Proxy → Bedrock AgentCore Gateway → AgentCore Runtime → Trino

为何将原始令牌交给工具会导致企业级检查失败

为了解释为何将原始令牌传递给工具会导致企业级检查失败,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 应使用结构严格的工具,并为这些工具添加明确的副作用标签。在自动批准之前,系统管理员需要知道哪些调用会改变状态。

架构概览

为便于快速了解架构设计,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任模块,而非复杂的流程链。工具的设计应具备明确的架构规范和清晰的副作用标识,这样主机在自动批准操作之前就能知道哪些调用会改变系统状态。为便于快速了解架构设计,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。除了功能结果外,还应记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外费用。

1. MCP客户端

对于MCP客户端,在修改代码之前需先明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置信息应置于应用程序代码之外,环境文件、密钥存储以及功能标志都应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审核。认证应在网关层面完成,重新授权则在数据层进行——仅凭承载令牌并不足以界定租户边界。

2. Lambda MCP代理(OAuth的核心组件)

对于第2点:Lambda MCP代理(即OAuth处理模块),在修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。需同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。应在网关处进行身份验证,在数据层进行重新授权——仅凭承载令牌并不能作为租户边界。

3. AWS Bedrock AgentCore网关

对于第3点:AWS Bedrock AgentCore网关,在修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。相比庞大的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能明确指向某个特定职责,而非整个复杂的流程。应在网关处进行身份验证,在数据层面重新授权——仅凭承载令牌并不足以界定租户边界。对于第3点:AWS Bedrock AgentCore网关,在修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。除了功能结果外,还应记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外账单。

4. AWS Bedrock AgentCore 运行时

对于 4. AWS Bedrock AgentCore 运行时,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作员无需查看整个系统结构即可进行审核。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。若没有这些记录,调试代理循环将会耗费大量时间。

端到端认证流程

对于端到端的认证流程,在修改代码之前需明确输入参数、各步骤的负责人以及结束条件。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要补充的内容。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试代理将陷入无止境的循环,耗费大量时间。

┌─────────────────────┐
│    MCP Client       │
│ Claude / Claude Code│
└──────────┬──────────┘
           │
           │ OAuth 2.1 + PKCE
           ▼
┌─────────────────────┐
│   Lambda MCP Proxy  │◄──────────────► Okta
│ OAuth / Identity    │   Login + JWT
└──────────┬──────────┘
           │
           │ Bearer JWT
           ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Gateway   │
│ CUSTOM_JWT validation       │
└────────────┬────────────────┘
             │ SigV4
             ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Runtime   │
│ FastMCP Server              │
│                             │
│ list_catalogs               │
│ describe_table              │
│ execute_query               │
└────────────┬────────────────┘
             │
             │ User JWT
             ▼
┌─────────────────────────────┐
│ Enterprise Query Engine     │
│ Per-user authorization      │
└─────────────────────────────┘

深入探讨1:让Lambda代理支持OAuth 2.1协议

在“深入探讨1:让Lambda代理支持OAuth 2.1”中,修改代码之前需先明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 建议使用小型、可测试的单元而非冗长的脚本。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试循环会耗费大量时间。 在“深入探讨1:让Lambda代理支持OAuth 2.1”中,修改代码之前需先明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况可避免在流程执行过程中出现意外账单。

从演示环境过渡到共享环境。

# GET /.well-known/protected-resource
{
  "resource": f"{BASE_URL}/mcp",
  "authorization_servers": [BASE_URL],
}
# GET /.well-known/oauth-authorization-server
{
  "issuer": BASE_URL,
  "authorization_endpoint": f"{BASE_URL}/authorize",
  "token_endpoint":         f"{BASE_URL}/token",
  "registration_endpoint":  f"{BASE_URL}/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
}

在不破坏功能的前提下中继 PKCE

为在不破坏功能的情况下中继 PKCE,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,以便操作人员无需查看整个流程即可进行审计。 提供具有严格结构定义和明确副作用标签的工具。主机需要在自动批准之前知道哪些调用会修改状态。

relay_payload = json.dumps({"u": original_redirect, "s": original_state}).encode()
relay_state   = base64.urlsafe_b64encode(relay_payload).decode().rstrip("=")
params = {
    "client_id":             OKTA_CLIENT_ID,
    "response_type":         "code",
    "redirect_uri":          f"{BASE_URL}/callback",   # <- proxy's callback
    "state":                 relay_state,               # <- client's state, wrapped
    "code_challenge":        qs["code_challenge"],      # <- untouched
    "code_challenge_method": "S256",
}

动态客户端注册

对于动态客户端注册,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要补充的内容。 应提供具有严格结构定义和明确副作用标识的工具。在自动批准之前,主机需要知道哪些调用会改变系统状态。

def handle_register(event):
    req = json.loads(event.get("body") or "{}")
    return _json_resp(201, {
        "client_id": OKTA_CLIENT_ID,
        "redirect_uris": req.get("redirect_uris", []),
        "grant_types":   req.get("grant_types", ["authorization_code"]),
        "response_types": req.get("response_types", ["code"]),
        "token_endpoint_auth_method": "none",
    })

深入探讨2:FastMCP置于AgentCore之后时出现的故障

在深度探讨2中:当FastMCP位于AgentCore之后时会出现故障,因此在修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于庞大的脚本,应优先使用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任模块,而非复杂的流程链。 应提供具有明确结构规范和清晰副作用标签的工具。主机在自动批准之前需要知道哪些调用会修改状态。 在深度探讨2中:当FastMCP位于AgentCore之后时会出现故障,因此在修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应在功能结果旁记录执行时间以及令牌或查询成本。尽早让成本信息可见有助于预防问题。

当路径从演示环境切换到共享环境时,会出现意外的费用。

尖锐问题 #1:没有自定义 HTTP 请求头能传递到容器中

针对“尖锐问题 #1:没有自定义 HTTP 请求头能传递到容器中”这一情况,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志都应集中存放于一个位置,这样操作人员无需查看整个流程即可进行审核。 在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不足以作为租户边界。

meta = payload.setdefault("params", {}).setdefault("_meta", {})
meta["okta_token"]   = token         # the validated user identity
meta["trino_target"] = routing_hint  # optional routing metadata
class CredentialExtractMiddleware:
    async def __call__(self, scope, receive, send):
        # buffer the whole body
        body = await _read_full_body(receive)
        # pull identity out of params._meta
        meta = json.loads(body).get("params", {}).get("_meta", {})
        token = meta.get("okta_token")        # AgentCore also injects the validated caller token as a header —
        # prefer that when present, fall back to _meta / Authorization otherwise
        token = token or _caller_token_from_headers(scope)        _req_okta_token.set(token)        # replay the buffered body so the MCP app can read it too
        await self.app(scope, _replay(body, receive), send)

尖锐问题 #2:AgentCore 会移除 Accept 请求头

对于“锐利边缘#2”:AgentCore会在修改代码之前移除Accept请求头,明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。 在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不足以界定租户边界。

class InjectAcceptMiddleware:
    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            headers = [(k, v) for k, v in scope["headers"] if k != b"accept"]
            headers.append((b"accept", b"application/json, text/event-stream"))
            scope = {**scope, "headers": headers}
        await self.app(scope, receive, send)

深入探讨3:将Okta访问令牌映射到独立的Trino会话

在“深入探讨3:将Okta访问令牌映射到独立的Trino会话”中,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 建议使用小型、可测试的单元而非庞大的脚本。当某个步骤失败时,故障应能指向具体的责任方,而非整个复杂的流程。 在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不能作为租户边界。 在“深入探讨3:将Okta访问令牌映射到独立的Trino会话”中,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 除了功能结果外,还需记录执行时间以及令牌或查询的成本。提前了解成本情况可避免出现意外的费用账单。

当路径从演示环境切换到共享环境时。

def get_connection():
    token = _req_okta_token.get()
    user  = _jwt_email(token)          # e.g. "alice@company.com"
    return backend.connect(
        host=BACKEND_HOST,
        user=user,                      # identity for attribution/authorization
        auth=JWTAuthentication(token),  # the backend re-validates this itself
        http_scheme="https",
    )

授权与只读限制最终应体现在数据层。

为了确保授权与只读限制真正落实在数据层,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。若没有这些记录,调试代理将陷入无限循环,耗费大量时间。

forbidden = ("INSERT", "UPDATE", "DELETE", "DROP", "CREATE",
             "ALTER", "TRUNCATE", "MERGE", "REPLACE", "GRANT", "REVOKE")
if any(query.strip().upper().startswith(kw) for kw in forbidden):
    raise ValueError("Write operations are not allowed.")

连接客户端的两种方式

对于连接客户端的两种方式,在修改代码之前需先定义输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 每次调用都需记录工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试代理将陷入无限循环,耗费大量时间。

{ "mcpServers": { "data": { "type": "http", "url": "https://<lambda-url>/mcp" } } }
{ "mcpServers": { "data": { "type": "http", "url": "http://localhost:8766/mcp" } } }

我们学到的经验

根据我们的经验,在修改代码之前应明确输入参数、该步骤的负责人以及终止标准。操作人员应当能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应选择小型且易于测试的单元。当某个步骤失败时,故障原因应能指向具体的责任方,而非复杂的流程链。每次调用都需记录工具名称、参数哈希值、延迟时间以及执行结果;如果没有这些记录,调试过程将会浪费大量时间。根据我们的经验,在修改代码之前应明确输入参数、该步骤的负责人以及终止标准。操作人员应当能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。在功能结果之外,还需记录执行时间以及令牌或查询成本。提前了解这些成本信息,可以避免在从演示环境过渡到共享环境时出现意外的费用支出。

总结

最后,在修改代码之前,需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置信息应置于应用程序代码之外,环境文件、密钥存储及功能标志应集中存放于一个操作人员可审核的位置,无需阅读整个流程图。此外,应提供具有明确结构规范和显式副作用标签的工具,以便主机在自动批准之前了解哪些调用会修改状态。

操作检查清单

在制定操作检查清单时,同样需要在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。

将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功判定标准,杜绝默许的半完成状态。

在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不能构成租户边界。

在耗时较高的步骤之后设置检查点。当操作员重新尝试后续节点时,恢复流程不应再次计费相同的大型语言模型调用。

将身份信息从客户端传递到下游存储系统,而无需将所有用户统一归为服务账户。

在预算允许的情况下,使用测试数据而非真实的付费 API,在持续集成过程中添加用于检测关键路径的冒烟测试。

在推广该技术栈之前,应先冻结版本、为关键路径生成标准记录,并确认回滚步骤。共享环境需要设置速率限制、进行租户检查,同时明确密钥轮换的负责人。与其展示花哨的一次性演示,不如注重扎实的可靠性。

部署注意事项1:在更大范围推广之前,先锁定镜像版本、设定请求预算,并在测试环境中验证租户隔离效果。

部署注意事项2:在更大范围推广之前,先锁定镜像版本、设定请求预算,并在测试环境中验证租户隔离效果。

部署注意事项3:在更大范围推广之前,先锁定镜像版本、设定请求预算,并在测试环境中验证租户隔离效果。

部署注意事项4:在更大范围推广之前,先锁定镜像版本、设定请求预算,并在测试环境中验证租户隔离效果。

部署注意事项5:在更大范围推广之前,先锁定镜像版本、设定请求预算,并在测试环境中验证租户隔离效果。

部署注意事项6:在更广泛推广之前,先在测试环境中锁定图像、设置请求预算,并验证租户隔离功能。