同一个 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 或其他宿主;它是一个可复跑的协议兼容实验,不是生产部署验收。

同一 SDK 2.3.1 中,auto 与默认旧模式的协商路径和相同库存结果
图 1:版本号、协议路径与业务结果分开观察。两条路径都由真实 SDK 客户端发出

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.7package-lock.json、registry 元数据、实际导入
协商模式auto / 默认 legacyClient 配置与 connect 后的 getter
协议 revision2026-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/client2.3.1Client、StreamableHTTPClientTransport、SdkHttpError
@modelcontextprotocol/server2.3.1McpServer、createMcpHandler、legacyStatelessFallback
@modelcontextprotocol/core2.3.1保留原核验清单;不直接 import
@modelcontextprotocol/server-legacy2.3.1仅核验存在和可导入;本实验未使用其入口
zod4.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 只是负例标记,不是预测未来协议。

头部正文冲突、不支持版本和strict拒绝的错误码,以及修正头部后的成功对照
图 2:错误分类要结合请求上下文;相同的 HTTP 状态码不能取代协议错误码与分派计数

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 实现。它再次检查顺序、版本、错误码、结果和总计,减少只看测试退出码的盲区。

四组场景对应5个测试、21次HTTP交换与5次业务调用的明细
图 3:计数口径来自完整日志,GET 405 和 notifications/initialized 均保留

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 客户端或任何宿主。