news 2026/9/15 4:31:11

在 MCP Apps 中集成 A2UI:构建基于 Python MCP Server 的交互式 UI 应用服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 MCP Apps 中集成 A2UI:构建基于 Python MCP Server 的交互式 UI 应用服务

在 MCP Apps 中集成 A2UI:构建基于 Python MCP Server 的交互式 UI 应用服务

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

导读

本文基于 a2ui 仓库中的samples/community/mcp/a2ui-in-mcpapps/server示例,系统讲解如何用 Python 编写一个 Model Context Protocol(MCP)服务器,将独立打包的 Web 应用作为 MCP App 资源对外提供,并通过 MCP 工具(tools)把原始的A2UI JSON 载荷直接翻译为丰富的交互式 UI 渲染。读完本文,你将掌握 MCP Server 中resourcestools的声明方式、_meta.ui.resourceUri的 UI 模板关联机制、SSE/Stdio 双传输通道的启动方式,以及 A2UI 载荷(dataModelUpdate/surfaceUpdate/beginRendering)如何驱动界面增量更新。

一、示例概览:MCP 生态中的 A2UI 承载方式

本示例位于仓库的 samples/community/mcp/a2ui-in-mcpapps,整体分为三个部分:

  • server/:Python +uv构建的 MCP Server,对外提供 micro-app 资源与交互工具,是本文的主角;
  • client/:Angular 编写的宿主容器应用,通过安全的双 iframe 代理模式加载并隔离运行来自 MCP 的微应用;
  • server/apps/:被托管的微应用源码(Basic 计数器应用与 Editor 生成式文档编辑器),构建为单文件 HTML 后交由 MCP Server 对外提供。

其中,server 端目录的核心文件与职责如下(见 server/README.md):

文件/目录职责
server.pyMCP Server 核心实现,定义 tools、resources 与传输方式(SSE / Stdio)
simple_counter_a2ui.json示例 A2UI 载荷数据文件,供fetch_counter_a2ui工具返回
apps/被托管应用(hosted application)的源码与构建产物目录
apps/public/app.htmlServer 实际对外服务的自包含单文件应用(需先构建生成)

关键约束:该 Server 专门期望服务位于apps/public/app.html的打包产物;该文件缺失或源码变更后,必须先重新构建(详见下文"托管应用构建"一节)。

二、暴露的接口:Resources 与 Tools

MCP Server 通过resources/read提供 MCP App 的 HTML 模板,通过tools/call返回 A2UI 载荷来驱动界面渲染。

2.1 Resources(资源)

  • ui://basic/app:对外提供自包含的apps/public/app.html应用,MIME 类型为text/html;profile=mcp-app

在 server.py 中,list_resources除 basic 外还声明了第二个资源ui://editor/app(Editor 应用),并且read_resource会按 URI 映射到apps/public/下对应的app.html/editor.html文件。这里有一个关键实现细节:resources/read的返回内容必须携带text/html;profile=mcp-appMIME 类型,而不仅仅是resources/list声明时带上——这是 MCP Apps 规范对资源读取的强制要求,也是本示例特意在代码注释中强调的点。

2.2 Tools(工具)

基础计数器相关工具(对应simple_counter_a2ui.json):

  • get_basic_app:返回ui://basic/app资源的引用。该工具通过_meta.ui.resourceUri预声明 UI 模板,宿主通过resources/read获取模板,而不会把模板作为内嵌资源混进工具结果中(见 server.py);
  • fetch_counter_a2ui:读取simple_counter_a2ui.json,返回初始计数器 A2UI 载荷用于测试渲染;
  • increase_counter:对内存计数器自增并返回标准的dataModelUpdate,实现 UI 组件更新。

