同一个 MCP 只读工具,如何同时服务两代 HTTP 协议
把包版本、最终协商结果和业务执行次数分开验证,别让一次探测替你宣布兼容成功。
- SDK 包升级不等于这次连接已选择新协议
- 用 connect 后的协商结果与完整请求轨迹确认 era
- 区分旧入口探测 -32000、头部冲突 -32020 和不支持版本 -32022
- 记录业务分派次数,避免把成功回退误解为可任意重放写操作
- 保留完整依赖锁与日志,让机器测试、正文代码和离线包对得上
1. 升级了 SDK,为什么日志里还有 initialize?
把 MCP SDK 升到 2.3.1,客户端却仍然先发 initialize,这不一定是升级失败。包版本说明装了哪份实现,协议版本说明这次连接选择了哪套交互规则。只有把两件事分开,才能判断兼容层是在正常工作,还是错误地掩盖了请求问题。
本文让一个合成库存工具同时服务 2026-07-28 与 2025-11-25,再故意制造探测失败、版本冲突和严格拒绝。我们不只检查“返回了 7”,还保存每一次 HTTP 交换,核对协商结果、错误层次,以及工具究竟执行几次。
本文的新旧对照均使用同一套 SDK 2.3.1。“旧客户端”指该 SDK 的 legacy 模式,并不是另外安装了真实 SDK v1。实验调用进程内 HTTP handler,没有监听端口,没有接入 Claude、Codex 或其他宿主;它是一个可复跑的协议兼容实验,不是生产部署验收。
2. 先固定“新”和“旧”的范围
2026-07-28 的规范变更移除了该 revision 中的 initialize / notifications/initialized 握手及协议级 session,把协议版本与客户端能力放进每次请求的 _meta。旧 revision 仍保留旧握手。因此,不能把“initialize deprecated”简化成“任何 MCP 请求都不能再出现 initialize”。
SDK 的 era 是行为家族,本文只测试其中两个具体 revision:modern 的 2026-07-28,以及 legacy 的 2025-11-25。其他旧 revision、订阅、输入补充、任务扩展、资源分页等不在这五个测试里。
| 层次 | 本文固定的值 | 用什么证明 |
|---|---|---|
| 发行包 | 四个 MCP 包 2.3.1;zod 4.6.7 | package-lock.json、registry 元数据、实际导入 |
| 协商模式 | auto / 默认 legacy | Client 配置与 connect 后的 getter |
| 协议 revision | 2026-07-28 / 2025-11-25 | 最终协商值与逐请求记录 |
| 业务结果 | DEMO-001,可用量 7 | 文本结果、structuredContent、分派计数 |
按固定到 v2.3.1 的 Protocol versions 文档,未设置 versionNegotiation 时仍走 legacy;auto 会先探测;pin 要求指定 revision,不允许回退。本文实测 auto 和默认 legacy。pin 是文档提供的选择,未列入本文测试通过项。
3. 这套小实验实际接了什么
业务输入只有一个 SKU。库存由两个 Object.freeze 对象构成,DEMO-001 是 7,DEMO-002 是 12。五个场景的成功调用都查询 DEMO-001;第二个 SKU 只是工具声明里的另一个合法值,本次没有单独调用它。工具没有数据库、网络 SDK、扣减库存或保存凭据的代码。
inventoryFactory 接收 observations,返回一个工厂。工厂每次拿到 era 就记录下来,注册同名 get_inventory 工具;工具进入回调时才记录 invocations。这样,“创建了 server”“收到了 HTTP 请求”“执行业务”是三个可以分开检查的事实。readOnlyHint 等 annotations 是声明,不能代替代码审查与库存快照比对。
运输层使用真实的 StreamableHTTPClientTransport,但把 fetch 注入为进程内函数:把 SDK 输入构造成 Request,交给 handler.fetch,读取 Response 的克隆保存正文,再把原 Response 还给 SDK。URL 使用 synthetic.invalid,仅用于构造与断言地址,从未解析或拨号。另把 globalThis.fetch 替换成计数后抛错的函数,防止意外走到全局 fetch。
这证明了本次代码路径没有全局 fetch 调用,不构成操作系统级断网沙箱。npm 安装本身可以访问官方 registry;真正的协议测试在依赖安装之后运行。认证、DNS、TLS、代理、负载均衡、真实 HTTP 端口和浏览器 CORS 都没有经过这条进程内路径。
4. 复跑顺序:六个文件,一个输出目录
在新建空目录中保存文末完整的六个文件:inventory.mjs、probe.test.mjs、audit.mjs、expected.json、package.json、package-lock.json。不要只抄依赖清单后运行 npm install,那会丢掉本次传递依赖锁定的意义。package-lock.json 已完整收在可展开区域,并非省略片段。
mkdir -p evidence
npm ci --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org
npm test
node audit.mjs
实测环境为 Linux x86_64、Node.js v24.19.0、npm 11.9.0。测试使用 ESM JavaScript,调用 TypeScript SDK 的已发布发行包;没有 TypeScript 编译验收。运行后会覆盖当前实验目录下 evidence/probe-output.json,因此使用新的实验目录,不要把输出位置指向已有业务数据。
初次依赖来自官方 npm registry。可行性核验曾删除并重装 node_modules;成稿又从未改动的锁文件,使用同一运行环境的已有缓存执行 npm ci 后复跑。这个记录不是另一台全新机器、其他 Node 版本或其他操作系统的成功证明。--ignore-scripts 禁止运行依赖安装脚本;--no-audit 表示安装时不发起 npm audit,并不表示做过安全漏洞审计。
完整离线 HTML 可以直接阅读,示例压缩包包含源码和锁文件,但不携带 node_modules 或 npm 缓存。没有依赖缓存的机器仍需联网安装。正文没有假设存在公开下载地址;即使只复制本文代码,也足够重建本次实验。
5. 依赖为什么有四个 MCP 包
v2.3.1 固定发布页列出了 client、server、core 与 server-legacy 的该版本。本文还核对了官方 registry 元数据和实际安装导出,避免把“文档写过”当成“本机存在这个 API”。锁文件保留 88 个安装包的具体版本、registry 地址与 integrity。
| 包 | 固定版本 | 本例角色 |
|---|---|---|
| @modelcontextprotocol/client | 2.3.1 | Client、StreamableHTTPClientTransport、SdkHttpError |
| @modelcontextprotocol/server | 2.3.1 | McpServer、createMcpHandler、legacyStatelessFallback |
| @modelcontextprotocol/core | 2.3.1 | 保留原核验清单;不直接 import |
| @modelcontextprotocol/server-legacy | 2.3.1 | 仅核验存在和可导入;本实验未使用其入口 |
| zod | 4.6.7 | 工具输入 schema |
特别注意:本例的 legacyStatelessFallback 来自 server 包,不是 server-legacy。后者是冻结的 v1 SSE/OAuth 迁移实现,安装时有明确弃用提示。这里保留它是为了让原核验包和锁文件字节一致,不是建议所有支持 2025 协议的项目都安装它,也不是本文测了传统 SSE 入口或 OAuth。package.json 的原研究名称和 description 同样保留,避免为了排版重生成锁。
6. 场景一:同一个兼容入口,跑出两种协商结果
兼容入口使用 createMcpHandler(factory, { legacy: 'stateless', onerror })。auto 客户端的 supportedProtocolVersions 同时保留现代与旧 revision,probe 的超时为 1500 ms、maxRetries 为 0;旧模式客户端只固定 supportedProtocolVersions 为 2025-11-25,未设置 versionNegotiation。
必须等 connect 成功,再读 getProtocolEra() 和 getNegotiatedProtocolVersion()。本次现代客户端得到 modern / 2026-07-28,RPC 顺序只有 server/discover → tools/list → tools/call。旧模式得到 legacy / 2025-11-25,RPC 顺序为 initialize → notifications/initialized → tools/list → tools/call。
每次调用都先 listTools,断言工具只有 get_inventory,且 readOnlyHint 为 true;再 callTool,分别把 structuredContent 和文本块中的 JSON 与固定预期比较。两种协议的完整响应不必逐字相等:现代响应会有 resultType 与服务器元数据,旧响应没有这套现代包络。本例比较的是业务语义。
{
"sku": "DEMO-001",
"label": "Synthetic notebook",
"available": 7
}
这一组实际记录 8 次 HTTP 交换,而 RPC 方法清单只有 7 项。额外的一次是旧客户端的 GET,入口回答 405。日志保留它,没有为了获得一张漂亮的成功清单而过滤掉。两次成功工具分派分别标为 modern 和 legacy。
7. 场景二:auto 探测失败,什么时候才算成功回退
第二组把入口换成 SDK 实际导出的 legacyStatelessFallback(factory, onerror)。这里“2025-only”是对照 modern 的场景简称:该旧入口还宣告支持其他旧 revision,客户端本次最终固定在 2025-11-25;它不是只允许一个字符串的手写仿真服务器。
auto 先发送带现代 _meta 与版本头的 server/discover。安装包的旧 HTTP 版本校验先拒绝现代版本头,实际结果是 HTTP 400 / JSON-RPC -32000,随后客户端只做一次 initialize,完成旧协议协商,再 list 和 call。整个场景业务回调只进入一次。
最初核验把这个探测的预期写成 -32601,测试确实失败过。对照已安装包的 validateProtocolVersion 实现后,才修正为实际的 -32000。错误出现在 HTTP 入口的版本校验,不能仅凭 RPC 方法名就认定会到达“方法不存在”分支。保留失败记录有助于解释为什么这里不用一个看似更常见的错误码。
server/discover → HTTP 400 / -32000
initialize → HTTP 200,protocolVersion = 2025-11-25
initialized → HTTP 202
GET → HTTP 405
tools/list → HTTP 200
tools/call → HTTP 200,业务分派 1 次
最终:getProtocolEra() = legacy
首个探测已经带新版 _meta,最终连接仍然是 legacy。这正是不能用“我在日志里搜到了 2026-07-28”作为成功判据的原因。同样,这个已测拒绝能触发回退,不等于任意 400、超时、401 或 5xx 都应该降级;本文没有把网络错误或认证失败伪装成旧协议,也没有验证那些分支。
8. 场景三:分开验证“冲突”与“不支持”
这一组拆成两个测试。第一个直接发送现代 tools/call:正文 _meta 是 2026-07-28,MCP-Protocol-Version 头却是 2025-11-25。入口返回 HTTP 400 / -32020,响应 id 为 101。拒绝后立刻断言 factories 与 invocations 都为空,证明业务没有先执行再被包装成错误。
正向对照保持业务参数与入口不变,只把版本头修正为 2026-07-28,并换用请求 id 102,得到 HTTP 200 与库存结果,业务调用数成为 1。这个对照直接调用 handler 的 HTTP 边界,没有经过 Client 自动恢复,因此不声称已验证客户端遇到 -32020 后的自动重试策略。
第二个测试把正文与头部都改成合成的 2099-01-01。两者一致,仍然不能处理,因为版本不受支持。实际是 HTTP 400 / -32022,错误 data 中 requested 为 2099-01-01,supported 只有 2026-07-28;工厂与工具仍为零。2099 只是负例标记,不是预测未来协议。
9. 场景四:strict 拒绝旧请求,仍服务现代请求
把同一个 factory 放进 createMcpHandler(factory, { legacy: 'reject', onerror }),默认旧模式客户端连接时会发 initialize。本次响应是 HTTP 400 / -32022,requested 为 2025-11-25,supported 为 [2026-07-28]。拒绝后先确认工厂与业务都没有进入,再让现代客户端连接,完成一次正常读取。
客户端捕获到的异常是 SdkHttpError。本次实际本地 code 是 CLIENT_HTTP_NOT_IMPLEMENTED;它不是 HTTP 状态 501,也不是 JSON-RPC -32601。异常 data.status 明确为 400,data.text 中的协议错误才是 -32022。记录看起来不直观时,要保存完整异常层次,不要按字符串名称改写服务端事实。
SDK 异常类型:SdkHttpError
SDK 本地 code:CLIENT_HTTP_NOT_IMPLEMENTED
HTTP data.status:400
HTTP body.error.code:-32022
HTTP body.error.data.requested:2025-11-25
strict 是服务策略,不是迁移成绩。只有确定目标客户端允许使用现代协议,才应决定是否拒绝旧流量;本文没有访问真实宿主,所以不能替任何宿主做这个决定。它证明的是这份 handler 配置可以有条件地拒绝旧入口,同时保留现代调用能力。
10. 怎样读证据,才不会把探测当成业务
每个场景都有三份互相约束的记录:客户端最终协商结果;逐请求 method、headers、body、status、responseHeaders 与 responseText;工厂和业务回调计数。测试不仅检查返回值,还检查该出现和不该出现的调用。独立 audit.mjs 只使用 Node 标准库读取 JSON,不 import SDK 或 inventory 实现。它再次检查顺序、版本、错误码、结果和总计,减少只看测试退出码的盲区。
audit 通过应输出下面的完整对象。它是第二套程序化断言,不是独立人工审稿,更不是官方完整协议符合性套件。测试日志中的运行毫秒数和 probe-output.json 的 checkedAt 每次会改变,不应把动态时间戳当成源码不一致。
{
"status": "pass",
"cases": 5,
"httpExchanges": 21,
"toolDispatches": 5,
"forbiddenFetches": 0,
"inventoryUnchanged": true
}
两个协议路径都没有 Mcp-Session-Id,这是因为旧入口也被明确配置为 stateless。不能反推“无 session 头就是 modern”。同样,旧响应使用 text/event-stream,不代表本实验启用了 server-legacy 包的旧 SSE transport;这是所测 Streamable HTTP 响应的内容类型。
11. 完整请求与响应:对照同一次库存读取
下面两个对象直接来自录制器,各自包含全部请求头、JSON-RPC 正文、状态、响应头和原始响应文本。responseText 中的反斜杠是把原始正文保存进 JSON 字符串的转义,不是额外一层 MCP 业务参数。
展开 modern tools/call 的完整交换
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
},
"jsonrpc": "2.0",
"id": 1
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7},\"resultType\":\"complete\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":1}"
}
展开 legacy tools/call 的完整交换
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
}
},
"jsonrpc": "2.0",
"id": 2
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7}},\"jsonrpc\":\"2.0\",\"id\":2}\n\n"
}
失败样例也必须保留原始响应。下面依次是 auto 的旧入口探测、头部与正文冲突、双方一致但版本不支持,以及 strict 对旧 initialize 的拒绝。前三者的 requested 或 id 含义不同,不能把同一个错误处理分支套在所有响应上。
展开四个完整失败交换
[
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "server/discover",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"jsonrpc": "2.0",
"id": "server-discover-probe-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Bad Request: Unsupported protocol version: 2026-07-28 (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)\"},\"id\":null}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"jsonrpc": "2.0",
"id": 101,
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "raw-synthetic-probe",
"version": "1.0.0"
}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32020,\"message\":\"Bad Request: the request headers and body disagree: the body envelope names protocol version 2026-07-28 but the MCP-Protocol-Version header names 2025-11-25\",\"data\":{\"mismatch\":{\"header\":\"2025-11-25\",\"body\":\"the body envelope names protocol version 2026-07-28 but the MCP-Protocol-Version header names 2025-11-25\"}}},\"id\":101}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2099-01-01"
},
"body": {
"jsonrpc": "2.0",
"id": 201,
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2099-01-01",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "raw-synthetic-probe",
"version": "1.0.0"
}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2099-01-01\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2099-01-01\"}},\"id\":201}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json"
},
"body": {
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
}
},
"jsonrpc": "2.0",
"id": 0
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2025-11-25\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2025-11-25\"}},\"id\":0}"
}
]
12. 把问题交给编码 Agent 时,要求它交付这些证据
可以把下面这段任务说明连同六个文件交给 Agent。它围绕一个具体改造目标写,避免让 Agent 顺手修改真实宿主、认证或部署配置:
在新建的隔离目录复跑这份 MCP HTTP 协议兼容实验。
保留 package.json 与 package-lock.json 的精确版本和原字节。
先安装锁定依赖,再运行 node:test 与独立 audit;不要接入真实库存。
若要适配我的只读工具,只替换合成业务层,不改写协议错误来迁就测试。
交付:源码 diff、完整请求记录、最终协商版本、工厂与业务计数。
任何需要凭据、真实宿主设置、监听端口或写操作的步骤先停下说明边界。
验收时看五个可判断的结果,而不只接受“升级成功”:① 同一 compatible handler 使两种模式各读到相同库存;② auto 对旧入口的探测失败后只进行一次旧握手,业务只调用一次;③ 版本冲突和不支持版本在业务分派前被拒绝;④ strict 拒绝旧请求后仍能处理现代请求;⑤ 审计得到 21 次交换、5 次业务分派,global fetch 为 0,库存前后不变。
如果你的下一步是验证真正的 SDK v1 客户端,就应建立额外、版本隔离的客户端项目,再对目标宿主和真实运输链路另做验收。本文的五个测试不能替代那项工作。若业务会扣库存、发消息或创建订单,也不能由这个只读回退实验推导“重发一定安全”;需要另行定义幂等键、提交时点与失败恢复边界。
13. 复跑失败时,先定位在哪一层
| 现象 | 先查什么 | 不要怎样修 |
|---|---|---|
| ERR_MODULE_NOT_FOUND | 是否在六文件目录执行 npm ci;锁文件是否完整 | 不要临时混装另一版 SDK |
| 找不到 evidence/probe-output.json | 先 mkdir -p evidence,再 npm test,最后 audit | 不要用旧输出冒充当前测试 |
| fallback 的错误码不等于 -32000 | 核对本机锁文件、入口类型、实际响应和安装包版本 | 不要直接放宽为任意错误都通过 |
| 21 次交换变成 19 次 | 检查是否过滤掉两个 GET 405 | 不要只统计带 JSON-RPC method 的请求 |
| 库存返回正确但计数超标 | 查每次 tools/call、invocations 和重试路径 | 不要把重复业务执行归为无害探测 |
| SDK 本地 code 与 HTTP 不像一回事 | 分开保存 error.code、data.status、data.text | 不要凭异常名字推导协议错误码 |
代码中的 SSE 读取器只允许一个 data: JSON-RPC 响应,适合本文会结束响应体的 list/call 操作。订阅、持续通知、长流以及多轮交互需要另一套有界录制器;直接照搬 response.clone().text() 可能一直等待。这个限制是为了让当前实验能结束,不是对所有 MCP 流的通用解析实现。
14. 完整依赖清单:package.json
保存为 package.json。以下内容与实际复跑文件逐字一致。
{
"name": "mcp-protocol-compatibility-research",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Isolated read-only synthetic MCP protocol compatibility probe; not site content",
"scripts": {
"test": "node --test probe.test.mjs"
},
"dependencies": {
"@modelcontextprotocol/client": "2.3.1",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/server": "2.3.1",
"@modelcontextprotocol/server-legacy": "2.3.1",
"zod": "4.6.7"
}
}
15. 独立固定预期:expected.json
保存为 expected.json。以下内容与实际复跑文件逐字一致。
{
"modernVersion": "2026-07-28",
"legacyVersion": "2025-11-25",
"toolName": "get_inventory",
"sku": "DEMO-001",
"item": {
"sku": "DEMO-001",
"label": "Synthetic notebook",
"available": 7
},
"headerMismatchCode": -32020,
"unsupportedVersionCode": -32022,
"legacyDiscoverErrorCode": -32000
}
16. 完整业务实现:inventory.mjs
保存为 inventory.mjs。以下内容与实际复跑文件逐字一致。
import { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
// Deliberately synthetic, fixed, and read-only. No database, credentials, or network.
export const inventory = Object.freeze({
'DEMO-001': Object.freeze({ sku: 'DEMO-001', label: 'Synthetic notebook', available: 7 }),
'DEMO-002': Object.freeze({ sku: 'DEMO-002', label: 'Synthetic pen', available: 12 }),
});
export function inventoryFactory(observations) {
return ({ era }) => {
observations.factories.push(era);
const server = new McpServer({ name: 'synthetic-inventory', version: '1.0.0' });
server.registerTool('get_inventory', {
description: 'Read a fixed synthetic inventory item; no external service or mutation',
inputSchema: z.object({ sku: z.enum(['DEMO-001', 'DEMO-002']) }),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
}, async ({ sku }) => {
observations.invocations.push({ era, sku });
return {
content: [{ type: 'text', text: JSON.stringify(inventory[sku]) }],
structuredContent: { ...inventory[sku] },
};
});
return server;
};
}
17. 完整五项测试:probe.test.mjs
保存为 probe.test.mjs。以下内容与实际复跑文件逐字一致。
import assert from 'node:assert/strict';
import { test, after } from 'node:test';
import { readFileSync, writeFileSync } from 'node:fs';
import { Client, StreamableHTTPClientTransport, SdkHttpError } from '@modelcontextprotocol/client';
import { createMcpHandler, legacyStatelessFallback } from '@modelcontextprotocol/server';
import { inventory, inventoryFactory } from './inventory.mjs';
const expected = JSON.parse(readFileSync(new URL('./expected.json', import.meta.url)));
const MODERN = expected.modernVersion;
const LEGACY = expected.legacyVersion;
const META_VERSION = 'io.modelcontextprotocol/protocolVersion';
const META_CAPS = 'io.modelcontextprotocol/clientCapabilities';
const META_CLIENT = 'io.modelcontextprotocol/clientInfo';
const URL_LOCAL = 'http://synthetic.invalid/mcp'; // Never dialed.
const inventoryBefore = JSON.stringify(inventory);
const reports = [];
let forbiddenFetches = 0;
// All SDK requests must enter the explicitly injected in-process fetch adapter.
// An accidental global network fetch fails instead of reaching any external host.
globalThis.fetch = async () => { forbiddenFetches++; throw new Error('Network fetch forbidden by this probe'); };
function harness(name, kind = 'compatible') {
const observations = { name, factories: [], invocations: [], errors: [], exchanges: [] };
const factory = inventoryFactory(observations);
const errorLog = e => observations.errors.push({ name: e.name, code: e.code ?? null, message: e.message });
const handler = kind === 'legacy-only'
? { fetch: legacyStatelessFallback(factory, errorLog), close: async () => {} }
: createMcpHandler(factory, { legacy: kind === 'strict' ? 'reject' : 'stateless', onerror: errorLog });
const fetch = async (input, init) => {
const request = new Request(input, init);
assert.equal(request.url, URL_LOCAL, 'Only the synthetic in-process endpoint is allowed');
const raw = request.method === 'POST' ? await request.clone().text() : '';
const body = raw ? JSON.parse(raw) : null;
const exchange = { method: request.method, headers: Object.fromEntries(request.headers), body };
observations.exchanges.push(exchange);
const response = await handler.fetch(request);
exchange.status = response.status;
exchange.responseHeaders = Object.fromEntries(response.headers);
// These bounded, non-subscription operations close their response bodies.
exchange.responseText = await response.clone().text();
return response;
};
reports.push(observations);
return { observations, handler, fetch };
}
function clientFor(h, modern = true) {
const client = new Client({ name: 'synthetic-probe', version: '1.0.0' }, modern
? { supportedProtocolVersions: [MODERN, LEGACY], versionNegotiation: { mode: 'auto', probe: { timeoutMs: 1500, maxRetries: 0 } } }
: { supportedProtocolVersions: [LEGACY] });
const transport = new StreamableHTTPClientTransport(new URL(URL_LOCAL), { fetch: h.fetch });
return { client, transport };
}
function rpcs(exchanges) { return exchanges.filter(e => e.body?.method); }
function rpcMethods(exchanges) { return rpcs(exchanges).map(e => e.body.method); }
function responseRpc(exchange) {
if (!exchange.responseText) return null;
if (exchange.responseHeaders['content-type']?.startsWith('application/json')) return JSON.parse(exchange.responseText);
const lines = exchange.responseText.split('\n').filter(x => x.startsWith('data:'));
assert.equal(lines.length, 1, 'This probe expects exactly one RPC response per SSE exchange');
return JSON.parse(lines[0].slice(5).trim());
}
function noSession(exchanges) {
for (const e of exchanges) {
assert.equal(e.headers['mcp-session-id'], undefined);
assert.equal(e.responseHeaders['mcp-session-id'], undefined);
}
}
async function lookup(client) {
const list = await client.listTools();
assert.deepEqual(list.tools.map(t => t.name), [expected.toolName]);
assert.equal(list.tools[0].annotations.readOnlyHint, true);
const result = await client.callTool({ name: expected.toolName, arguments: { sku: expected.sku } });
assert.deepEqual(result.structuredContent, expected.item);
assert.deepEqual(JSON.parse(result.content[0].text), expected.item);
return result;
}
function modernBody(id, version = MODERN) {
return { jsonrpc: '2.0', id, method: 'tools/call', params: {
name: expected.toolName, arguments: { sku: expected.sku }, _meta: {
[META_VERSION]: version, [META_CAPS]: {}, [META_CLIENT]: { name: 'raw-synthetic-probe', version: '1.0.0' },
},
} };
}
async function post(h, body, headerVersion) {
const response = await h.fetch(URL_LOCAL, { method: 'POST', headers: {
'content-type': 'application/json', accept: 'application/json, text/event-stream',
'mcp-protocol-version': headerVersion, 'mcp-method': body.method, 'mcp-name': expected.toolName,
}, body: JSON.stringify(body) });
return { status: response.status, rpc: responseRpc(h.observations.exchanges.at(-1)) };
}
test('One compatible endpoint serves modern auto and default legacy clients', { timeout: 10000 }, async () => {
const h = harness('same-endpoint-two-eras');
const modern = clientFor(h);
const legacy = clientFor(h, false);
try {
await modern.client.connect(modern.transport);
assert.equal(modern.client.getProtocolEra(), 'modern');
assert.equal(modern.client.getNegotiatedProtocolVersion(), MODERN);
const modernResult = await lookup(modern.client);
const split = h.observations.exchanges.length;
const modernExchanges = h.observations.exchanges.slice(0, split);
assert.deepEqual(rpcMethods(modernExchanges), ['server/discover', 'tools/list', 'tools/call']);
for (const e of rpcs(modernExchanges)) {
assert.equal(e.body.params._meta[META_VERSION], MODERN);
assert.deepEqual(e.body.params._meta[META_CAPS], {});
assert.equal(e.headers['mcp-protocol-version'], MODERN);
assert.equal(e.headers['mcp-method'], e.body.method);
}
assert.equal(rpcs(modernExchanges).at(-1).headers['mcp-name'], expected.toolName);
await legacy.client.connect(legacy.transport);
assert.equal(legacy.client.getProtocolEra(), 'legacy');
assert.equal(legacy.client.getNegotiatedProtocolVersion(), LEGACY);
const legacyResult = await lookup(legacy.client);
assert.deepEqual(legacyResult.structuredContent, modernResult.structuredContent);
const legacyExchanges = h.observations.exchanges.slice(split);
assert.deepEqual(rpcMethods(legacyExchanges), ['initialize', 'notifications/initialized', 'tools/list', 'tools/call']);
assert.equal(rpcs(legacyExchanges)[0].body.params.protocolVersion, LEGACY);
for (const e of rpcs(legacyExchanges)) assert.equal(e.body.params?._meta?.[META_VERSION], undefined);
for (const e of rpcs(legacyExchanges).filter(e => e.body.method.startsWith('tools/'))) assert.equal(e.headers['mcp-protocol-version'], LEGACY);
assert.deepEqual(h.observations.invocations, [{ era: 'modern', sku: expected.sku }, { era: 'legacy', sku: expected.sku }]);
noSession(h.observations.exchanges);
h.observations.summary = { modernVersion: modern.client.getNegotiatedProtocolVersion(), legacyVersion: legacy.client.getNegotiatedProtocolVersion(), toolCalls: 2, modernMethods: rpcMethods(modernExchanges), legacyMethods: rpcMethods(legacyExchanges) };
} finally { await modern.client.close(); await legacy.client.close(); await h.handler.close(); }
});
test('Auto mode probes a 2025-only entry, then falls back once to initialize', { timeout: 10000 }, async () => {
const h = harness('auto-legacy-fallback', 'legacy-only');
const { client, transport } = clientFor(h);
try {
await client.connect(transport);
assert.equal(client.getProtocolEra(), 'legacy');
assert.equal(client.getNegotiatedProtocolVersion(), LEGACY);
await lookup(client);
assert.deepEqual(rpcMethods(h.observations.exchanges), ['server/discover', 'initialize', 'notifications/initialized', 'tools/list', 'tools/call']);
const [probe, initialize, , list, call] = rpcs(h.observations.exchanges);
assert.equal(probe.body.params._meta[META_VERSION], MODERN);
// SDK 2.3.1 legacy HTTP rejects the modern version header before RPC dispatch.
// Source: server/dist/index.mjs WebStandardStreamableHTTPServerTransport.validateProtocolVersion.
assert.equal(probe.status, 400);
assert.equal(responseRpc(probe).error.code, expected.legacyDiscoverErrorCode);
assert.match(responseRpc(probe).error.message, /Unsupported protocol version: 2026-07-28/);
assert.equal(initialize.body.params.protocolVersion, LEGACY);
for (const e of [list, call]) {
assert.equal(e.headers['mcp-protocol-version'], LEGACY);
assert.equal(e.body.params?._meta?.[META_VERSION], undefined);
}
assert.deepEqual(h.observations.invocations, [{ era: 'legacy', sku: expected.sku }]);
noSession(h.observations.exchanges);
h.observations.summary = { era: client.getProtocolEra(), protocolVersion: client.getNegotiatedProtocolVersion(), discoverError: responseRpc(probe).error.code, toolCalls: 1, methods: rpcMethods(h.observations.exchanges) };
} finally { await client.close(); await h.handler.close(); }
});
test('A header/envelope version mismatch is rejected before tool dispatch', { timeout: 10000 }, async () => {
const h = harness('metadata-header-mismatch');
try {
const mismatch = await post(h, modernBody(101), LEGACY);
assert.equal(mismatch.status, 400);
assert.equal(mismatch.rpc.error.code, expected.headerMismatchCode);
assert.equal(mismatch.rpc.id, 101);
assert.equal(h.observations.invocations.length, 0);
assert.equal(h.observations.factories.length, 0);
const matching = await post(h, modernBody(102), MODERN);
assert.equal(matching.status, 200);
assert.deepEqual(matching.rpc.result.structuredContent, expected.item);
assert.equal(h.observations.invocations.length, 1);
h.observations.summary = { mismatchStatus: mismatch.status, mismatchCode: mismatch.rpc.error.code, rejectedToolCalls: 0, matchingStatus: matching.status, matchingToolCalls: 1 };
} finally { await h.handler.close(); }
});
test('An unsupported envelope version is distinct from header disagreement', { timeout: 10000 }, async () => {
const h = harness('unsupported-metadata-version');
try {
const rejected = await post(h, modernBody(201, '2099-01-01'), '2099-01-01');
assert.equal(rejected.status, 400);
assert.equal(rejected.rpc.error.code, expected.unsupportedVersionCode);
assert.equal(rejected.rpc.error.data.requested, '2099-01-01');
assert.deepEqual(rejected.rpc.error.data.supported, [MODERN]);
assert.equal(h.observations.invocations.length, 0);
assert.equal(h.observations.factories.length, 0);
h.observations.summary = { status: rejected.status, code: rejected.rpc.error.code, toolCalls: 0 };
} finally { await h.handler.close(); }
});
test('Strict modern entry rejects legacy initialize while serving modern calls', { timeout: 10000 }, async () => {
const h = harness('strict-modern-rejects-legacy', 'strict');
const legacy = clientFor(h, false);
const modern = clientFor(h);
try {
await assert.rejects(() => legacy.client.connect(legacy.transport), e => {
assert.ok(e instanceof SdkHttpError);
assert.equal(e.data.status, 400);
h.observations.clientError = { name: e.name, code: e.code, data: e.data, message: e.message };
return true;
});
assert.deepEqual(rpcMethods(h.observations.exchanges), ['initialize']);
const rejection = responseRpc(h.observations.exchanges[0]);
assert.equal(rejection.error.code, expected.unsupportedVersionCode);
assert.equal(h.observations.factories.length, 0);
assert.equal(h.observations.invocations.length, 0);
await modern.client.connect(modern.transport);
assert.equal(modern.client.getProtocolEra(), 'modern');
await lookup(modern.client);
assert.equal(h.observations.invocations.length, 1);
h.observations.summary = { legacyStatus: h.observations.exchanges[0].status, legacyError: rejection.error.code, rejectedToolCalls: 0, modernVersion: modern.client.getNegotiatedProtocolVersion(), modernToolCalls: 1 };
} finally { await legacy.client.close(); await modern.client.close(); await h.handler.close(); }
});
after(() => {
assert.equal(forbiddenFetches, 0);
assert.equal(JSON.stringify(inventory), inventoryBefore, 'Inventory stayed unchanged');
for (const report of reports) noSession(report.exchanges);
const output = { checkedAt: new Date().toISOString(), runtime: process.version, forbiddenFetches, inventoryUnchanged: true, reports };
writeFileSync(new URL('./evidence/probe-output.json', import.meta.url), JSON.stringify(output, null, 2) + '\n');
});
18. 完整日志审计:audit.mjs
保存为 audit.mjs。以下内容与实际复跑文件逐字一致。
// Independent transcript checks: uses only Node standard library, not SDK or server code.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const evidence = JSON.parse(readFileSync(new URL('./evidence/probe-output.json', import.meta.url)));
const byName = Object.fromEntries(evidence.reports.map(r => [r.name, r]));
const rpc = r => r.exchanges.filter(e => e.body?.method);
const names = r => rpc(r).map(e => e.body.method);
const bodyVersion = e => e.body?.params?._meta?.['io.modelcontextprotocol/protocolVersion'];
const readResponse = e => e.responseHeaders['content-type']?.startsWith('application/json')
? JSON.parse(e.responseText)
: JSON.parse(e.responseText.split('\n').find(x => x.startsWith('data:')).slice(5).trim());
assert.equal(evidence.reports.length, 5);
assert.equal(evidence.forbiddenFetches, 0);
assert.equal(evidence.inventoryUnchanged, true);
const both = byName['same-endpoint-two-eras'];
assert.deepEqual(names(both), ['server/discover', 'tools/list', 'tools/call', 'initialize', 'notifications/initialized', 'tools/list', 'tools/call']);
assert.deepEqual(both.invocations.map(x => x.era), ['modern', 'legacy']);
const modern = rpc(both).slice(0, 3);
for (const e of modern) {
assert.equal(bodyVersion(e), '2026-07-28');
assert.equal(e.headers['mcp-protocol-version'], '2026-07-28');
assert.equal(e.headers['mcp-method'], e.body.method);
assert.equal(readResponse(e).result.resultType, 'complete');
}
const fallback = byName['auto-legacy-fallback'];
assert.deepEqual(names(fallback), ['server/discover', 'initialize', 'notifications/initialized', 'tools/list', 'tools/call']);
assert.equal(bodyVersion(rpc(fallback)[0]), '2026-07-28');
assert.equal(rpc(fallback)[0].status, 400);
assert.equal(readResponse(rpc(fallback)[0]).error.code, -32000);
assert.equal(rpc(fallback)[1].body.params.protocolVersion, '2025-11-25');
assert.equal(readResponse(rpc(fallback)[1]).result.protocolVersion, '2025-11-25');
for (const e of rpc(fallback).slice(1)) assert.equal(bodyVersion(e), undefined);
assert.deepEqual(fallback.invocations.map(x => x.era), ['legacy']);
const mismatch = byName['metadata-header-mismatch'];
assert.equal(mismatch.exchanges[0].status, 400);
assert.equal(readResponse(mismatch.exchanges[0]).error.code, -32020);
assert.equal(mismatch.exchanges[1].status, 200);
assert.equal(mismatch.invocations.length, 1);
const unsupported = byName['unsupported-metadata-version'];
assert.equal(readResponse(unsupported.exchanges[0]).error.code, -32022);
assert.equal(unsupported.invocations.length, 0);
const strict = byName['strict-modern-rejects-legacy'];
assert.equal(strict.exchanges[0].body.method, 'initialize');
assert.equal(strict.exchanges[0].status, 400);
assert.equal(readResponse(strict.exchanges[0]).error.code, -32022);
assert.equal(strict.clientError.name, 'SdkHttpError');
assert.equal(strict.clientError.data.status, 400);
assert.deepEqual(strict.invocations.map(x => x.era), ['modern']);
let httpExchanges = 0;
let toolDispatches = 0;
for (const report of evidence.reports) {
toolDispatches += report.invocations.length;
for (const e of report.exchanges) {
httpExchanges++;
assert.equal(e.headers['mcp-session-id'], undefined);
assert.equal(e.responseHeaders['mcp-session-id'], undefined);
if (e.body?.method === 'tools/call' && e.status === 200) {
assert.deepEqual(readResponse(e).result.structuredContent, { sku: 'DEMO-001', label: 'Synthetic notebook', available: 7 });
}
}
}
assert.equal(httpExchanges, 21);
assert.equal(toolDispatches, 5);
console.log(JSON.stringify({ status: 'pass', cases: 5, httpExchanges, toolDispatches, forbiddenFetches: 0, inventoryUnchanged: true }, null, 2));
19. 完整依赖锁:package-lock.json
该文件锁住所有传递依赖。展开并完整复制,不要用下方正文解释代替文件。它与本次已安装、已复跑的锁文件逐字一致。
展开完整 package-lock.json(含全部 registry URL 与 integrity)
{
"name": "mcp-protocol-compatibility-research",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mcp-protocol-compatibility-research",
"version": "1.0.0",
"dependencies": {
"@modelcontextprotocol/client": "2.3.1",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/server": "2.3.1",
"@modelcontextprotocol/server-legacy": "2.3.1",
"zod": "4.6.7"
}
},
"node_modules/@modelcontextprotocol/client": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.3.1.tgz",
"integrity": "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.3.1",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.8",
"jose": "^6.1.3",
"pkce-challenge": "^5.0.0",
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@modelcontextprotocol/core": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@modelcontextprotocol/server": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.3.1",
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@modelcontextprotocol/server-legacy": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server-legacy/-/server-legacy-2.3.1.tgz",
"integrity": "sha512-+cuB+hl7s8DiFXIt13le3cD2wZPOeDYwlycAkRMPljqqNbOLekGRiyslx8WXfqaXSCveS1eDD8/B8rmxz9BZFQ==",
"deprecated": "This package is a frozen copy of v1's SSE transport and OAuth Authorization Server helpers for migration purposes only. Use StreamableHTTP from @modelcontextprotocol/server and a dedicated OAuth server in production. Will not receive new features.",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.3.1",
"content-type": "^1.0.5",
"cors": "^2.8.5",
"express-rate-limit": "^8.2.1",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^4.2.0"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"express": "^4.18.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"express": {
"optional": true
}
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^2.0.0",
"debug": "^4.4.3",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
"qs": "^6.15.2",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz",
"integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT",
"peer": true
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT",
"peer": true
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/express": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz",
"integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.3.0",
"content-disposition": "^2.0.1",
"content-type": "^2.0.0",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.8",
"qs": "^6.16.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.1.0",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.1.tgz",
"integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/express/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"peer": true,
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"peer": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.7.4",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz",
"integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT",
"peer": true
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jose": {
"version": "6.2.12",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
"integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
"integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
"integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
"license": "MIT",
"peer": true,
"dependencies": {
"content-type": "^2.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/negotiator/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"peer": true,
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"peer": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/pkce-challenge": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
"integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
"integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/qs": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"es-define-property": "^1.0.1",
"side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
"integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"peer": true,
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4",
"side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"peer": true,
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
"integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT",
"peer": true,
"dependencies": {
"content-type": "^2.0.0",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/type-is/node_modules/content-type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC",
"peer": true
},
"node_modules/zod": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
20. 完整机器记录:可逐次核对的 21 次交换
下面是这次成稿复跑产生的完整输出,其中 checkedAt 是该次测试时间,runtime 是实际 Node 版本。记录只含合成 SKU、合成客户端名称及协议元数据,没有真实库存、账户或凭据。运行你自己的实验会生成新的时间戳。
展开 evidence/probe-output.json 全文
{
"checkedAt": "2026-10-13T09:57:03.073Z",
"runtime": "v24.19.0",
"forbiddenFetches": 0,
"inventoryUnchanged": true,
"reports": [
{
"name": "same-endpoint-two-eras",
"factories": [
"modern",
"modern",
"modern",
"legacy",
"legacy",
"legacy",
"legacy"
],
"invocations": [
{
"era": "modern",
"sku": "DEMO-001"
},
{
"era": "legacy",
"sku": "DEMO-001"
}
],
"errors": [],
"exchanges": [
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "server/discover",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"jsonrpc": "2.0",
"id": "server-discover-probe-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"supportedVersions\":[\"2026-07-28\"],\"capabilities\":{\"tools\":{\"listChanged\":true}},\"resultType\":\"complete\",\"ttlMs\":0,\"cacheScope\":\"private\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":\"server-discover-probe-1\"}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/list",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"method": "tools/list",
"jsonrpc": "2.0",
"id": 0,
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"tools\":[{\"name\":\"get_inventory\",\"description\":\"Read a fixed synthetic inventory item; no external service or mutation\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"sku\":{\"type\":\"string\",\"enum\":[\"DEMO-001\",\"DEMO-002\"]}},\"required\":[\"sku\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}}],\"resultType\":\"complete\",\"ttlMs\":0,\"cacheScope\":\"private\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":0}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
},
"jsonrpc": "2.0",
"id": 1
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7},\"resultType\":\"complete\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":1}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json"
},
"body": {
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
}
},
"jsonrpc": "2.0",
"id": 0
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\",\"id\":0}\n\n"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"jsonrpc": "2.0",
"method": "notifications/initialized"
},
"status": 202,
"responseHeaders": {},
"responseText": ""
},
{
"method": "GET",
"headers": {
"accept": "text/event-stream",
"mcp-protocol-version": "2025-11-25"
},
"body": null,
"status": 405,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Method not allowed.\"},\"id\":null}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"method": "tools/list",
"jsonrpc": "2.0",
"id": 1
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"tools\":[{\"name\":\"get_inventory\",\"description\":\"Read a fixed synthetic inventory item; no external service or mutation\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"sku\":{\"type\":\"string\",\"enum\":[\"DEMO-001\",\"DEMO-002\"]}},\"required\":[\"sku\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}}]},\"jsonrpc\":\"2.0\",\"id\":1}\n\n"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
}
},
"jsonrpc": "2.0",
"id": 2
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7}},\"jsonrpc\":\"2.0\",\"id\":2}\n\n"
}
],
"summary": {
"modernVersion": "2026-07-28",
"legacyVersion": "2025-11-25",
"toolCalls": 2,
"modernMethods": [
"server/discover",
"tools/list",
"tools/call"
],
"legacyMethods": [
"initialize",
"notifications/initialized",
"tools/list",
"tools/call"
]
}
},
{
"name": "auto-legacy-fallback",
"factories": [
"legacy",
"legacy",
"legacy",
"legacy",
"legacy"
],
"invocations": [
{
"era": "legacy",
"sku": "DEMO-001"
}
],
"errors": [],
"exchanges": [
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "server/discover",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"jsonrpc": "2.0",
"id": "server-discover-probe-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Bad Request: Unsupported protocol version: 2026-07-28 (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)\"},\"id\":null}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json"
},
"body": {
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
}
},
"jsonrpc": "2.0",
"id": 0
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\",\"id\":0}\n\n"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"jsonrpc": "2.0",
"method": "notifications/initialized"
},
"status": 202,
"responseHeaders": {},
"responseText": ""
},
{
"method": "GET",
"headers": {
"accept": "text/event-stream",
"mcp-protocol-version": "2025-11-25"
},
"body": null,
"status": 405,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Method not allowed.\"},\"id\":null}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"method": "tools/list",
"jsonrpc": "2.0",
"id": 1
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"tools\":[{\"name\":\"get_inventory\",\"description\":\"Read a fixed synthetic inventory item; no external service or mutation\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"sku\":{\"type\":\"string\",\"enum\":[\"DEMO-001\",\"DEMO-002\"]}},\"required\":[\"sku\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}}]},\"jsonrpc\":\"2.0\",\"id\":1}\n\n"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
}
},
"jsonrpc": "2.0",
"id": 2
},
"status": 200,
"responseHeaders": {
"cache-control": "no-cache, no-transform",
"connection": "keep-alive",
"content-type": "text/event-stream",
"x-accel-buffering": "no"
},
"responseText": "event: message\ndata: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7}},\"jsonrpc\":\"2.0\",\"id\":2}\n\n"
}
],
"summary": {
"era": "legacy",
"protocolVersion": "2025-11-25",
"discoverError": -32000,
"toolCalls": 1,
"methods": [
"server/discover",
"initialize",
"notifications/initialized",
"tools/list",
"tools/call"
]
}
},
{
"name": "metadata-header-mismatch",
"factories": [
"modern"
],
"invocations": [
{
"era": "modern",
"sku": "DEMO-001"
}
],
"errors": [
{
"name": "Error",
"code": null,
"message": "Rejected inbound request (header-body-version-mismatch): Bad Request: the request headers and body disagree: the body envelope names protocol version 2026-07-28 but the MCP-Protocol-Version header names 2025-11-25"
}
],
"exchanges": [
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2025-11-25"
},
"body": {
"jsonrpc": "2.0",
"id": 101,
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "raw-synthetic-probe",
"version": "1.0.0"
}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32020,\"message\":\"Bad Request: the request headers and body disagree: the body envelope names protocol version 2026-07-28 but the MCP-Protocol-Version header names 2025-11-25\",\"data\":{\"mismatch\":{\"header\":\"2025-11-25\",\"body\":\"the body envelope names protocol version 2026-07-28 but the MCP-Protocol-Version header names 2025-11-25\"}}},\"id\":101}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"jsonrpc": "2.0",
"id": 102,
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "raw-synthetic-probe",
"version": "1.0.0"
}
}
}
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7},\"resultType\":\"complete\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":102}"
}
],
"summary": {
"mismatchStatus": 400,
"mismatchCode": -32020,
"rejectedToolCalls": 0,
"matchingStatus": 200,
"matchingToolCalls": 1
}
},
{
"name": "unsupported-metadata-version",
"factories": [],
"invocations": [],
"errors": [
{
"name": "ProtocolError",
"code": -32022,
"message": "Unsupported protocol version: 2099-01-01"
}
],
"exchanges": [
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2099-01-01"
},
"body": {
"jsonrpc": "2.0",
"id": 201,
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2099-01-01",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "raw-synthetic-probe",
"version": "1.0.0"
}
}
}
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2099-01-01\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2099-01-01\"}},\"id\":201}"
}
],
"summary": {
"status": 400,
"code": -32022,
"toolCalls": 0
}
},
{
"name": "strict-modern-rejects-legacy",
"factories": [
"modern",
"modern",
"modern"
],
"invocations": [
{
"era": "modern",
"sku": "DEMO-001"
}
],
"errors": [
{
"name": "Error",
"code": null,
"message": "Rejected 2025-era request on a modern-only endpoint (modern-only-missing-envelope): Unsupported protocol version: 2025-11-25"
}
],
"exchanges": [
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json"
},
"body": {
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
}
},
"jsonrpc": "2.0",
"id": 0
},
"status": 400,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2025-11-25\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2025-11-25\"}},\"id\":0}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "server/discover",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"jsonrpc": "2.0",
"id": "server-discover-probe-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"supportedVersions\":[\"2026-07-28\"],\"capabilities\":{\"tools\":{\"listChanged\":true}},\"resultType\":\"complete\",\"ttlMs\":0,\"cacheScope\":\"private\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":\"server-discover-probe-1\"}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/list",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"method": "tools/list",
"jsonrpc": "2.0",
"id": 0,
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"tools\":[{\"name\":\"get_inventory\",\"description\":\"Read a fixed synthetic inventory item; no external service or mutation\",\"inputSchema\":{\"type\":\"object\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"sku\":{\"type\":\"string\",\"enum\":[\"DEMO-001\",\"DEMO-002\"]}},\"required\":[\"sku\"]},\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false}}],\"resultType\":\"complete\",\"ttlMs\":0,\"cacheScope\":\"private\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":0}"
},
{
"method": "POST",
"headers": {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
"mcp-method": "tools/call",
"mcp-name": "get_inventory",
"mcp-protocol-version": "2026-07-28"
},
"body": {
"method": "tools/call",
"params": {
"name": "get_inventory",
"arguments": {
"sku": "DEMO-001"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "synthetic-probe",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
},
"jsonrpc": "2.0",
"id": 1
},
"status": 200,
"responseHeaders": {
"content-type": "application/json"
},
"responseText": "{\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"sku\\\":\\\"DEMO-001\\\",\\\"label\\\":\\\"Synthetic notebook\\\",\\\"available\\\":7}\"}],\"structuredContent\":{\"sku\":\"DEMO-001\",\"label\":\"Synthetic notebook\",\"available\":7},\"resultType\":\"complete\",\"_meta\":{\"io.modelcontextprotocol/serverInfo\":{\"name\":\"synthetic-inventory\",\"version\":\"1.0.0\"}}},\"jsonrpc\":\"2.0\",\"id\":1}"
}
],
"clientError": {
"name": "SdkHttpError",
"code": "CLIENT_HTTP_NOT_IMPLEMENTED",
"data": {
"status": 400,
"statusText": "",
"text": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2025-11-25\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2025-11-25\"}},\"id\":0}"
},
"message": "Error POSTing to endpoint: {\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32022,\"message\":\"Unsupported protocol version: 2025-11-25\",\"data\":{\"supported\":[\"2026-07-28\"],\"requested\":\"2025-11-25\"}},\"id\":0}"
},
"summary": {
"legacyStatus": 400,
"legacyError": -32022,
"rejectedToolCalls": 0,
"modernVersion": "2026-07-28",
"modernToolCalls": 1
}
}
]
}
21. 最后核对:本文证明什么,保留什么未测项
本次五个 node:test 用例及独立日志审计均通过;直接与传递依赖由完整锁文件固定,源码、内联代码和离线示例包做字节校验。三幅 SVG 为针对本实验绘制的原创说明图,按自然宽高比展示,已在本地离线渲染检查。没有进行浏览器整页版式验收。
结论仅限:SDK 2.3.1 的进程内 HTTP handler 与客户端,在所列版本、只读工具和四组场景下,能给出可核对的协商、回退和拒绝行为。未验证真实 SDK v1 客户端、stdio、真实宿主、模型运行、生产网络、认证、多轮输入、订阅、写操作或跨平台环境。
要查版本语义,优先看 v2.3.1 协议版本文档;要查新版 HTTP 元数据与迁移边界,看 固定版本迁移指南;要查 revision 本身的变化,看 2026-07-28 规范变更。文档用于解释规则,本文的日志用于证明这一次到底执行了什么。
装了 SDK 2.3.1,为什么仍发送 initialize?
默认 versionNegotiation 仍采用 legacy。本例未设置该选项的客户端固定为 2025-11-25;auto 客户端才先探测 server/discover。应在 connect 成功后查看实际协商结果。
没有 Mcp-Session-Id 就能认定使用新协议吗?
不能。本例的现代路径与 stateless 旧路径都没有这个头。应结合最终协商版本、RPC 顺序与每次请求元数据判断。
auto 回退一定先收到 -32601 吗?
不是。本例 SDK 2.3.1 的旧 HTTP 入口先拒绝现代版本头,实际为 HTTP 400 / -32000,随后成功执行旧握手。
支持 2025-11-25 必须安装 server-legacy 吗?
本文没有使用该包的入口。兼容 handler 与 legacyStatelessFallback 均从 server 导入;server-legacy 只为核验原四包清单而保留。
这是否证明真实 SDK v1 和所有 MCP 宿主都兼容?
没有。两种客户端都来自 SDK 2.3.1,测试仅在进程内 HTTP 适配路径运行,没有验证真实 v1 客户端或任何宿主。