MCP tool server、browser/python 工具与工作流接口#

前面的结构化生成与 API 章节已经覆盖了 function calling、tool parser 和 Responses API 的 background 语义,但如果没有一章把“工具到底怎样进入工作流”单独讲透,第 6 节对外部接口的覆盖仍然会缺一条关键支线。很多读者会自然把工具系统理解成“模型输出一个函数调用 JSON,再去执行一下”。当前实现已经明显比这更厚:

  • MCPToolServer
  • 有内建 browser / python 工具人格
  • 有 tool session
  • 有把工具描述注入请求上下文的流程
  • 还有 background / streaming 场景下的模式限制

这章真正要补齐的,就是这条“工具工作流接口”本身,而不是再重复一遍 parser 或 tool-call schema。

先把工具放回 Responses 工作流,而不是函数调用 JSON#

如果只站在 function calling 角度看,工具似乎只是模型输出的一种结构化结果;从 Responses API 的实现看,工具实际上在更早就进入了工作流。下面这张图的价值就在于把这一点明确画出来:

flowchart LR
    Req["ResponsesRequest + tools"] --> Server["tool_server / MCPToolServer / DemoToolServer"]
    Server --> Session["tool session(s)"]
    Session --> Ctx["HarmonyContext / StreamingHarmonyContext"]
    Ctx --> Resp["Responses API generation workflow"]

图里最重要的一点是:工具不是在模型之外平行运行的插件,而是会在请求开始前就被准备成上下文资源。只要这一点稳住,读者就不再会把“工具工作流”误读成 parser 的附属品。

tool_server.py 才是这条链最值得先读的入口#

这棵树特别适合作为代码阅读起点,因为它把工具接口的两种主要人格都直接摆出来了:

  • ToolServer 抽象基类
  • MCPToolServer
  • DemoToolServer

这说明系统并不是把所有工具逻辑硬编码进某个 handler,而是先抽象出一层正式的工具接口。对系统书来说,这层抽象特别值钱,因为它能把“工具从哪里来”这个问题从 execution 主线里拆开:

  • 有的工具来自远端 MCP server
  • 有的工具是本地内建人格

来源不同,但都会在更下游被折成同一套 request-scoped session 和上下文能力。

MCPToolServer 真正做的事情,比“连一下 MCP”要厚得多#

它至少承担这些动作:

  • 通过 SSE + ClientSession 连接 MCP server
  • initialize() / list_tools()
  • 把 MCP 的 schema 修整成 Harmony 所需形式
  • 为每个 tool name 维护 URL 和描述
  • 提供 get_tool_session(...)

这说明 MCP 接入并不是“把远端工具列表原样抄过来”,而是要做一次正式的 schema 适配和 session 生命周期管理。也正因为如此,把它放在结构化生成与 API 章节里,比放在某个附录更合理:这里已经是系统正式对外暴露的工作流接口,而不是外围脚本。

DemoToolServer 说明工具并不等于外部 MCP#

这一点对读者尤其重要。DemoToolServer 明确提供了本地内建工具人格:

  • browser
  • python

这意味着工具工作流的核心问题不是“有没有外部 MCP server”,而是“工具能力怎样被抽象成统一接口,再按请求注入工作流”。只要这一点说清楚,读者就更容易理解为什么 built-in tool 和 MCP tool 看起来差异很大,却仍然会在 Responses API 里共享同一条上下文进入路径。

serving_responses.py 把工具真正接进请求,而不是等模型输出后再拼#

这一层非常关键。create_responses() 的真正价值不只是构造 GenerateReqInput(...),而是在更早的地方显式处理:

  1. 根据 request 里的 tool types 决定是否启用 browser / python
  2. tool_server.get_tool_session(tool_name) 建 session
  3. 把 tool description 注入系统消息或开发者消息
  4. HarmonyContext / StreamingHarmonyContext 里把这些 session 一起带进去

这说明工具不是在模型输出后才突然出现,而是在生成前就已经被作为上下文资源准备好了。对技术书来说,这一层特别重要,因为它能把“工具工作流”从 parser 和 schema 章节里真正解放出来,成为一条独立可理解的工作流接口。

为什么 background / streaming 模式下有些工具会被拒绝#

源码里有一条很值得系统书明确指出的保护逻辑:

  • 如果 tool_serverMCPToolServer
  • 并且 request 是 background 或 streaming
  • 又用了某些特定工具类型

系统会直接返回错误。

这说明工具工作流不是“只要接上就到处都能用”。不同工具接口和不同运行模式之间其实存在明确的兼容边界。换句话说,这里讲的不只是“能不能接工具”,还包括“在哪些工作流人格下不该接工具”。这类模式限制特别像成熟系统书该主动替读者讲透的边界。

HarmonyContext / StreamingHarmonyContext 把工具能力真正变成 request-scoped 资源#

这是这一章特别值得强调的另一点。工具 session 最终不是挂在某个全局单例里,而是作为请求上下文的一部分进入具体工作流。这意味着工具能力在这里有几个很清晰的特征:

  • 按请求注入
  • 生命周期跟着请求或 background task 走
  • 和 request identity 天然绑定

这是一种非常健康的系统设计,因为它避免了“工具能力是全局环境”的模糊状态。对于读者来说,这也能直接改变对工具工作流的理解:它不是系统外的脚本能力,而是 request-scoped capability。

这条链和 parser / Responses / maintainer 视角天然会回扣#

工具工作流很适合作为一本系统书里的桥章节,因为它天然连接三块内容:

  • 结构化生成:tool-call constraint 和 parser
  • Responses API:background task、retrieve / cancel、session 生命周期
  • 维护层:工具工作流出问题时,应该先怀疑 session、tool server,还是 parser

这正是一本“像书”的技术作品会有的咬合感:同一能力不是只在一章出现,而是在不同章节从不同层次被照亮。

最容易出现的三种误判#

第一,误把工具系统理解成纯 function calling JSON 解析。
实际上它还包括 tool server、session、schema trimming 和上下文注入。

第二,误以为 MCP 工具和内建工具没本质区别。
源码里两者在生命周期和模式支持上是明确分开的。

第三,误以为工具只是结果生成后的附加动作。
事实上它们在请求生成前就已经作为上下文资源进入工作流。

真正顺着这条链读源码时,更稳的顺序#

建议按下面顺序:

  1. 先读 tool_server.py
  2. 再看 serving_responses.py 里如何创建 tool session
  3. 再看 HarmonyContext / StreamingHarmonyContext
  4. 最后再回到 parser / tool-call constraint 章节看“模型怎样输出工具调用”

这样读,你会更容易把这条链看成“工具工作流接口”,而不是“再多几个工具相关文件”。

小结#

这一章真正补齐的,是第 6 节里很容易被忽略的一层:工具不是只靠 parser 和 JSON schema 就能工作,它们还需要 tool server、session 和请求上下文这层工作流接口。

把这一层讲清之后,Responses API 和工具工作流才真正像一个完整系统,而不是几个功能点的拼接。