协议的三层
公开教程 · 无需登录

协议的三层

把 MCP 拆成三层看:服务器能暴露什么、报文靠什么传输、报文本身是什么形状。本页逐层展开,并说明初始化握手中协商的内容。

1第一层:服务器能暴露什么

三类能力,分界不在「返回什么」,而在「谁发起」与「有没有副作用」。

工具(tools)

可被模型调用的动作:有名称、说明与参数结构,执行后返回产出的内容。它可能有副作用——写文件、发消息、改数据都算。发起者是模型(严格说是模型提议、宿主执行)。

资源(resources)

可被读取的内容:文件、记录、查询结果。由应用决定什么时候读,一般只读、不产生副作用。它解决的是「把什么放进上下文」,不是「做一个动作」。

提示(prompts)

服务器预置的提示模板,由人或应用主动选用,不是模型自己发起的。适合把某类任务的固定问法沉淀在服务器端,以免每个应用各写一遍。

工具 tools 谁发起:模型提出调用 副作用:可能有(写、发、改) 产出:执行结果的内容 模型提议 → 宿主执行 本课只用这一类 资源 resources 谁发起:应用决定何时读 副作用:一般没有(只读) 产出:文件、记录、查询结果 解决「把什么放进上下文」 不是「做一个动作」 提示 prompts 谁发起:人或应用主动选用 副作用:没有(一段模板) 产出:预置的提示文本 把某类任务的固定问法 沉淀在服务器端 三类的分界不在「返回什么」,而在「谁发起」与「有没有副作用」
服务器不必三类都提供:它须在握手时声明自己支持哪些,客户端按声明使用。

2第二层:报文靠什么传输

两种常见传输,选哪一路取决于服务器在本机还是远程。

标准输入输出(stdio)

宿主把服务器当子进程启动,借标准输入输出交换报文:不占端口,令牌不必走网络。代价是需要管理子进程的启停与输入输出的分帧。本机工具、命令行类工具多用这一路。

HTTP(Streamable HTTP,可流式返回的 HTTP 传输)

服务器是一个 HTTP 服务,用一个端点收发报文;需要流式返回时按 SSE(Server-Sent Events,服务端推送事件)逐段回传。要被多个应用共享、要跨机器部署的服务走这一路。

换传输,不换结构

无论哪一路,方法名与报文形状完全一样。这就是分层的回报:底层换成另一种传输,能力层与报文层的代码一行都不用改。

标准输入输出(stdio) 服务器由宿主启动,是它的一个子进程 宿主进程 工具服务器(子进程) 与宿主同一台机器 标准输入 标准输出 不占端口,令牌不必走网络 代价:要管子进程启停与输入输出的分帧 HTTP(Streamable HTTP) 服务器是独立的服务,两端在不同进程 宿主应用 发起请求 POST SSE 流式回传 工具服务器 独立部署 可被多个应用共享,也能跨机器 可直接复用请求头认证、服务发现与健康检查 代价:要自己解决部署、可达性与令牌保管 本课选这一路,注意力留给结构 换传输,不换结构:两种传输下的方法名与报文形状完全一样
选哪一路取决于服务器在本机还是远程;无论哪一路,上面两层的代码都不用改。
早期版本是「HTTP 发请求 + 另开一条 SSE 通道收消息」的双通道设计,现已合并为单端点。变的是传输层,方法名与报文没动——正好印证分层。

3第三层:报文是什么形状

报文是 JSON-RPC 2.0:一种轻量的远程调用报文格式,一条请求配一条响应。

请求固定带 jsonrpc、id、method,可选 params;响应用同一个 id 对应,成功走 result,失败走 error。id 的作用在于让并发的往返不至于错配。

方法作用请求参数返回
initialize初始化握手(handshake):协商协议版本与双方能力协议版本、客户端信息与其支持的能力服务器信息,以及它支持哪些能力
tools/list工具发现(discovery):询问服务器有哪些工具可用空对象(可用游标分页)工具清单,每项含名称、说明与参数结构
tools/call调用:让服务器执行某个工具工具名与参数字典工具产出的内容(文本等),以及是否出错
宿主应用 发起调用的一方 持有令牌 发起调用 工具服务器 campus-tools 校验令牌 执行工具 initialize 握手:报协议版本与自身信息 tools/list 发现:取回这台服务器暴露的工具清单 tools/call 调用:交参数,取回工具产出的文本
三步各管一件事:握手确认双方可用同一套协议,发现取回工具清单,调用产出结果。前两步在连接时做一次,只有第三步会随每次提问发生。
// 工具发现:列出可用工具
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

// 响应(result.tools 摘一条)
{"jsonrpc":"2.0","id":1,"result":{"tools":[
  {"name":"formula_hint",
   "description":"按学生的问题给出公式或步骤提示,不给出最终数值答案",
   "inputSchema":{"type":"object",
     "properties":{"query":{"type":"string"}},
     "required":["query"]}}
]}}
// 调用:交参数,取回产出
{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"formula_hint","arguments":{"query":"去括号这一步我不会"}}}

// 响应(result.content 是内容数组;isError 为真表示工具自己报错)
{"jsonrpc":"2.0","id":1,"result":{
  "content":[{"type":"text","text":"去括号时:括号前的系数要乘到括号内每一项。"}],
  "isError":false}}
请求头固定带 Authorization: Bearer <令牌>(承载令牌,Bearer token),由宿主发起调用时附带;模型侧与前端都接触不到它——这条边界在「工具带来的风险面」那一页展开。

4握手中协商什么:能力协商

initialize 不是连通性探测,而是能力协商:谈不拢即断开。

客户端报上自己支持的协议版本与能力,服务器回它的版本与能力——有没有工具、有没有资源、是否支持进度通知与取消等。两边取交集,交集为空则无法继续协作。

握手只在连接建立时执行一次,不在每次调用前重做:每次提问前都握手一次,等于凭空多出两次往返。

连接建立之后,服务器还能主动推通知(notifications)。例如自己的工具清单变了,就推一条「清单已变更」,客户端据此重新发现。这是「清单会变」这件事在协议中的对应机制。

由此得到一条重要性质:发现的结果是一次快照,不是永久承诺。远端删除或改动某个工具之后,旧的引用只会在调用时才暴露问题——因此引用既要在使用前校验,也要容忍「校验通过后仍会失效」。

5本课要求

本课只暴露工具这一类能力,采用 HTTP 这一路传输。

你要实现的服务器需响应三个方法:初始化握手、列出工具、执行工具;外加对令牌的校验。它至少提供两个工具,且只给提示、不给最终答案——与第 5 课「解题引导」的口径一致。

宿主一侧要补的是另一半:登记这台服务器、在连接时完成握手与发现、把发现结果转换为工具声明、按模型提出的调用去执行,并为每次调用留下记录。后四项分别在后续三页。