用 Amazon Bedrock AgentCore 构建可交互的 MCP Apps

2026-09-12 21 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

MCP Apps 让模型调用工具的结果不再局限于纯文本。通过交互式 HTML widget,同一个 MCP Server 可以把表单、筛选器、图表或审批控件交给 AI Host 渲染,让用户直接在对话界面中完成操作。

这套能力的关键在于:MCP Apps 是与 Host 解耦的标准扩展。只要 ChatGPT、Claude 或其他 AI Host 支持对应扩展,服务端就可以复用同一份工具和 UI 逻辑。Amazon Bedrock AgentCore 则可以作为部署和运行 MCP Server 的托管基础设施。

MCP App 的组成

一个可交互的 MCP App 通常包含三层:

  1. MCP 工具:接收模型或用户传入的参数,执行查询、计算或业务操作。
  2. 结构化结果:除了给模型阅读的文本,还返回 widget 所需的数据。
  3. HTML widget:在 AI Host 中展示结果,并通过事件再次调用 MCP 工具。

例如,模型可以调用 search_orders 查询订单。服务端返回订单列表后,widget 显示筛选器和“加载更多”按钮。用户点击按钮时,widget 发起新的工具调用,而不需要让模型重新猜测用户意图。

这会带来两个工程上的变化。工具接口需要同时考虑模型可读性和 UI 可读性;widget 也必须假设宿主环境不同,不能依赖某个特定聊天产品的私有 DOM 或浏览器 API。

先定义稳定的工具契约

建议让 MCP 工具返回明确的结构化数据,并把展示层需要的字段与内部数据库对象分开。下面是一个可以改造的 Python 示例。它使用标准库模拟 MCP 工具处理逻辑,便于在接入具体 MCP SDK 或 AgentCore Runtime 时复用数据模型。

# app.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import json

ORDERS = [
    {"id": "A-1001", "customer": "Acme", "status": "shipped", "total": 128.50},
    {"id": "A-1002", "customer": "Globex", "status": "pending", "total": 86.00},
]


def search_orders(status=None):
    rows = [o for o in ORDERS if not status or o["status"] == status]
    return {
        "items": rows,
        "count": len(rows),
        "filters": {"status": status or "all"},
    }


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != "/tools/search_orders":
            self.send_error(404)
            return

        length = int(self.headers.get("Content-Length", "0"))
        args = json.loads(self.rfile.read(length) or b"{}")
        result = search_orders(args.get("status"))
        body = json.dumps(result).encode("utf-8")

        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


if __name__ == "__main__":
    print("Listening on http://127.0.0.1:8080")
    HTTPServer(("127.0.0.1", 8080), Handler).serve_forever()

运行并测试:

python app.py
curl -s http://127.0.0.1:8080/tools/search_orders \
  -H 'Content-Type: application/json' \
  -d '{"status":"pending"}'

实际接入 MCP SDK 时,可以把 search_orders 注册为 MCP tool,把返回的 itemscountfilters 放入工具结果,并额外声明关联的 HTML 资源或 widget。具体注册函数和资源 URI 取决于所使用的 SDK 版本,因此不要把示例中的 HTTP 路径直接当成 AgentCore 的固定协议。

编写可移植的 HTML Widget

Widget 应该把宿主通信封装在一个很小的适配层里。业务 UI 只处理数据和用户动作,不直接依赖 ChatGPT 或 Claude 的私有对象。

