第 19 章 从静态工具到动态工具池 MCP · Agent架构实操十九 开始测验

从静态工具到动态工具池:一次 MCP 接入重构了 Agent 架构

启动时工具全写死在注册表。接文档服务要写一组 schema/handler,接部署服务又重复。本章用官方 MCP TypeScript SDK 连接本地 allowlist 的 stdio server,发现远程工具,验证后动态发布给 Lead,故障时原子撤销。
⏱ 约 15 分钟 🔌 connect/disconnect_mcp ⚡ 动态注册/撤销 +MCP

🎯 学习目标

学完能答
  • 六个术语(Alias/Connection/Published Tool/Policy/Exposed Tool/Snapshot)各回答什么?最关键的区分是?
  • 进程内 handler dict 算 MCP 吗?缺什么?
  • Published 和 Exposed 的区别?server 说只读就只读吗?
  • connect_mcp 回复里调用新工具会怎样?第几次请求才看到?
  • 断开 alpha 后,beta 的工具会受影响吗?
  • Subagent 和 Teammate 能看到 MCP 管理工具和远程工具吗?

🧠 核心概念

点击展开。
1 · 六个术语
术语回答的问题是否可信
MCP Server Alias本地 allowlist 用哪个稳定名字代表 server本地可信
MCP Connection这个 alias 当前是否有已初始化且存活的协议会话运行时状态
Published MCP Toolserver 通过 tools/list 发布了什么名称/描述/schema外部数据
MCP Tool Policy本地如何给某个远程工具指定 effect本地可信
Exposed MCP Tool远程声明经验证后本地真正暴露什么本地能力
Registry Snapshot一次模型请求及其回复共同使用哪一版工具集合不可变快照
最关键区分:Published vs Exposed server 说「我有一个只读工具」只是发布外部数据。只有本地 allowlist 为这个精确工具名配置了 effect,schema 和名称也通过验证,才成为可执行的 Exposed MCP Tool。
2 · 进程内 handler dict 不是 MCP

旧版做 MCPClient 放 dict[str, callable],tools/call 只是字典取函数。这没证明任何 MCP 行为:没有 transport、没有官方 Client 与 StdioClientTransport、没有 initialize、没有 tools/list 分页、没有 tools/call、没有子进程退出/超时/取消/关闭语义,server 实现和 Agent 同进程。

P19 直接用锁文件中的 @modelcontextprotocol/sdk,传输路径:McpServerSpec → StdioClientTransport → Client.connect(initialize) → Client.listTools(分页) → Client.callTool。演示 server 也是独立 TypeScript 子进程,真启动两进程通过 stdin/stdout 交换 MCP 消息。

3 · 动态注册与原子撤销

连接 demo_alpha 和 demo_beta(都发布 lookup),本地工具名变成 mcp__demo_alpha__lookupmcp__demo_beta__lookup(前缀防同名串线)。

下一次请求才可见 connect_mcp 所在回复里若提前调用新工具,该调用稳定得到 unknown_tool。下一次模型请求才看到新工具。Registry Snapshot 是不可变快照,一次请求和回复共用同一版工具集合。

断开 alpha 后只撤销 alpha 的全部定义,beta 仍可调用(原子撤销,不影响其他 connection)。超时或子进程退出会撤销该 alias 的全部工具。Runner 关闭后所有 MCP connection 与 stdio 子进程都被回收。三类故障三种迁移:远程 isError:true → mcp_remote_error,连接保留;调用超时 → mcp_timeout,连接丢弃;子进程退出/transport 断 → mcp_connection_lost,连接丢弃,空闲时由 Watchdog 主动撤销。撤销按对象身份而不只按名字——同名工具被另一连接重新注册过,按名撤销会误删别人的。

4 · 权限来自本地,不来自 server 声明

MCP server 可以在 description 中声称自己只读,但权限只能来自本地可信策略(MCP Tool Policy)。远程业务错误不泄露 server 私有错误细节(脱敏)。effect 由本地 allowlist 指定,不是 server 说了算。

这和第3章一脉相承:模型只能提出调用,是否执行由 Harness 决定。MCP server 也是外部不可信声明,本地策略才是硬边界。补充三道本地边界:① connect_mcp 只接受 alias,不接 command/args/cwd——否则它退化成另一种 shell 入口;② 本地 policy 集合与远程申报精确相等(不多不少,不是取交集),少发或多发都拒绝整条连接;③ JSON Schema 是执行边界:顶层必须 type:object,拒外部 $ref,发布前用 Ajv 严格编译,调用时先 Ajv 校验再出网,坏参数不出网;错误信封字段全部本地构造,远程私有细节不进上下文。名称规范化后若碰撞(如 lookup-one 与 lookup_one 都变 lookup_one)也拒绝整条连接。

5 · 只进入 Lead,不进入 Subagent/Teammate

动态工具只进入 Lead 的 live ToolRegistry。Subagent 与 Teammate 始终看不到 MCP 管理工具(connect_mcp/disconnect_mcp)和远程工具。

这避免子 Agent 或队友擅自连接/断开 MCP server,也避免远程工具的不可信声明进入隔离执行环境。MCP 生命周期由 Lead 统一管理。

▶️ 交互演示:MCP 连接与动态工具

连接 demo_alpha/demo_beta,看 Published 工具如何经验证变成 Exposed,断开 alpha 后只撤销 alpha 的工具。注意前缀防同名串线。

demo_alpha (stdio 子进程)

demo_beta (stdio 子进程)

🔑 一句话总结

Published≠Exposed:远程声明是外部数据,本地 policy 验证后才暴露;权限来自本地可信策略,不来自 server 声明

QA 测验