跳转至

第 7 章 协议与集成

工具从哪来,决定了 harness 能接多大的世界。硬编码的工具表只能覆盖一个进程内的能力;要把别人写好的服务接进来,就需要一层协议。本章以 Model Context Protocol(MCP)为主线讲清协议解决什么问题、角色与生命周期怎样划分、原语与传输怎样配合,并给出自建工具服务与版本兼容的判据。规范细节以 2026-07-28 版为准,接口字段随版本演进时以规范页为最终依据。

1. 为什么需要协议

没有协议时,每接一个工具源都要写一套适配:函数签名、传输、鉴权、错误格式各不一样。协议把可变部分收进三样东西——消息格式、能力声明、生命周期,让 harness 只需要实现一次客户端,就能接任意服务端。

协议不解决“工具干什么”的问题:同一个协议下,一个工具可以是只读查询,也可以是删库操作。它解决的是“怎么找到、怎么调用、怎么回结果”,语义与风险仍在工具自身的契约里(第 4 章)与权限层(第 5 章)。把协议当成安全边界,等于假设“接入了外部服务就默认安全”,这两件事没有关系。

MCP 的消息格式建立在 JSON-RPC 之上,调用与通知都有统一外壳;能力以"声明加协商"的方式交换,双方只使用彼此都支持的部分。判据:协议的价值在边界,而不在功能。它不替工具设计 schema(第 4 章),也不替你决定权限(第 5 章),只负责把"谁有什么能力、怎样调用、怎样回结果"标准化。

2. 角色与生命周期

三个角色:宿主(host)是运行智能体的应用,持有模型与权限;客户端(client)由宿主创建,与一个服务端保持一对一连接;服务端(server)暴露工具、资源与提示。一个宿主可以连多个服务端,每个连接独立协商能力。

生命周期分三段:初始化握手、正常操作(请求与通知)、关闭。初始化握手本身就是一组往返——客户端发 initialize 请求带上协议版本与能力声明,服务端在响应里给出自己的版本与能力,客户端再发 notifications/initialized 通知,握手才算完成;此后才允许发操作请求。版本协商决定后续可用字段;版本不一致时,正确做法是明确失败或降级到共同支持的版本,而不是假设字段兼容。

请求都要带 id 并支持并发:同一连接上多个请求可以同时在途,响应乱序返回靠 id 配对;长时间运行的操作要有取消通道,取消是通知而不是请求,发出后不等回应。连接保活与超时由传输层管,协议层只约定“超时后可以安全重试”的条件——幂等操作才可重试。

判据:能力以握手结果为准,不以配置为准。配置里写了"支持某能力"而服务端未声明,调用仍会失败;反过来,服务端声明了某能力但客户端未声明,服务端也不应请求它。

3. 服务端原语

服务端向外暴露三类原语:

  • 工具(tools):可调用、可能带副作用,由模型选择调用。
  • 资源(resources):可读取的内容,按标识寻址,通常只读。
  • 提示(prompts):预置的提示模板,由用户或宿主显式选用。

三者的权限语义不同:工具要走权限门,资源读取要受路径与范围约束,提示模板属于内容而非动作。把资源当工具用(每次读取都走一次模型选择),或把工具当资源用(只读却暴露成可调用),都会让权限与预算失真。

