ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于 ADK 与 A2A 的 A2UI MCP Apps Proxy Agent 实战:把 MCP 应用代理成可渲染界面

基于 ADK 与 A2A 的 A2UI MCP Apps Proxy Agent 实战:把 MCP 应用代理成可渲染界面 基于 ADK 与 A2A 的 A2UI MCP Apps Proxy Agent 实战把 MCP 应用代理成可渲染界面【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui本文以 samples/community/agent/adk/mcp_app_proxy 示例为主线讲解如何用 Google Agent Development KitADK结合 A2A 协议构建一个托管为 A2A 服务器的 MCP Apps Proxy Agent它作为 MCP 客户端连接外部 MCP Server把资源与工具包装成 A2UI 界面消息再通过 A2A 通道推送给前端渲染。读完本文你将掌握该示例的完整运行流程、启动参数与依赖矩阵、A2UI 双版本协商机制以及McpApp/WebAppFrameUrl/WebAppFrameSrcdoc三类嵌入组件的源码级用法并理解为何必须把外部 Agent 的一切输出当作不可信数据。一、背景为什么要有一个 MCP Apps Proxy AgentMCPModel Context Protocol把工具/资源标准化为可供 LLM 调用的能力但 MCP 本身不负责 UI 表达A2UI 则负责把界面描述surface、catalog、component、数据模型从 Agent 侧传向渲染器。二者叠加后一个自然的架构是让一个 Agent 既当 MCP 客户端去拉取外部应用资源又当 A2A 服务器向远程客户端声明能力并提供推理——这就是本示例所演示的 MCP Apps Proxy 模式。在 pyproject.toml 中可以看到这套依赖组合的意图a2a-sdk[http-server]0.3.0,0.4.0提供 A2A 服务器Starlette 应用与请求处理器google-adk1.28.1、google-genai1.27.0LLM Agent 运行时a2ui-agent-sdk0.2.4A2UI 的 Agent 侧 SDK提供 A2A 扩展、事件转换器与把 A2UI JSON 发给客户端的工具集mcp、anyio作为 MCP 客户端通过 SSE 连接外部 MCP Serveruvicorn、starlette承载 HTTP 服务click提供 CLI 入口litellm提供模型路由。整个示例演示了两类应用一个计算器来自 MCP Server 的资源和一个Pong 游戏分别以三种方式嵌入MCP App 内嵌 HTML、WebAppFrame URL、WebAppFrame Srcdoc充分展示了代理模式的表达能力。二、目录结构速览samples/community/agent/adk/mcp_app_proxy/ ├── __init__.py # 导入 agent 模块 ├── __main__.py # CLI 入口uv run . 即执行此处 ├── agent.py # McpAppProxyAgentAgent 定义、AgentCard、推理格式 ├── agent_executor.py # McpAppProxyAgentExecutorA2A 执行器与会话准备 ├── tools.py # 六个工具取应用、调计算、解说 Pong ├── pyproject.toml # 依赖与入口声明 ├── catalogs/ │ ├── 0.8/mcp_app_catalog.json # v0.8 组件契约 │ └── 0.9/mcp_app_catalog.json # v0.9 组件契约McpApp / WebAppFrame* / Pong* └── pong_base.html / pong_engine.js / pong_mcp_bridge.js # Pong 的 HTML 引擎与 MCP 桥接三、快速开始从克隆到启动原文档给出的前置条件是 Python 3.9 与 UV依赖安装工具。需要说明的是pyproject.toml 中实际声明的是requires-python 3.10因此以仓库实际为准建议使用 Python 3.10 及以上版本并安装 UV 以复现本文命令。步骤一启动提供 MCP App 的 MCP ServerProxy Agent 本身不生产计算器界面它需要先有一个暴露应用的 MCP Server。仓库在 samples/community/mcp/mcp-apps-calculator 提供了配套示例具体步骤如下cd samples/community/mcp/mcp-apps-calculator/apps/src yarn install yarn build # 产出自包含的 apps/public/calculator.html回到mcp-apps-calculator目录后以 SSE 传输方式默认端口 8000启动服务器cd samples/community/mcp/mcp-apps-calculator uv run .该服务器对外暴露两个能力详见其 README资源ui://calculator/app计算器应用 UItext/html;profilemcp-app工具calculate执行add/subtract/multiply/divide四种算术参数为operation、a、b。Proxy Agent 的get_calculator_app与calculate_via_mcp两个工具正是分别消费这两项能力。步骤二配置 .env 环境变量进入示例目录并准备环境文件cd samples/community/agent/adk/mcp_app_proxy cp .env.example .env # 编辑 .env 填入真实 API Key切勿提交 .env 到版本库若你拉取的仓库副本中未附带.env.example模板直接新建.env并填写下表变量即可。变量清单依据main.py 与 tools.py 的os.getenv调用整理环境变量默认值说明GEMINI_API_KEY无Gemini API Key当GOOGLE_GENAI_USE_VERTEXAI不为TRUE时必填缺失会触发MissingAPIKeyError并退出退出码 1GOOGLE_GENAI_USE_VERTEXAI未设置设为TRUE时走 Vertex AI可绕过GEMINI_API_KEY检查LITELLM_MODELgemini/gemini-2.5-flash-lite模型标识若以gemini/开头启动时会剥掉前缀交给Gemini(model...)MCP_SERVER_HOSTlocalhost上游 MCP Server 主机MCP_SERVER_PORT8000上游 MCP Server 端口SSE 端点拼为http://host:port/ssePONG_SERVER_URLhttp://localhost:8081/pong_app_web_frame_srcdoc.htmlSrcdoc 方式下远程拉取 Pong HTML 的地址步骤三启动 Proxy Agentuv run .CLI 支持两个参数--host默认localhost与--port默认10006。启动成功后Agent 会在http://localhost:10006上以 A2A 服务器形式对外服务并输出 AgentCard。同时它在启动时加载双版本推理格式v0.8 / v0.9与对应 catalog因此客户端可根据自身能力完成版本协商。四、架构与请求处理链路源码级4.1 服务器入口与对象组装main.py 的main()把整个服务拼装起来链路非常清晰agent McpAppProxyAgent(modelGemini(modelgemini_model), base_urlbase_url) agent_executor McpAppProxyAgentExecutor(base_urlbase_url, agentagent) request_handler DefaultRequestHandler(agent_executoragent_executor, task_storeInMemoryTaskStore()) server A2AStarletteApplication(agent_cardagent.agent_card, http_handlerrequest_handler) app server.build()McpAppProxyAgent负责声明能力与运行 AgentMcpAppProxyAgentExecutor继承自google.adk.a2a.executor.a2a_agent_executor.A2aAgentExecutor负责把 A2A 请求转成 ADK 的 Runner 执行DefaultRequestHandlerInMemoryTaskStore提供任务存储与请求分发InMemoryTaskStore说明任务状态仅存内存重启即失A2AStarletteApplication把 AgentCard 与请求处理器包装成 Starlette 应用最后挂上CORSMiddleware允许来源为http://localhost:5173典型的本地前端开发端口并allow_credentialsTrue、放行全部方法与请求头随后交给uvicorn.run(app, hosthost, portport)。4.2 Agent 定义角色提示词、工具与技能声明agent.py 中McpAppProxyAgent是核心类几处关键设计角色提示词三段式ROLE_DESCRIPTION、WORKFLOW_DESCRIPTION、UI_DESCRIPTION被传给DirectJsonFormat.generate_system_prompt()生成系统提示。其中ROLE_DESCRIPTION明确要求不要手动构造 JSON由工具自动处理并规定各场景必须调用哪个工具如请求计算器→get_calculator_app、计算动作→calculate_via_mcp、commentate_pong动作→commentate_pong_game且只调用工具不回复文本。双版本 Runner 与推理格式构造时对VERSION_0_8与VERSION_0_9各构建一个DirectJsonFormatcatalog 指向catalogs/{version}/mcp_app_catalog.jsonaccepts_inline_catalogsTrue和对应的 UI Runner另有一个默认的纯文本 Runner。get_runner(version)根据版本分发无版本走文本 Runner有版本走对应 UI Runner。AgentCard 声明capabilities.streamingTrue并通过get_a2ui_agent_extension()为每个版本生成 A2A 扩展声明同时声明 4 个AgentSkillopen_calculatorOpen Calculatoropen_pong_mcpOpen Pong with MCP Appsopen_pong_web_frameOpen Pong with WebApp URLopen_pong_web_frame_srcdocOpen Pong with WebApp Srcdoc这些技能让远程客户端在握手阶段即可发现这个 Agent 能开什么应用。Agent 工具与规划器LlmAgent挂载 6 个工具见下一节并启用BuiltInPlanner(thinking_configtypes.ThinkingConfig(include_thoughtsTrue))让模型先思考再行动disallow_transfer_to_peersTrue关闭 Agent 间转交。4.3 ExecutorA2UI 扩展激活与会话准备agent_executor.py 有两个值得展开的机制bypass_tool_checkTrue构造函数中传入A2uiEventConverter(bypass_tool_checkTrue)。代码注释点明了动机工具响应转 A2A part 时跳过工具调用检查避免真的去执行SendA2uiJsonToClientTool工具调用从而减少 token 消耗并防止 LLM 幻觉改写内嵌在 A2UI JSON 里的 MCP Apps HTML 内容显著提升可靠性。也就是说工具直接返回{validated_a2ui_json: messages}结构体由事件转换器直接透传给客户端。会话准备中的能力协商_prepare_session()调用try_activate_a2ui_extension(context, self._agent.agent_card)根据客户端消息元数据A2UI_CLIENT_CAPABILITIES_KEY决定激活哪个 A2UI 版本选择对应 Runner 与推理格式随后把base_url写入会话状态并通过追加一个 author 为system的EventEventActions.state_delta向会话注入三个系统键system:a2ui_enabled→Truesystem:a2ui_catalog→ 按客户端能力get_selected_catalog()选出的 catalogsystem:a2ui_examples→ 示例当前为占位空串源码中以 TODO 注释标注从文件加载这三个键由get_a2ui_enabled()/get_a2ui_catalog()/get_a2ui_examples()三个读取函数消费而SendA2uiToClientToolset正是依赖这些 Provider 判断是否启用 A2UI、用哪个 catalog。五、核心工具实现剖析tools.pytools.py 定义了 6 个工具它们共同演示MCP 能力 → A2UI 消息的完整转换。5.1 计算器从 MCP Resource 到 McpAppget_calculator_app(tool_context)的调用链是用sse_client(fhttp://{MCP_SERVER_HOST}:{MCP_SERVER_PORT}/sse)建立 MCP 连接ClientSession初始化session.read_resource(ui://calculator/app)读取应用资源把 HTML 内容做url_encoded: urllib.parse.quote(html_content)前缀编码这是让 Agent 把大段 HTML 当作单值传递、避免 JSON 结构被干扰的技巧组装两条 v0.9 消息createSurfacesurfaceId: calculator_surfacecatalogId指向本仓库的 0.9 catalog与updateComponents根组件McpApp含htmlContent、title: Calculator、allowedTools: [calculate]设置tool_context.actions.skip_summarization True后返回{validated_a2ui_json: messages}。其中allowedTools声明内嵌应用允许调用哪些 Agent 工具配合计算器 HTML 里的tools/call请求见 mcp-apps-calculator 的说明 按钮把运算委托给 MCP Server构成闭环。5.2 计算委托calculate_via_mcp当用户在计算器界面点击按钮发出calculate动作后Agent 调用calculate_via_mcp(operation, a, b)同样通过 SSE 建立 MCP 会话session.call_tool(calculate, arguments{operation: ..., a: ..., b: ...})取result.content[0].text直接以文本形式返回给用户因此它不需要构造 A2UI JSON。这个工具演示了界面动作 → Agent 工具 → MCP 工具 → 结果回填的代理模式最典型的交互。5.3 Pong 的三种嵌入方式Pong 游戏被用来对比三种嵌入外部 Web 应用的方式三者共享同一套 A2UI 骨架createSurfacesurfaceId: pong_surface→updateDataModel初始化/pong_state含player_score、cpu_score、commentary→updateComponentsPongLayout组合McpApp/WebAppFrame*与PongScoreBoard计分板字段通过 JSON Pointer 绑定/pong_state/player_score等路径工具组件内容来源特点get_pong_mcp_app_jsonMcpApp本地pong_base.html拼入pong_mcp_bridge.js、pong_engine.js后 url_encoded内容内嵌在 A2UI 消息里allowedTools: [commentate_pong]、allowedFunctions: [showWinnerModal]、data.paths.state → /pong_stateget_pong_app_web_frame_jsonWebAppFrameUrlurl: http://localhost:8081/pong_app_web_frame.html以 iframe URL 加载通过allowedEvents声明commentate_pong事件 schema、allowedFunctions声明showWinnerModal、mutableData授权写回、config.matchingScore: 5get_pong_app_web_frame_srcdoc_jsonWebAppFrameSrcdoc从PONG_SERVER_URL默认http://localhost:8081/pong_app_web_frame_srcdoc.html用urllib抓取 HTMLtimeout2.0UA 为A2UI-AgenthtmlContent直接作为 srcdoc 渲染其余声明与 URL 版一致三种方式共享共享状态 授权边界的契约内嵌应用可发事件、调宿主函数、按mutableData授权写回数据模型但一切行为都以 catalog 中声明的 schema 为界。每次加载时还会重置PONG_CURRENT_SCORE全局计数。5.4 AI 实时解说commentate_pong_gamecommentate_pong_game(tool_context, game_event)是事件驱动 Agent 回写数据模型的示例从事件上下文提取game_event如Score: Player 1 - CPU 0 (player scored).用google.genai.Client()调用LITELLM_MODEL同样剥掉gemini/前缀生成一段不超过 15 词、霓虹街机风格的解说词返回一条updateDataModel消息把解说词写入/pong_state/commentary驱动PongScoreBoard的 commentary 字段刷新。该工具全程只调用、不输出文本配合 AgentCard 与系统提示中的规则验证了外部应用事件 → Agent 推理 → 数据模型更新 → 界面响应的完整回路。六、自定义 CatalogMcpApp / WebAppFrame 组件契约Proxy Agent 之所以能输出合法 A2UI 消息是因为 catalogs/0.9/mcp_app_catalog.json 定义了组件的 JSON Schema 契约。几个要点McpApp必填component与htmlContentDynamicString可选title、allowedTools允许调用的 Agent 工具名数组、allowedFunctions允许调用的宿主函数名数组、data.paths自定义状态键到 JSON Pointer 的映射。在tools.py中还能看到allowedTools在 v0.9 下既出现于McpAppPong 版也以allowedEvents对象形式出现在WebAppFrame*上——前者是数组工具名后者是事件名 → JSON Schema的映射。WebAppFrameUrl必填component与url外部 iframe 地址可选height、allowedEvents、allowedFunctions、mutableData内嵌应用被授权改写的父数据模型键及其取值 schema、config静态初始化配置不做响应式绑定、data.paths。WebAppFrameSrcdoc必填component与htmlContent原始 HTML 字符串经 srcdoc 渲染其余字段与 URL 版一致。PongScoreBoard/PongLayout/Column示例自定义的布局与计分板组件playerScore/cpuScore/commentary支持字面量或 path 绑定。$defs.anyComponent用oneOf把这些组件收拢为合法组件集合作为 LLM 生成界面时的约束空间。对照 catalogs/0.8/mcp_app_catalog.json 可以看到 v0.8 契约更朴素htmlContent/title使用literalString/path结构allowedTools仅为字符串数组且没有WebAppFrame*——这解释了为何 Pong 的三种方式只在 v0.9 消息中出现get_calculator_app与 Pong 工具产出的消息version均为v0.9而 v0.8 分支则是通过DirectJsonFormat(versionVERSION_0_8, ...)为旧客户端保留兼容路径。七、双版本协商与整体数据流把上面的源码证据串起来一次典型请求的完整链路是客户端如http://localhost:5173的前端通过 A2A 连接http://localhost:10006先获取 AgentCard含 A2UI 扩展与 4 个技能声明McpAppProxyAgentExecutor._prepare_session()依据客户端消息元数据调用try_activate_a2ui_extension()完成版本协商选定 v0.8 / v0.9 之一的 Runner 与推理格式并把 catalog、启用标志注入会话状态用户提出打开计算器→ ADK 的BuiltInPlanner先思考再调用get_calculator_app工具经 SSE 连接上游 MCP Server读取ui://calculator/app把 HTML 编码后组装成createSurfaceupdateComponents的validated_a2ui_jsonA2uiEventConverter(bypass_tool_checkTrue)把工具输出直接转成 A2A part 下发给客户端客户端渲染McpApp用户点 → 发送calculate动作 → Agent 调calculate_via_mcp→ MCP 工具计算 → 结果以文本返回用户Pong 场景则由commentate_pong_game以updateDataModel回写解说词驱动计分板刷新。八、安全边界把外部 Agent 视为不可信实体原文档的 Disclaimer 是本文不可省略的组成部分。示例代码仅用于演示 A2UI 与 A2A 协议机制生产环境中必须把不受你直接控制的 Agent 视为潜在不可信实体所有操作数据都应视为不可信输入包括其 AgentCard、消息、artifacts 与任务状态。例如恶意 Agent 可以在name、skills.description等字段中植入精心构造的数据若未经净化直接拼进 LLM 提示词可能引发prompt injection提示注入攻击。UI 定义与数据流同样不可信恶意 Agent 可能伪造合法界面诱骗用户phishing、通过属性值注入恶意脚本XSS、或生成过度复杂的布局拖垮客户端DoS。嵌入式内容尤其要警惕如果应用支持 iframe、web view 等可选嵌入内容需防止被导向恶意外部站点。开发者责任必须实现输入净化、Content Security PolicyCSP、对可选嵌入内容做严格隔离、安全地管理凭据。参考仓库中WebAppFrame*组件的allowedEvents/allowedFunctions/mutableData设计思路——以 schema 收窄授权面本身就是一种安全实践内嵌应用只能调用白名单内的事件与函数、只能改写被授权的数据路径。九、延伸阅读上游 MCP App 服务与资源/工具说明samples/community/mcp/mcp-apps-calculator/README.md本示例组件契约catalogs/0.9/mcp_app_catalog.json、catalogs/0.8/mcp_app_catalog.json其他 ADK 示例对照file_upload_summarizer同款GEMINI_API_KEY/LITELLM_MODEL环境变量约定A2A 与 A2UI 的协议与扩展规范specification/v1_0/docs、docs/public/concepts【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表