下面的 HTML 可以独立运行。它默认通过普通 HTTP 请求访问上面的示例服务;接入 MCP Host 时,把 callTool 替换为 Host 提供的 MCP 调用桥接即可。

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>订单查询</title>
  <style>
    body { font: 16px system-ui, sans-serif; max-width: 720px; margin: 32px auto; padding: 0 16px; }
    select, button { font: inherit; padding: 8px 10px; margin-right: 8px; }
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    th, td { border-bottom: 1px solid #ddd; padding: 10px 6px; text-align: left; }
    #message { color: #666; margin-top: 16px; }
  </style>
</head>
<body>
  <h1>订单查询</h1>
  <label>
    状态
    <select id="status">
      <option value="">全部</option>
      <option value="pending">待处理</option>
      <option value="shipped">已发货</option>
    </select>
  </label>
  <button id="refresh">刷新</button>
  <div id="message"></div>
  <table>
    <thead><tr><th>订单号</th><th>客户</th><th>状态</th><th>金额</th></tr></thead>
    <tbody id="orders"></tbody>
  </table>

  <script>
    async function callTool(name, arguments_) {
      // 在 MCP Host 中替换成宿主提供的工具调用桥接。
      const response = await fetch("http://127.0.0.1:8080/tools/" + name, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(arguments_)
      });
      if (!response.ok) throw new Error("工具调用失败: " + response.status);
      return response.json();
    }

    async function loadOrders() {
      const message = document.querySelector("#message");
      const tbody = document.querySelector("#orders");
      message.textContent = "加载中...";
      tbody.replaceChildren();
      try {
        const status = document.querySelector("#status").value;
        const result = await callTool("search_orders", { status });
        for (const order of result.items) {
          const row = document.createElement("tr");
          for (const value of [order.id, order.customer, order.status, `$${order.total.toFixed(2)}`]) {
            const cell = document.createElement("td");
            cell.textContent = value;
            row.appendChild(cell);
          }
          tbody.appendChild(row);
        }
        message.textContent = `共 ${result.count} 条订单`;
      } catch (error) {
        message.textContent = error.message;
      }
    }

    document.querySelector("#refresh").addEventListener("click", loadOrders);
    loadOrders();
  </script>
</body>
</html>

保存为 widget.html 后,可以启动静态服务器进行本地验证:

python -m http.server 8000

然后打开 http://127.0.0.1:8000/widget.html。浏览器直接打开本地文件时,跨域策略可能阻止请求,因此使用静态服务器更接近实际部署环境。

部署到 AgentCore 时要关注什么

可以把 MCP Server 打包成 AgentCore Runtime 支持的部署单元,并通过环境变量注入数据库连接、下游 API 地址和认证配置。部署流程中的具体命令应以当前 AWS SDK、CLI 和 AgentCore 文档为准,但工程结构可以保持稳定:

mcp-app/
├── server.py          # MCP tool 和资源注册
├── widgets/
│   └── orders.html     # 交互式 HTML widget
├── requirements.txt
└── Dockerfile

一个最小的容器配置示例如下,适合作为改造起点:

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

ENV PORT=8080
EXPOSE 8080
CMD ["python", "server.py"]

部署前应验证以下边界:

  • 认证与授权:每次工具调用都要基于当前用户或会话做权限检查,不能只在 widget 中隐藏按钮。
  • 输入校验:模型生成的参数仍然是不可信输入,状态值、分页参数和资源 ID 都需要服务端校验。
  • 跨 Host 行为:不同 Host 可能有不同的 iframe 沙箱、尺寸限制、网络策略和事件支持。widget 应减少外部依赖,并为加载失败提供可读错误。
  • 结果大小:不要把大表格或敏感字段一次性返回。使用分页、摘要和按需查询,降低模型上下文与 UI 渲染压力。
  • 幂等性:涉及创建、退款、删除等写操作时,增加确认步骤、幂等键和审计日志。

一份落地检查清单

开始接入时,可以按这个顺序推进:

  1. 先用纯 MCP tool 跑通数据查询和错误处理。
  2. 为工具结果定义稳定的 JSON schema,区分模型摘要字段和 widget 展示字段。
  3. 单独在浏览器中测试 HTML widget 的空数据、加载中、失败和超长数据状态。
  4. 在 AgentCore Runtime 中部署后,再分别验证支持 MCP Apps 的 AI Host。
  5. 对工具调用延迟、权限失败、重复提交和 Host 不支持 widget 的情况添加日志与降级文本。

MCP Apps 的价值不只是“把 HTML 放进聊天窗口”。真正需要设计的是一份跨 Host 的交互契约:MCP 工具负责可靠的业务能力,结构化结果负责稳定的数据边界,widget 负责高效的人机操作。将这三部分分开后,同一套服务才更容易在 Amazon Bedrock AgentCore、ChatGPT、Claude 以及其他兼容 Host 之间持续复用。


相关推荐