工具生态毕业项目:搭一个完整的工具生态 本节摘要:Phase 13 把每一块都教了。这个毕业项目把它们接成一个生产形态的系统:一个含工具 + 资源 + 提示 + 任务 + UI 的 MCP 服务端,边缘的 OAuth 2.1,RBAC 网关,多服务端客户端,一次 A2A 子 Agent 调用,入采集器的 OTel 追踪,CI 里的工具投毒检测,以及一份 AGENTS.md + SKILL.md 打包。做完你能为每一处架构选择辩护。这是本章的收尾——把分散的原语熔成一个整体,验证你已经吃透。 学习目标 阅读完本节,你应当能够: 组装一个暴露工具、资源、提示与带 应用的任务的 MCP 服务端。 在服务端前放一个强制 RBAC 与钉住哈希的 OAuth 2.1 网关。
本节摘要:Phase 13 把每一块都教了。这个毕业项目把它们接成一个生产形态的系统:一个含工具 + 资源 + 提示 + 任务 + UI 的 MCP 服务端,边缘的 OAuth 2.1,RBAC 网关,多服务端客户端,一次 A2A 子 Agent 调用,入采集器的 OTel 追踪,CI 里的工具投毒检测,以及一份 AGENTS.md + SKILL.md 打包。做完你能为每一处架构选择辩护。这是本章的收尾——把分散的原语熔成一个整体,验证你已经吃透。
阅读完本节,你应当能够:
ui:// 应用的任务的 MCP 服务端。要交付一个「研究并报告」系统:
ui:// 资源;每一步都记进 OTel。Phase 13 的全部原语都登场了。这不是玩具——2026 年 Anthropic(Claude Research 产品)、OpenAI(用 Apps SDK 的 GPTs)与第三方交付的生产研究助手系统都是这个形状。
[user] -> [client] -> [gateway (OAuth 2.1 + RBAC)] -> [research MCP server] | +- MCP tool: arxiv_search (pure) +- MCP resource: notes://recent +- MCP prompt: /research_topic +- MCP task: generate_report (long) +- MCP Apps UI: ui://report/current +- A2A call: writer-agent (tasks/send) | +- OTel GenAI spans
agent.invoke_agent ├── llm.chat (kick off) ├── mcp.call -> tools/call arxiv_search ├── mcp.call -> resources/read notes://recent ├── mcp.call -> prompts/get research_topic ├── a2a.tasks/send -> writer-agent │ └── task transitions (opaque internals) ├── mcp.call -> tools/call generate_report (task-augmented) │ └── tasks/status polling │ └── tasks/result (completed, returns ui:// resource) └── llm.chat (final synthesis)
一个 trace id,每个 span 都带对的 gen_ai.* 属性。
alice 有 research:read、research:write,能调所有工具;bob 只有 research:read,不能调 generate_report。RBAC = { "alice": {"scopes": ["research:read", "research:write"], "tools": ["arxiv_search", "notes.read", "generate_report"]}, "bob": {"scopes": ["research:read"], "tools": ["arxiv_search", "notes.read"]}, # 无 generate_report } def authorize(user, tool): if tool not in RBAC[user]["tools"]: raise PermissionError(f"{user} 无权调 {tool}")
最终的 generate_report 任务返回内容块加一个 ui://report/current 资源。客户端宿主(Claude Desktop 等)在沙箱 iframe 里渲染交互仪表盘。仪表盘含排序后的论文列表、被引数,以及一个按钮——用户点任何一篇就调 host.callTool('summarize_paper', {arxiv_id})。
整体以如下结构交付:
research-system/ AGENTS.md # 项目约定 skills/ run-research/ SKILL.md # 顶层工作流 servers/ research-mcp/ # MCP 服务端 pyproject.toml src/ agents/ writer/ # A2A Agent gateway/ config.yaml # RBAC + 钉住清单
用户用 docker compose up 部署。Claude Code、Cursor、Codex、opencode 用户都能通过调用 run-research skill 驱动系统。
| 节 | 毕业项目用到的 |
|---|---|
| 01-05 | 工具接口、跨厂商可移植、并行调用、schema、lint |
| 06-10 | MCP 原语、服务端、客户端、传输、资源 + 提示 |
| 11-14 | 采样、roots + elicitation、异步任务、ui:// 应用 |
| 15-17 | 工具投毒、OAuth 2.1、网关 + 注册中心 |
| 18 | A2A 子 Agent 委托 |
| 19 | OTel GenAI 追踪 |
| 20 | LLM 层的路由网关 |
| 21 | SKILL.md + AGENTS.md 打包 |
设计要点:毕业项目的价值不在「能跑」,而在每个架构选择都能辩护——为什么 OAuth 2.1 而非 API Key、为什么 A2A 而非把写作 Agent 内联、为什么钉哈希、为什么 OTel。把这些「为什么」讲清,才是真正吃透 Phase 13。
把网关、服务端、A2A、OTel 串成一条主线:
trace = new_trace() with Span(trace, "agent.invoke_agent", "INTERNAL") as root: auth = oauth_pkce_flow(resource=GATEWAY_URL) for tool_call in client.plan(): with root.child("mcp.call", "CLIENT", {"gen_ai.tool.name": tool_call.name}): gateway.authorize(user, tool_call.name) # RBAC if tool_call.name == "generate_report": task = mcp_task_start(tool_call) # 异步任务 ui = mcp_task_await(task) # 轮询 → ui:// 资源 elif tool_call.delegated: with root.child("a2a.tasks/send", "CLIENT"): artifact = a2a_send(writer_agent, tool_call.message) # 内部不透明 else: result = gateway.call(tool_call, auth)
| 维度 | 本毕业项目 | 纯 MCP 玩具 | 纯 A2A 多 Agent |
|---|---|---|---|
| 鉴权 | OAuth 2.1 + RBAC 网关 | 无/弱 | JWT 卡片 |
| 工具 | MCP | MCP | 无 |
| 委托 | A2A(不透明) | 无 | A2A |
| 追踪 | OTel GenAI 全链 | 无 | 部分 |
| 打包 | AGENTS.md + SKILL.md | 无 | 无 |
| 形态 | 生产形态 | 学习用 | 实验性 |
💡 心法:生产级研究助手 = MCP(工具层)+ A2A(协作层)+ OAuth 2.1 网关(控制平面)+ OTel(可观测)+ Skills(可复用工作流)。没有任何单一原语能单独成事,组合才是 Phase 13 的真正考点。
本节产出 outputs/skill-ecosystem-blueprint.md——给定一个产品诉求(研究、摘要、自动化),这个 skill 产出完整架构:用哪些 MCP 原语、哪些网关控制、哪些 A2A 调用、哪些遥测、哪种打包。
code/main.py 把前面各节的模式拼成一个可运行的 demo。全 stdlib、全进程内,你能从头读到尾。它为「研究并报告」场景跑完整流:与网关握手、模拟 OAuth 2.1、合并 tools/list、把 generate_report 作为任务跑、A2A 调 writer、返回 ui:// 资源、发 OTel span。可重点看:一个 trace id 贯穿每跳、网关策略挡住第二个用户写、任务生命周期经 working → completed 并同时返回文本与 ui:// 内容、A2A 调用的内部状态对协调器不透明、AGENTS.md 与 SKILL.md 是另一个 Agent 复现工作流所需的唯一文件。
追踪单 trace:运行 code/main.py,留意单一 trace id 与 span 嵌套,数 demo 触到了 Phase 13 的多少个原语。
加第二个后端:加第二个后端 MCP 服务端(如 bibliography),确认网关把它的工具合并进同一命名空间。
接真实 A2A:用一个跑在子进程上的真实 writer Agent 替换假的那个,用第 19 节的测试具。
加 PII 脱敏:在协调器与 LLM 之间的路由网关里加一步 PII 脱敏,确认用户查询里的邮件被清洗。
写交接 AGENTS.md:为将维护这个系统的队友写一份 AGENTS.md,读它应不到五分钟,且足以让他在 Cursor 或 Codex 里驱动这个毕业项目。
ui:// 报告 → OTel 全链。gen_ai.* 属性。恭喜你走完了第 14 章「工具与协议」。从第 01 节的工具接口与结构化 I/O 出发,我们一路走过了函数调用的深水区、并行与流式、结构化输出、schema 设计;再进入 MCP 的全部原语——服务端、客户端、传输、资源与提示、采样、roots 与 elicitation、异步任务、ui:// 应用;随后是安全的纵深——工具投毒、OAuth 2.1、网关与注册中心、生产鉴权;接着把视野扩展到 Agent 之间,讲了 A2A 协议;最后用 OpenTelemetry GenAI 把一切串成可观测的 trace,用 LLM 路由层统一厂商管理,用 Skills 与 Agent SDK 把工作流变成可移植产物,并在本毕业项目里把它们熔成一个整体。
回顾这一章,有三条主线贯穿始终:
掌握了这些,你已经具备了搭建生产级 Agent 工具生态的全部基础。下一章,我们将进入新的主题——把这套工具与协议的能力,放回到更大的系统与流程中去检验。