生成式编辑器相关工具(同一 server 文件中的扩展工具):

  • get_editor_app:打开 Editor A2UI 应用视图(通过_meta.ui.resourceUri关联ui://editor/app);
  • smart_editor_get_controls:根据用户选中的文本,让 Gemini 生成 A2UI 调参控件(slider / checkbox / select);
  • smart_editor_apply:把用户在控件上调整后的参数提交给 Gemini 重写文本。

每个工具都通过_meta.ui.visibility声明可见性(如["model"]仅模型可见、["app"]允许应用调用)。需要说明的是,这些 Editor 扩展工具属于仓库中同一 server 代码的一部分,是理解"服务端如何为 MCP App 提供完整交互闭环"的重要补充,但计数器工具才是本文主体 README 所聚焦的基础演示。

三、快速开始:运行 MCP Server

3.1 前置条件

  • Python 3.10+
  • uv(推荐的 Python 包管理工具,可依据server/.python-version自动管理 Python 版本)。

3.2 方式 A:SSE 传输(默认)

server/目录下启动,默认监听127.0.0.1:8000等待 SSE 连接:

cd samples/community/mcp/a2ui-in-mcpapps/server uv run python server.py --transport sse --port 8000

由于程序默认参数就是sse与端口8000,也可以直接简写为:

uv run python server.py

首次运行前建议先执行uv sync按 pyproject.toml 安装依赖(clickmcp[cli]sse-starlettestarletteuvicorngoogle-genaipython-dotenv等)。

3.3 方式 B:Stdio 传输

使用标准输入输出与宿主进程通信,适合作为子进程被 Agent 宿主拉起:

uv run python server.py --transport stdio

传输方式的切换由 server.py 中的 click 参数控制:

@click.command() @click.option("--port", default=8000, help="Port to listen on for SSE") @click.option( "--transport", type=click.Choice(["stdio", "sse"]), default="sse", help="Transport type", )

3.4 两种传输的底层实现差异

  • SSE:基于 Starlette + Uvicorn 搭建 HTTP 服务,Route("/sse")处理事件流连接,Mount("/messages/")接收客户端 POST 消息;同时注册了CORSMiddleware(源码中带有明确警告:生产环境必须将allow_origins=["*"]收紧为宿主客户端的具体来源,例如http://localhost:4200);
  • Stdio:通过mcp.server.stdio.stdio_server在标准输入输出上建立双向消息流,并用anyio.run驱动事件循环。

四、深入源码:工具调用如何返回 A2UI 载荷

handle_call_tool是界面驱动的核心,其返回的CallToolResult中内嵌了 MIME 类型为application/a2ui+json(源码常量A2UI_MIME_TYPE)的TextResourceContents(见 server.py):

  • get_basic_app/fetch_counter_a2ui:把simple_counter_a2ui.json的内容序列化后,以a2ui://ping-result为 URI 的内嵌资源返回;
  • increase_counter:修改全局计数器后,构造一个dataModelUpdate消息,通过surfaceId: "ping-result"contents中的{"key": "counter", "valueNumber": COUNTER}精准更新数据模型中指定 key 的值,前端据此重新渲染score-value文本。

这种"工具结果携带 A2UI JSON"的方式,让 Agent 宿主可以直接把载荷交给 A2UI 渲染层解析,实现一次工具调用即完成一次界面更新

4.1 初始 A2UI 载荷剖析

simple_counter_a2ui.json(见 simple_counter_a2ui.json)是 A2UI v0.8 规范的典型三段式消息序列:

  1. dataModelUpdate:声明surfaceIdpath: "/"与数据内容(如counter = 0);
  2. surfaceUpdate:以组件清单描述 UI 树——Card包住Column,内部依次是Text("Pong from MCP Server (v0.8)!")、Row(含计数器卡片与按钮)。其中按钮通过"action": {"name": "increase_counter", "context": []}用户点击映射回 MCP 工具调用,而计数文本则通过"text": {"path": "/counter"}绑定数据模型
  3. beginRendering:指定root: "root"作为渲染入口,通知渲染器开始绘制该 surface。

这一结构清楚展示了 A2UI 的"数据模型 + 组件树 + 渲染指令"分离设计,以及action与数据绑定如何支撑起完整的交互闭环。

五、托管应用的构建要求

Server 对外服务的是apps/public/app.html这个自包含单文件产物。若该文件缺失,或你修改了apps/src/下的托管应用源码,都必须重新构建(详见 apps/README.md)。

5.1 构建工作流

server/apps/src/目录下执行:

cd server/apps/src yarn install yarn build:all

build:all会先执行 Angular 编译,再触发node inline.js把产物内联为单文件public/app.html

5.2 为什么要单文件内联

由于 MCP App 的安全隔离要求(通常依赖沙箱 iframe,例如srcdoc场景),应用必须是一个不依赖外部请求的独立 HTML 文件。inline.js脚本的工作流程为:

  1. 收集 Angular 原始构建产物(dist/raw下的index.html及 JS/CSS);
  2. 把所有 JavaScript 与 CSS 动态内联进index.html
  3. 输出自包含的app.htmlpublic/目录。

以 Editor 应用的 inline.js 为例,实现中还有两个值得注意的细节:

  • esbuild 强制打包:Angular 17+ 默认启用 ES Module 代码分割,main.js会依赖外部相对路径 chunk;而在沙箱srcdociframe 中这些相对请求会被浏览器拦截(缺乏可访问的 base origin),因此脚本调用npx esbuild --bundle --format=esm将所有 split chunks 合并为单一文件后再内嵌;
  • 清理 modulepreload:Angular 自动注入的<link rel="modulepreload">在强制打包后会产生无意义的 404/CORS 网络错误,脚本会将其全部剔除,同时剥离 sourceMappingURL 以减小体积。

5.3 构建产物与 Git 忽略

dist/(原始构建输出)与public/(最终打包产物)均被 git 忽略:全新 clone 的仓库默认没有这些产物,Server 即使没有它们也能启动,但对应 surface 无法加载。因此在端到端运行示例前,至少需要构建一个应用。仓库根目录的 样例总览 提供了 Editor 与 Basic 两种应用的构建命令,以及先yarn install链接工作区包的提醒。

六、端到端运行与完整通信流程

6.1 启动宿主客户端

除 Server 外,还需要构建宿主容器(Angular Client)的沙箱桥接资源并在 4200 端口启动:

cd samples/community/mcp/a2ui-in-mcpapps/client yarn install yarn build:sandbox # 生成 client/public/sandbox_iframe/sandbox.{js,html} yarn start # 打开 http://localhost:4200 查看运行中的宿主

6.2 消息流转时序

样例总览文档用 sequence diagram 描述了完整闭环(简化版):

  1. 宿主从托管服务器加载,向 MCP Server 发tools/list,得到带_meta.ui.resourceUri(指向ui://模板)的工具定义;
  2. 宿主调用应用入口工具(tools/call),随后通过resources/read拉取声明的 HTML 模板;
  3. 宿主把模板 HTML 交给沙箱代理,代理在隔离 iframe 中加载 MCP App;
  4. App 内 CTA 触发后,通过 代理 → 宿主 → Server 的链路转发工具调用;
  5. Server 返回 A2UI JSON 载荷,经宿主与代理中继后交由 App 内的 A2UI Surface 渲染组件;
  6. 用户在 A2UI 组件上点击时,action被映射为tools/call请求,再走同一链路回传dataModelUpdate,最终完成增量渲染更新。

宿主侧的 client/src/app/app.ts 中可见其实现要点:监听window message事件并校验event.originevent.source(安全边界),按每个工具声明的_meta.ui.visibility构建allowedTools集合,并通过ui/notifications/sandbox-proxy-readyui/notifications/sandbox-resource-ready等约定消息与沙箱代理握手。

七、扩展:生成式文档编辑器中的 A2UI 动态控件

除基础计数器外,同一 Server 还演示了"LLM 动态生成 A2UI 控件"的高级用法(实现于 smart_editor_agent.py):

  • generate_controls(text, full_text):把选中文本交给 Gemini(默认模型gemini-2.5-flash,可通过GENAI_MODEL环境变量覆盖),通过response_schema约束输出 JSON,得到 2~3 个调参控件;随后把控件映射为 A2UI 组件——Slider(0–100 数值)、CheckBox(布尔值)、MultipleChoice(下拉选项),连同dataModelUpdatesurfaceUpdatebeginRendering三段消息返回给前端;
  • apply_revision(text, user_parameters):解析control_config_json中的控件定义,把用户当前值格式化为自然语言指令(如- Verbose vs. Concise: 0.30 (0 meaning low...)),再要求 Gemini 输出text_before / original_text / revised_text / text_after四段式修订结果,实现基于用户调参的文本重写

该扩展说明:MCP Server 返回的 A2UI 载荷不必是静态 JSON——服务端可以借助 LLM 按上下文实时生成界面结构,这正是 A2UI 面向 Agent 生态的灵活之处。

八、总结与排查建议

  • 启动失败:确认 Python ≥ 3.10,且已uv sync;SSE 模式下可开启日志观察连接建立(源码中logging.basicConfig(level=logging.INFO)就是为此准备的);
  • surface 加载空白:检查apps/public/app.html是否已构建,Server 在read_resource找不到文件时会抛出ValueError: Resource file not found...
  • 跨域问题:本地调试允许 CORS*,但生产环境务必按源码警告收紧allow_origins
  • 换用 UI 模板:新增应用时,需同时修改list_resourcesread_resource中的 URI 映射,以及对应工具声明的_meta.ui.resourceUri

本示例的价值在于给出了一个可运行的最小参考实现:从资源声明、工具返回 A2UI 载荷、单文件应用构建,到宿主沙箱隔离与增量渲染,完整串联了 MCP Apps 与 A2UI 的集成路径。相关可继续研读的仓库文件包括:server.py、simple_counter_a2ui.json、apps/README.md 与 样例总览。

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 4:30:29

国产修图软件悟空图像PhotoSir实测:能否替代Photoshop?

1. 为什么我停了Photoshop&#xff0c;开始认真用悟空图像PhotoSir先交代一下背景。我平时的工作流里&#xff0c;修图这东西绕不开。公众号头图、活动海报、产品详情页、偶尔还要给客户出一版快手方案&#xff0c;几乎天天都在跟图层、蒙版、钢笔路径打交道。以前电脑上常年挂…

作者头像 李华
网站建设 2026/9/15 4:29:44

多摄像头融合的上帝视角监控系统:从单应性矩阵到实时目标追踪

1. 项目概述1.1 我为什么想做一个“上帝视角”系统先说一个很现实的场景&#xff1a;我手头管理着园区里三个分散的监控区域&#xff0c;加起来二十多路摄像头。传统监控画面是一块块小格子&#xff0c;保安盯得眼睛都快瞎了&#xff0c;还是容易出现“人在画面A消失、在画面B没…

作者头像 李华
网站建设 2026/9/15 4:29:00

混凝土多边形骨料二维建模技术与实践

1. 混凝土多边形骨料二维建模概述在建筑材料研究中&#xff0c;混凝土的细观结构建模一直是学术界和工程界关注的重点。多边形骨料作为混凝土中最主要的组成部分&#xff0c;其几何形态和分布特征直接影响着混凝土的宏观力学性能。传统的圆形骨料模型虽然计算简便&#xff0c;但…

作者头像 李华
网站建设 2026/9/15 4:28:18

MeanShift纹理分割实战:从带宽参数到区域mask

简介&#xff1a;一份基于OpenCV的均值漂移分割算法实现&#xff0c;用于抑制图像中的细小纹理并完成纹理分割&#xff0c;适合图像处理、计算机视觉方向的学习者和开发者参考。压缩包仅1个cpp源码文件&#xff0c;包体大小973B&#xff0c;轻量易读&#xff0c;代码结构清晰&a…

作者头像 李华
网站建设 2026/9/15 4:27:27

从awesome-llm-apps看LLM应用:RAG、Agent与工程落地

大概从 2023 年开始&#xff0c;GitHub 上冒出过一个很有意思的现象&#xff1a;满屏都是“awesome-xxx”的仓库。这种清单型项目&#xff0c;说穿了就是一个领域里的老玩家&#xff0c;把散落在全网的高质量工具、开源项目、论文、教程&#xff0c;手动整理成一份精挑细选的目…

作者头像 李华