
A2UI over MCP 完整实战指南用 MCP 工具与资源为任意客户端提供可交互 A2UI 界面【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui本篇指南以 A2UI 仓库中的官方教程 docs/public/guides/a2ui_over_mcp.md 为核心骨架结合仓库内的 Recipe 演示服务端 server.py、静态模板 recipe_form.json 与 recipe_a2ui.json、客户端引擎 engine.ts 以及 A2UI Agent SDK 源码完整讲解如何在 MCP Server 上通过Tools 与 Embedded Resources对外提供富交互的 A2UI 界面。读完本文你将掌握 A2UI over MCP 的展示与数据分离架构、_meta.ui协议细节、目录协商Catalog Negotiation、用户动作回传与错误处理并具备从零搭建一个可运行的 MCP 服务端与宿主客户端的实战能力。一、为什么需要 A2UI over MCP分离展示层与数据层MCPModel Context Protocol为 Agent 与外部工具之间提供了标准化的通信通道但其tools/call的经典返回形态是纯文本或结构化 JSON交互体验有限。A2UI 的目标是让任何 MCP 兼容客户端LLM Host、桌面应用、Web 前端能够直接渲染出富交互界面。将两者叠加后核心思路是把用户界面拆成两个互不耦合的层次静态展示模板通过 MCP Resources 提供包含组件树createSurface与updateComponents与数据绑定如/title、/cookTime、/image的布局通过resources/read以自定义 URI如a2ui://recipe-form、a2ui://recipe-card和 MIME 类型application/a2uijson提供。由于模板不包含硬编码的业务数据客户端可以安全地本地缓存无需重复拉取。动态数据更新通过 MCP Tools 提供工具执行时服务端只返回模板所需的动态值封装成一条 A2UIupdateDataModel消息。数据与展示完全解耦同一模板可复用于任意多份数据。在此基础上还有两个关键协议要素_meta.ui工具元数据在工具定义tools/list与CallToolResult中携带_meta.ui对象声明该工具关联的展示模板资源 URI 与 MIME 类型_meta: { ui: { resourceUri: a2ui://recipe-card, mimeType: application/a2uijson } }客户端侧解析与水合Hydration宿主客户端读取_meta.ui.resourceUri先查本地模板缓存首次加载时通过resources/read拉取初始化 surface 布局再把工具响应中的动态updateDataModel应用到 surface 上最终由 A2UI Renderer 完成渲染。[!IMPORTANT]MIME 类型统一静态模板资源与动态工具负载都使用application/a2uijson。在工具响应中数据模型更新被放在EmbeddedResource内同时保留一个TextContent作为兜底文本。这种标识方式让客户端应用可以把负载直接路由给 A2UI 处理器Processor。二、快速开始运行仓库自带的 Recipe 演示仓库在 samples/community/mcp/a2ui-over-mcp-recipe 提供了一套开箱即用的 MCP Recipe 演示完整覆盖表单选择 → 动态生成菜谱卡片的典型流程。环境前置Python3.10 或更高版本uv快速 Python 包管理工具Node.js18 或更高版本用于运行 MCP Inspector。启动 MCP 服务端git clone https://github.com/a2ui-project/a2ui.git cd a2ui/samples/community/mcp/a2ui-over-mcp-recipe # 启动 MCP 服务器默认 SSE 传输端口 8000 uv run .从服务端源码 server.py 可以看到启动入口支持三个命令行参数参数默认值说明--port8000SSE 监听的端口--transportsse传输类型可选stdio或sse--bypass-verificationTrueflag是否跳过 A2UI 能力capability校验服务端在启动时即用DirectJsonFormatv0.9 版本 Basic Catalog初始化 schema 管理器并对 recipe_a2ui.json 与 recipe_form.json 两份模板做启动时预校验selected_catalog.validator.validate(...)确保对外发布的模板一定合法。该示例的依赖在 pyproject.toml 中声明为mcp、a2ui-agent-sdk0.1.1、anyio、click、httpx等。[!NOTE] 示例通过[tool.uv.sources]使用本地路径引用 A2UI Agent SDK。你自己的项目应从 PyPI 安装pip install a2ui-agent-sdk方案 A通过 MCP Inspector 交互验证另开一个终端启动 MCP Inspectornpx modelcontextprotocol/inspectorlatest --web --transport sse --server-url http://localhost:8000/sse打开http://localhost:6274按以下步骤验证协议行为点击List Resources→ 可以看到a2ui://recipe-form与a2ui://recipe-card两个资源Read 一个资源 → 内容为静态 A2UI 展示模板createSurfaceupdateComponents包含数据绑定点击List Tools→ 可以看到get_recipe_form_a2ui与get_recipe_a2ui两个工具都通过_meta.ui关联到各自的展示模板资源运行get_recipe_form_a2ui→ 工具返回初始表单选中状态包裹在updateDataModel消息中默认值Grilled、Chicken运行get_recipe_a2ui并传入自定义参数 → 工具返回动态菜谱详情同样包裹在updateDataModel消息中。方案 B运行 Recipe 客户端 Web 应用[!NOTE] 在 A2UI 仓库内运行内置示例应用需要使用 Yarn workspacesyarn install/yarn dev。在仓库之外的项目中你可以使用任意包管理器npm、pnpm、yarn。新开终端进入客户端目录cd client安装依赖yarn install启动 Vite 开发服务器yarn dev浏览器访问http://localhost:5173。应用加载后客户端通过 SSE 连接 MCP 服务器并执行get_recipe_form_a2ui读取_meta.ui拉取并缓存a2ui://recipe-form展示模板随后应用返回的updateDataModel填充默认选项Grilled、Chicken。在表单中选择其他烹饪方式与蛋白质并点击Get Recipe客户端执行get_recipe_a2ui拉取a2ui://recipe-card模板并在右侧动态渲染出对应的菜谱卡片。客户端实现可参考 client/app.tsA2uiRecipeApp组件通过A2uiMcpEngine完成连接与渲染firstUpdated()中读取sse_url查询参数或VITE_SSE_URL环境变量默认http://127.0.0.1:8000/sse随后调用executeTool触发入口表单工具。客户端还通过 CSS 变量如--a2ui-color-primary: #ff5a5f对 A2UI 组件做主题定制说明宿主应用可以完全掌控渲染风格。更多示例请浏览 samples/community/mcp/。三、解耦架构详解展示与数据分离的完整交付流下面把前面提到的两层分离落实成一条端到端的交付流水线1. 工具调用Tool Invocation Client → tools/call如 get_recipe_a2ui→ MCP Server ↓ 计算动态值 ↓ Client ← CallToolResultupdateDataModel← MCP Server _meta.ui: { resourceUri: a2ui://recipe-card } 2. 模板解析首次拉取后进入缓存 若 a2ui://recipe-card 不在客户端缓存中 Client → resources/read (a2ui://recipe-card) → MCP Server Client ← TemplatecreateSurface、updateComponents← MCP Server 3. Surface 水合Hydration Client 将工具响应中的 updateDataModel 应用到 surface。 A2UI Renderer 更新显示。1. 将展示模板定义为 MCP Resources通过resources/list与resources/read暴露静态布局模板app.list_resources() async def list_resources() - list[types.Resource]: return [ types.Resource( uria2ui://recipe-form, nameRecipe Form, mimeTypeapplication/a2uijson, descriptionStatic form allowing users to pick cuisine and protein., ), types.Resource( uria2ui://recipe-card, nameRecipe Card, mimeTypeapplication/a2uijson, descriptionStatic recipe card layout template., ), ] app.read_resource() async def read_resource(uri: str) - list[ReadResourceContents]: if str(uri) a2ui://recipe-form: return [ ReadResourceContents( contentjson.dumps(recipe_form_json), mime_typeapplication/a2uijson, ) ] if str(uri) a2ui://recipe-card: return [ ReadResourceContents( contentjson.dumps(recipe_a2ui_json), mime_typeapplication/a2uijson, ) ] raise ValueError(fUnknown resource: {uri})模板资源的内容来自两个 JSON 文件recipe_form.json 定义了表单模板——包含标题Text、两个ChoicePickercookingStyle 与 protein 的 chips 选项、以及携带callMcpTool动作的提交Buttonrecipe_a2ui.json 定义了菜谱卡片模板——Card→Column→ 图片与文本树其中所有可变内容如/title、/rating、/cookTime都通过{path: /xxx}形式进行数据绑定模板本身不含任何具体数据值。这正是不依赖硬编码数据、可安全缓存的关键。服务端能力校验实际示例的read_resource与call_tool处理器都会先调用verify_a2ui_capability()见 server.py从 MCP 会话的client_params.capabilities.model_extra中读取a2ui.clientCapabilities[v0.9].supportedCatalogIds校验客户端是否声明支持 Basic Catalog v0.9。默认开启--bypass-verification便于演示关闭后不满足能力的客户端会收到isErrorTrue的错误响应。2. 在工具定义上声明 UI 元数据在工具定义中声明其关联的展示模板 URItypes.Tool( nameget_recipe_a2ui, titleGet Recipe A2UI, descriptionReturns recipe data and links to the recipe-card template., inputSchema{ type: object, properties: { cookingStyle: { type: array, items: {type: string}, description: Selected cooking styles, }, protein: { type: array, items: {type: string}, description: Selected proteins, }, }, additionalProperties: True, }, _meta{ ui: { resourceUri: a2ui://recipe-card, mimeType: application/a2uijson, } }, )注意inputSchema中additionalProperties: True的使用它允许客户端把cookingStyle/protein以数组形式传入由表单的ChoicePicker多选产生服务端在 server.py 中取出数组首元素作为选中值并带有一个弹性兜底——若未命中RECIPES字典的键则回退到(Baked, Salmon)这条默认数据。3. 在工具执行时返回动态数据在call_tool处理器中把动态状态封装成updateDataModel消息并附上_meta.uiapp.call_tool() async def handle_call_tool( name: str, arguments: dict[str, Any] ) - types.CallToolResult: if name get_recipe_a2ui: # Resolve selected recipe from user arguments style_list arguments.get(cookingStyle, [Baked]) protein_list arguments.get(protein, [Salmon]) style style_list[0] if style_list else Baked protein protein_list[0] if protein_list else Salmon recipe RECIPES.get((style, protein)) # Generate lightweight updateDataModel payload data_model_update [ { version: v0.9, updateDataModel: { surfaceId: recipe-card, path: /, value: { title: recipe[title], rating: recipe[rating], reviews: recipe[reviews], cookTime: recipe[cookTime], prepTime: recipe[prepTime], servings: recipe[servings], image: recipe[image], }, }, } ] return types.CallToolResult( content[ types.TextContent( typetext, textfGenerated recipe: {recipe[title]}, ), types.EmbeddedResource( typeresource, resourcetypes.TextResourceContents( uria2ui://recipe-card/data, mimeTypeapplication/a2uijson, textjson.dumps(data_model_update), ), ), ], _meta{ ui: { resourceUri: a2ui://recipe-card, mimeType: application/a2uijson, } }, )响应中同时包含两类contentTextContent作为对 LLM 可读的兜底说明EmbeddedResource携带真正的 A2UIupdateDataModel负载其uri形如a2ui://recipe-card/datamimeType为application/a2uijson。客户端引擎在 engine.ts 的extractA2uiMessages()中按顺序扫描content优先解析type resource的 JSON 文本也兼容直接以 JSON 字符串出现的text项。4. 客户端解析与水合以宿主引擎为例官方客户端引擎 engine.ts 完整实现了这套协议逻辑可作为宿主端实现蓝本连接与能力声明connectServer()通过SSEClientTransport连接 MCP 服务端并在Client构造参数中声明capabilities.a2ui.clientCapabilities[v0.9].supportedCatalogIdshttps://a2ui.org/specification/v0_9/basic_catalog.json与服务端握手协商工具预发现listTools()后遍历所有工具把_meta?.ui?.resourceUri存入toolUiResources映射同时以server:tool和tool两种键登记供后续调用时使用模板缓存getOrFetchTemplate()用templateCache按 resource URI 缓存模板未命中时调用readResource再筛选出mimeType A2UI_MIME_TYPE的内容解析为 JSONsurface 创建与数据应用executeTool()依次执行——调用工具 → 从响应_meta或缓存映射解析模板 URI → 拉取模板并在 surface 不存在时用processor.processMessages(template)创建 surface → 用extractA2uiMessages()提取updateDataModel并再次processMessages应用数据 → 触发onSurfaceChange事件让 UI 刷新。整个过程由MessageProcessor来自a2ui/web_core/v0_9统一管理。四、Catalog 协商在发送 A2UI 前先对齐目录服务端向客户端发送 A2UI 之前双方必须先确认可用的目录Catalog集合。A2UI 协议为此提供了两种协商方式可按架构选择。方案 AMCP 初始化阶段协商推荐MCP 是有状态会话协议最高效的做法是在建连时一次性声明能力。客户端在initialize请求的capabilities下声明 A2UI 支持{ jsonrpc: 2.0, method: initialize, id: init-123, params: { protocolVersion: 2025-11-25, clientInfo: { name: a2ui-enabled-client, version: 1.0.0 }, capabilities: { a2ui: { clientCapabilities: { v0.9: { supportedCatalogIds: [ https://a2ui.org/specification/v0_9/basic_catalog.json ] } } } } } }服务端在整个会话期间保存该状态这正是上面verify_a2ui_capability()从session.client_params读取数据的依据。supportedCatalogIds声明客户端支持的具体目录标识Basic Catalog v0.9 的目录 ID 为https://a2ui.org/specification/v0_9/basic_catalog.json其在仓库中的对应物可见于 specification/v0_9/catalogs/basic。方案 B逐消息元数据协商适用于无状态服务端若服务端必须保持无状态客户端可以在每次工具调用的_meta字段中携带 A2UI 能力{ jsonrpc: 2.0, method: tools/call, id: id-123, params: { name: generate_report, arguments: {date: 2026-03-01}, _meta: { a2ui: { clientCapabilities: { v0.9: { supportedCatalogIds: [ https://a2ui.org/specification/v0_9/basic_catalog.json ], inlineCatalogs: [] } } } } } }除supportedCatalogIds外这里还出现了inlineCatalogs字段——它允许客户端直接内联传入目录定义而非仅引用 ID适用于自定义目录或动态目录场景。关于动态目录与 schema 管理的更多细节可参见仓库中的 Agent 开发指南 docs/public/guides/agent-development.md。五、处理用户动作从 Button 点击回到 MCP 工具调用像Button这样的交互组件可以触发动作Action这些动作会以 MCP 工具调用的形式回传服务端。Recipe 表单中的提交按钮就是典型例子——它在 recipe_form.json 中声明了callMcpTool动作动作上下文里包含server、tool、cookingStyle路径绑定/cookingStyle与protein路径绑定/protein。1. 在 A2UI JSON 中为组件定义动作{ id: confirm-button, component: { Button: { child: confirm-button-text, action: { event: { name: confirm_booking, context: { start: /dates/start, end: /dates/end } } } } } }2. 客户端把动作转成工具调用用户点击按钮后客户端针对 surface 状态解析数据绑定如/dates/start并携带协议要求的动作字段发起工具调用{ jsonrpc: 2.0, method: tools/call, id: id-456, params: { name: a2ui_action, arguments: { name: confirm_booking, surfaceId: booking-surface, sourceComponentId: confirm-button, timestamp: 2026-03-20T12:00:00Z, context: { start: 2026-03-20, end: 2026-03-25 } } } }在 Recipe 客户端中动作由 app.ts 的handleAction()统一路由当action.name callMcpTool时调用引擎的handleMcpCallTool(action.context)后者从上下文解析server与tool字段其余键作为工具参数再委托给executeTool()精确路由到目标服务端执行。3. 服务端处理动作app.tool() async def a2ui_action( name: str, surfaceId: str, sourceComponentId: str, timestamp: str, context: dict[str, Any], ) - types.CallToolResult: Handle A2UI user actions. if name confirm_booking: # Process the booking, then return confirmation UI return types.CallToolResult(content[ types.TextContent( typetext, textfBooking confirmed for {surfaceId}: {context[start]} to {context[end]} ) ]) raise ValueError(fUnknown action: {name})[!NOTE] 动作的全部五个字段name、surfaceId、sourceComponentId、timestamp、context都是 A2UI 规范要求的必填项。在工具参数中完整声明所有字段可以防止 MCP SDK 剥离surfaceId等字段从而丢失动作的来源 surface 上下文。示例服务端 server.py 中的action工具正是为演示这一模式而设计的。六、错误处理客户端把渲染与校验错误回报服务端客户端可以通过工具调用把 A2UI 渲染与校验错误上报给服务端{ jsonrpc: 2.0, method: tools/call, id: id-789, params: { name: a2ui_error, arguments: { code: VALIDATION_FAILED, surfaceId: booking-surface, path: /components/0/text, message: Failed to parse A2UI payload. } } }服务端处理app.tool() async def a2ui_error( code: str, surfaceId: str, message: str, path: str | None None, ) - types.CallToolResult: Handle A2UI client errors. # Log the error, retry, or send a fallback UI return types.CallToolResult(content[ types.TextContent( typetext, textfAcknowledged error {code} on surface {surfaceId}: {message} ) ])错误参数包括code错误码如VALIDATION_FAILED、surfaceId出错 surface、message错误描述以及可选的path出错的组件路径。示例服务端中的error工具见 server.py同样实现了此协议。服务端收到后可以记日志、重试或下发兜底 UI。七、Verbalization 与可见性控制用 Resource Annotations 控制 LLM 上下文通过 MCP 的Resource Annotations可以控制 LLM 在后续对话轮次中能否读取A2UI 负载——例如把原始 JSON 对 LLM 隐藏、仅对用户渲染从而避免大段 UI 结构挤占 LLM 的上下文窗口a2ui_resource types.EmbeddedResource( typeresource, resourcetypes.TextResourceContents( uria2ui://training-plan-page, mimeTypeapplication/a2uijson, textjson.dumps(a2ui_payload) ), # Show the UI to the user, but hide the raw JSON from the LLM annotationstypes.Annotations(audience[user]) )audience取值与行为的对应关系Audience行为(empty)用户与 LLM 均可见[user]渲染给用户对 LLM 上下文隐藏[assistant]可供 LLM 后续推理使用不渲染给用户这一机制让开发者能够在给用户看的界面与给模型推理的结构之间精确分配上下文预算。八、生产化使用 A2UI Agent SDK 管理 schema 与校验面向生产环境A2UI Agent SDK替你承担了 schema 管理、校验与提示词生成等繁琐工作pip install a2ui-agent-sdkfrom a2ui.strategies.schema import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog # Initialize the schema manager with the basic catalog schema_manager A2uiSchemaManager( catalogs[BasicCatalog.get_config()], ) # Validate A2UI output before sending selected_catalog schema_manager.get_selected_catalog() selected_catalog.validator.validate(a2ui_payload)从源码结构看agent_sdks/python/a2ui_agent/src/a2ui/schema/manager.py 中的A2uiSchemaManager继承自DirectJsonFormat而get_selected_catalog()定义在 agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/direct_json/format.pyBasicCatalog则位于 agent_sdks/python/a2ui_agent/src/a2ui/basic_catalog/provider.py通过get_config(version...)可指定协议版本如 v0.9。示例服务端正是这样在启动时对模板 JSON 做预校验的见 server.py——先validate再对外提供服务从源头保证下发的 A2UI 负载合法。关于 schema 管理、动态目录与流式输出的完整说明请参阅 Agent 开发指南。九、下一步学习路径A2UI 规范 v0.9 —— 完整协议参考组件画廊 —— 浏览可用组件在 A2UI Surface 中嵌入 MCP 应用 —— 在 A2UI 内嵌入基于 HTML 的 MCP 应用客户端搭建 —— 构建一个展示 A2UI 的渲染器。【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考