资源用 URI 寻址,可带模板参数(如 repo://{owner}/{name}/README),参数同样要校验——模板参数是注入面的另一个入口。工具可以带注解声明“只读”“会破坏环境”,但注解是给模型选工具的提示,不是权限依据;真正决定能不能执行的仍是第 5 章的判定。

4. 客户端特性

协议也允许服务端向客户端请求能力,例如让客户端代为调用模型(采样)或向用户请求补充信息(引出)。这类特性由客户端在握手时声明,服务端按声明使用。早期的一些客户端特性已被弃用,采用前应查当前版本规范页,不沿用旧示例。

采样请求要带模型偏好与上限:服务端可以表达“希望的能力”,但不能指定具体付费账户,也不能绕过宿主的预算——每次采样都是一次真实计费调用。引出请求是格式化问答,不是自由聊天,问题与选项要预先声明,否则用户会在协议弹窗里遇到无法回答的开放题。

判据:客户端特性扩大了服务端能触达的范围,必须与权限模型一起设计。服务端发起的模型调用同样计入预算,发起的用户询问同样要防疲劳;不能因为请求来自"协议内部"就绕过第 5 章的门。

5. transport 与会话

当前规范定义两种传输:标准输入输出(stdio)与可流式 HTTP(Streamable HTTP)。stdio 适合本地子进程,生命周期与进程绑定,天然隔离;HTTP 适合远程服务,需要处理会话标识、断线重连与多客户端并发。

宿主进程
  ├─ client A ── stdio ──> 本地工具服务进程
  └─ client B ── HTTP  ──> 远程工具服务(带会话标识)

传输选择影响三件事:身份与授权(本地进程用操作系统身份,远程用协议授权)、失败恢复(进程退出即会话结束,HTTP 可重连)、审计(本地可看进程树,远程要看会话与请求标识)。把这些差异写进部署判据,比在代码里用同一套重试逻辑包打天下可靠。

stdio 有一条硬约束:标准输出只能跑协议消息,日志一律写标准错误。把一行调试信息打到标准输出,客户端会把它当成半个 JSON-RPC 消息,表现为随机解析失败,而不是报错。HTTP 侧要带会话标识并在重连后恢复订阅,同时校验来源与内容类型,避免把浏览器发起的跨站请求当成合法客户端。

6. 工具结果的协议形态

工具结果以内容块数组返回,并带错误标志。内容块可以是文本、图像或结构化数据;错误标志区分"调用成功但业务失败"与"协议层失败"。这个区分很重要:业务失败应回灌给模型判断是否换路径,协议失败应触发重连或换服务端。

错误至少分三层:协议层(方法不存在、参数不合法、版本不符)、工具层(目标不存在、权限拒绝、超时)、业务层(执行完成但结果不满足预期)。三层对应三种处置:重连/升级、换路径、改策略。只给一个 isError 布尔值,模型会把“对象不存在”当成“服务坏了”。

判据:结果要能被模型消费,也要能被 harness 记账。只返回一段自然语言,模型能读但 harness 无法统计;只返回结构化字段,模型可能读不懂失败原因。常见做法是结构化字段加一段人类可读说明,两者同时回灌。

7. 自建工具服务

自建服务的最小形态是:声明能力、注册工具、实现调用分发、返回内容块、把异常映射为错误标志。工具实现应复用第 4 章的契约与第 5 章的权限判定,不因换了传输就跳过校验。

def dispatch(name, args):
    tool = REGISTRY.get(name)          # 未知工具 → 协议级错误
    if tool is None:
        return err(f"unknown tool: {name}")
    try:
        payload = tool.validate(args)  # 复用同一套 schema 校验
        result = tool.run(**payload)
        return ok(result)              # 内容块数组 + 成功标志
    except ToolError as e:
        return err(str(e), business=True)   # 业务失败:可回灌、可换路径

服务端不应信任客户端传来的参数,也不应假设客户端已做权限检查;两侧都校验,边界才闭合。

服务进程自身要有超时与并发上限,否则一个卡住的工具会阻塞同连接上的其他请求;反过来说,同一个工具处理函数必须可重入,或显式串行化。关闭时先停止接收新请求,再等待在途请求结束或超时,最后退出——直接断连接会让客户端拿到“不确定是否执行”的状态(第 9 章)。

8. 授权与多租户

本地 stdio 服务的身份通常继承自宿主进程,权限边界由操作系统与沙箱决定;远程 HTTP 服务需要协议层授权,具体流程与令牌形态以当前规范为准。多租户场景还要区分"服务端能看到哪些资源"与"这个客户端被允许访问哪些资源",前者是部署隔离,后者是授权。

令牌按最小范围签发:限定资源前缀与动作,带短过期与受众声明;受众不符的令牌即便签名有效也不应被接受。多租户的隔离要在存储层再确认一次——只靠网关按租户路由,一旦直接访问后端就绕过了。

判据:授权失败要可区分(未登录、令牌过期、范围不足),并回传可执行的下一步;把三类失败统一成"未授权",客户端只能盲目重试。

9. 版本演进与兼容

协议版本用日期标记,字段会增删。兼容策略有三条:握手时只使用双方声明的版本;对未知字段忽略而非报错;对已弃用特性给迁移窗口。服务端与客户端都应记录实际协商到的版本,排障时先看版本再看行为。

能力协商要落成矩阵:客户端声明什么、服务端声明什么、交集是什么、降级后哪些功能不可用。降级不是静默:不可用的功能在调用时返回明确的“未协商”错误,而不是超时或空结果。

判据:不要用"最新示例"代替版本判断。示例页会随规范更新,旧部署不会。把协商版本写进 trace,是跨版本问题最省事的定位手段。

10. 要点

  • 协议的价值在边界:统一消息、能力与生命周期,不替工具设计与权限决策。
  • 角色是宿主、客户端、服务端;每个连接独立握手,能力以协商结果为准。
  • 工具、资源、提示的权限语义不同,不能混用。
  • 客户端特性扩大服务端触达范围,仍受预算与权限门约束。
  • stdio 与 Streamable HTTP 在身份、恢复与审计上差异明显,按部署选。
  • 结果要同时可被模型消费与被 harness 记账;业务失败与协议失败分开。
  • 自建服务复用 schema 校验与权限判定,两侧都校验。
  • 版本以握手协商为准,未知字段忽略,弃用特性给迁移窗口。

参考文献与延伸阅读

  • Model Context Protocol. 规范与文档. 规范
  • OpenAPI Initiative. OpenAPI Specification. 规范
  • gRPC. 文档. 文档