MCP 的 initialize 握手真的没了?67 行标准库实测 2026-07-28 规范

刷 MCP 更新说明的时候看到一句话:2026-07-28 修订把 initialize 握手和协议层会话整个删掉了 ------那套每个教程都在教的「先 initialize、再发 initialized 通知」的连接仪式,从规范层面不存在了。教程没骗人,只是过时了。我不信邪,用 Python 标准库写了一个 67 行的最小 server(不装任何 SDK),按新规范把 discover、tools/list、tools/call 全流程跑通,连「版本不支持该报什么错」都实测了一遍。结论:协议比 SDK 让你以为的要轻得多------这也解释了为什么它能在一年内被每家厂商采纳、又在 2025-12 被捐进 Linux 基金会下的 Agentic AI Foundation。

目录

    • 一、删掉的是握手,不是能力协商
    • [二、67 行标准库 server:协议只剩 JSON-RPC](#二、67 行标准库 server:协议只剩 JSON-RPC)
    • [三、10 条消息实测:全流程 1,528 字节](#三、10 条消息实测:全流程 1,528 字节)
    • [四、三个坑:都是 stdout 惹的祸](#四、三个坑:都是 stdout 惹的祸)

一、删掉的是握手,不是能力协商

背景一句话:MCP 已经是 Agent 生态的事实连接标准------官方 server 注册表 2025 年 9 月进入预览,到 2026 年社区与厂商维护的 server 以千计;对要给模型接工具的人来说,这是绕不开的一层。先补时间线:2024-11-05 首发时是 stdio + HTTP+SSE;2025-03-26 换成 Streamable HTTP;2025-06-18 加了 elicitation;2025-11-25 是最后一代「握手 + 会话」基线;2026-07-28(现行)改为无状态核心 ------SEP-2575 删掉 initialize/initialized 握手,SEP-2567 删掉 Mcp-Session-Id 会话头。版本号、客户端信息、能力声明,改放在每个请求的 params._meta 里 随行;客户端想提前了解 server 端支持哪些修订版本,调 server/discover。这一改动是给部署解锁的:任何实例都能应答任何请求,负载均衡不再需要会话粘滞。这次修订一共动了五块:无状态核心 (上述两条删除);Extensions 框架 (扩展第一次成为一等公民,Tasks 长任务与 MCP Apps(由 server 渲染界面)是头两个官方扩展);授权加固 (对齐 OAuth 2.1 与 OIDC 部署,含 issuer 校验);工具 schema 升级到完整 JSON Schema 2020-12 (oneOf、anyOf、条件引用都能用了);以及一条正式的功能生命周期------每个特性标注 Active/Deprecated/Removed,被弃用的特性至少保留十二个月。一句话:2024 年那个「最小可用协议」正式长成了企业级标准。

二、67 行标准库 server:协议只剩 JSON-RPC

核心逻辑就一个 handle 函数,先看精简骨架(完整 67 行在文末仓库式清单里可复用):

python 复制代码
import json, sys

REVISION = "2026-07-28"

def reply(req_id, result):
    result = dict(result, resultType="complete")
    return {"jsonrpc": "2.0", "id": req_id, "result": result}

def handle(req):
    rid, method = req.get("id"), req.get("method", "")
    ver = (req.get("params") or {}).get("_meta", {}).get(
        "io.modelcontextprotocol/protocolVersion")
    if ver != REVISION:
        return {"jsonrpc": "2.0", "id": rid, "error": {
            "code": -32022, "message": f"unsupported: {ver!r}"}}
    if method == "server/discover":
        return reply(rid, {"supportedRevisions": [REVISION],
                           "capabilities": {"tools": {}},
                           "serverInfo": {"name": "min-stdio",
                                          "version": "0.1.0"}})
    if method == "tools/list":
        return reply(rid, {"tools": TOOLS})
    if method == "tools/call":
        if req["params"].get("name") == "fx_rate":
            return reply(rid, {"content": [{"type": "text",
                                            "text": "HKD/USD = 7.80"}]})
        return {"jsonrpc": "2.0", "id": rid,
                "error": {"code": -32602, "message": "unknown tool"}}
    return {"jsonrpc": "2.0", "id": rid,
            "error": {"code": -32601, "message": "not found"}}

三个实现决策都来自规范原文:stdio 传输下消息逐行分隔、行内禁止换行 ,所以序列化用紧凑分隔符;诊断信息只准写 stderr,stdout 是纯协议流;现代规范的成功结果必须带 resultType (本 server 统一放 complete,多轮往返的 inputRequired 是另一条路径,本文未覆盖)。

三、10 条消息实测:全流程 1,528 字节

探针脚本做的事很朴素:以子进程拉起 server,往 stdin 写 JSON-RPC,每写一行就从 stdout 读一行应答,把方向、字节数、完整消息记进 transcript,stderr 单独收着做断言。它与本机 server 子进程完整对话五条路径------discover、tools/list、tools/call、坏版本(2025-06-18)、缺 _meta 版本------每条消息的方向、字节数、内容全部落盘:

python 复制代码
tr = json.load(open("原始返回/mcp_transcript.json", encoding="utf-8"))
assert len(tr) == 10                                    # 五问五答
assert [t["dir"] for t in tr] == ["C->S", "S->C"] * 5
assert all(t["msg"].get("jsonrpc") == "2.0" for t in tr)
assert tr[1]["msg"]["result"]["serverInfo"]["name"] == "min-stdio"
assert tr[5]["msg"]["result"]["resultType"] == "complete"
# 两条错误路径:版本不支持统一 -32022
assert tr[7]["msg"]["error"]["code"] == -32022
assert tr[9]["msg"]["error"]["code"] == -32022
total = sum(t["bytes"] for t in tr)
assert total == 1528 and max(t["bytes"] for t in tr) == 273
print(f"10 条消息 / {total} B / 最大单条 273 B / -32022 两条路径实测")

结果:10 条消息合计 1,528 字节 ,最大单条是 tools/list 的应答(273 B,带完整 inputSchema)。两个错误路径都按预期返回 -32022------坏版本号和压根不带版本号,本 server 统一按「不支持」处理;这是实现者自己的选择,规范只规定了错误码语义。最贵的洞见是字节数本身:一次完整的能力发现加工具调用,双向不到 1.6 KB ,协议开销小到可以忽略。字节的分布也有信息量:最贵的一条是 tools/list 应答(273 B),因为 inputSchema 的完整 JSON 要逐字段传;最便宜的一条只有 58 B(不带 _meta 的裸请求,代价是被 -32022 拒掉)。换句话说,这个协议里真正占重量的只有 schema 和元数据,信封本身轻得可以忽略------SDK 们包装出来的复杂度,不是协议本身的重量。

四、三个坑:都是 stdout 惹的祸

坑一:调试 print 是新手第一杀手。 stdio 模式下 stdout 就是协议流,print 调试语句会直接插进 JSON-RPC 流,client 的逐行解析立刻崩------而且症状是「client 报格式错误」,你根本想不到是自己的日志。所有日志一律 print(..., file=sys.stderr),规范白纸黑字。

坑二:行内禁止换行。 一条消息一行;json.dumps 默认不带换行,但格式化输出(indent=2)或者消息文本里混入裸换行都会断流。发送前用「序列化结果里不允许出现 \n」做断言,一行代码买断这类事故。

坑三:resultType 的位置。 现代规范要求它长在 result 对象里,不是 JSON-RPC 响应的顶层------我第一版放错位置,探针立刻抓住。对着规范实现协议时,「字段在哪个对象里」和「字段叫什么」同等重要。顺带一提,探针脚本本身也是回归测试:server 每改一行,重跑五条路径十一个断言,十五秒内就知道有没有改坏------协议实现最怕的「看起来能跑」,靠的就是这种土办法。

边界四条说在前面:本次实测只覆盖 stdio 传输、单工具、无多轮往返的最小面------Streamable HTTP 的 Mcp-Method 路由头、Multi Round-Trip 的 InputRequiredResult、以及 Extensions 注册机制都没有跑,别把本文当成全量合规测试;规范细节(尤其错误码的精确语义)以官方文档为准,本文的 -32022 行为是「规范允许范围内的实现选择」;2025-11-25 及更早的 legacy 客户端怎么兼容,官方给的是「先探 discover,失败再回落旧握手」的双 era 模式,本文的 server 只实现了现代侧;最后,demo 工具返回的是写死的汇率,别当真。

这 67 行 server 和探针脚本可以直接搬走当测试床,收藏备用;如果这篇帮你省下读 SDK 源码的一晚上,收藏+点赞。 吐槽与安利各一句:吐槽 MCP 的 Python SDK 把一个 67 行能说清楚的事包了十几层抽象;安利规范本身写得极其克制------删功能比加功能更需要勇气。你升级到无状态版了吗?旧握手的 server 还打算撑多久?评论区聊聊。MCP/Agent 实测系列开更,关注不迷路。

参考链接

  1. https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
  2. https://modelcontextprotocol.io/specification/latest
相关推荐
vx_Biye_Design42 分钟前
springboot角色扮演服务平台65161-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·spring·课程设计
追烽少年x1 小时前
从零构建一个3D点云预览器:Python + PySide6 + pyqtgraph 实战
python·3d
墨心@1 小时前
AI Agent 学习总结
人工智能·自然语言处理·agent·harness·datawhale共学
weixin_440401691 小时前
质朴的爬虫+数据处理
爬虫·python·数据分析·pandas
估值探索者1 小时前
【Python量化系统工程实战 #01】数据存储选型 CSVSQLiteMySQL 对比与 SQLite 实战建库
开发语言·jvm·python·sqlite·api接口·数据api接口·股票数据api接口
计算机毕业编程指导师1 小时前
计算机毕设答辩技巧:基于Hadoop+Django的公共交通运营数据分析与可视化系统怎么做 源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·spark·毕业设计·课程设计·交通运行
砚底藏山河1 小时前
量化实战:行情数据 Schema 演进与向后兼容
java·python·金融·maven
苏离~Hack1 小时前
InfoScraper:面向授权目标的一站式资产信息收集工具
python
计算机毕业编程指导师1 小时前
【计算机毕设选题推荐】基于Hadoop+Django高频电力消耗大数据分析系统从0到1 源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
hadoop·python·数据分析·spark·毕业设计·课程设计·电力