资讯详情

MCP TypeScript SDK 网关模式实战:用 `connect({ prior })` 实现一次探测、零往返连接与全集群复用

📅 2026/9/15 20:49:06 | 华诺云谱 👁 阅读
MCP TypeScript SDK 网关模式实战:用 `connect({ prior })` 实现一次探测、零往返连接与全集群复用
MCP TypeScript SDK 网关模式实战用connect({ prior })实现一次探测、零往返连接与全集群复用【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk本指南以仓库中的 gateway 示例 为主线讲解 Model Context ProtocolMCPTypeScript SDK 面向网关、代理与水平扩展 worker 集群的经典接入模式connect({ prior: { kind: modern, discover } })零往返连接。你将掌握如何用一次server/discover探测换取任意数量的免协商连接如何用持久化的DiscoverResult让每个 worker 首次调用即可直接callTool以及该模式的安全边界与陈旧裁决处理策略。为什么需要零往返连接2026-07-28 协议在 HTTP 上的无状态化在协议修订2026-07-28即文档所称modern era中MCP 在 HTTP 传输上是无状态的每个请求都会携带逐请求的_meta信封协议版本、客户端信息、客户端能力因此一旦客户端知道了服务器的DiscoverResult就没有任何东西需要再协商。这正是网关场景的核心收益。一个网关、代理或 worker 集群如果都前置同一台服务器就不应该让每个 worker 各自重新探测——正确的做法是探测一次之后每一次 connect 都是零开销2026 protocol isstateless on HTTP: every request carries the per-request_metaenvelope (protocol version, client info, client capabilities), so once you know the serversDiscoverResultthere is nothing left to negotiate.从类型定义看DiscoverResult承载了服务器能力协商所需的全部信息supportedVersions服务器支持的协议版本列表、capabilities服务器能力、instructions面向 LLM 的自然语言使用指引并继承CacheableResult的缓存提示字段ttlMs、cacheScope、resultType——见 packages/core-internal/src/types/spec.types.2026-07-28.ts。这份广告足够让任意后续客户端直接进入可用状态。快速上手运行网关示例在仓库根目录安装好 workspace 依赖后示例的依赖与脚本约定见 examples 目录说明分别启动服务端与客户端pnpm --filter mcp-examples/gateway server -- --http --port 3000 pnpm --filter mcp-examples/gateway client -- --http http://127.0.0.1:3000/两条命令分别对应 package.json 中的server: tsx server.ts与client: tsx client.ts脚本。这个示例是HTTP-only的原因在 package.json 的注释里写得很清楚断言故事依赖createMcpHandler每个请求构建一个 server 实例的工厂语义而 stdio 下工厂是每连接构建一次2/3/7的计数断言将不再成立。服务端 server.ts 是一个普通的 2026 时代 MCP 服务器注册了三个工具echo原样回显输入文本uppercase将输入转为大写request_count返回到达本进程的 MCP 请求总数——这是整个模式证明的关键仪器。其中request_count的注释点明了计数原理createMcpHandler为每个入站请求构建一个 server 实例因此模块级计数器就等价于被服务的 MCP 请求数server/discover、tools/call等参见 server.ts。服务端还通过localhostHostValidation/localhostOriginValidation在 handler 前置了回环主机与来源校验被拒绝的请求由守卫自行应答不会计入工厂计数。核心模式探测一次持久化零往返复用示例的核心代码段展示了完整的bootstrap → 持久化 → worker 复用流程// 1. Bootstrap: probe once. const bootstrap new Client({ name: bootstrap, version: 1.0.0 }, { versionNegotiation: { mode: auto } }); await bootstrap.connect(new StreamableHTTPClientTransport(url)); const persisted JSON.stringify(bootstrap.getDiscoverResult()); // → write to Redis / config / process-local cache await bootstrap.close(); // 2. Every worker: zero-round-trip connect from the persisted blob. const worker new Client({ name: worker, version: 1.0.0 }); await worker.connect(new StreamableHTTPClientTransport(url), { prior: { kind: modern, discover: JSON.parse(persisted) } }); await worker.callTool({ name: echo, arguments: { text: hi } }); // first wire trafficgetDiscoverResult()从哪里来getDiscoverResult()的取值有三种来源SDK 文档注释明确列出versionNegotiation: { mode: auto }或 pin 模式的探测路径connect 时发送server/discover并记录结果显式调用client.discover()modern 连接上可随时重新探测并更新结果legacy 连接上discover()会抛错因为server/discover不是 2025 时代的方法一次 modern 裁决的connect({ prior })直接采用 prior 中的DiscoverResult。而legacy 裁决的连接会让getDiscoverResult()保持undefined。该值可以经过JSON.stringify/JSON.parse完整往返——也就是说它在进程间、主机间天然可持久化。从 SDK 实现看packages/client/src/client/client.ts 中getDiscoverResult()只是返回_discoverResult这个按连接维护的 backing store见 client.tsclient.discover()则通过_requestWithSchema发送server/discover并用DiscoverResultSchema校验后记录client.ts。零往返连接在 SDK 内部做了什么传入prior后connect 走的是 client.ts 的_connectFromPrior私有路径其关键逻辑是计算客户端与现代版本的交集clientModern.find(v discover.supportedVersions.includes(v))若交集为空在传输启动之前抛出SdkError(SdkErrorCode.EraNegotiationFailed)——因此同一个Client实例还能安全地再次 connect 走降级路径否则await super.connect(transport)后直接采用prior 中的DiscoverResult同步填充_discoverResult、_serverCapabilities、_serverVersion、_instructions与_negotiatedProtocolVersion并调用transport.setProtocolVersion。注意_connectFromPrior的注释强调No auto-opened listen stream on this path (request-only workers)——即 modern 裁决的connect({ prior })客户端是纯请求型的SDK 不会自动打开subscriptions/listen流若某个 worker 需要接收通知须自行调用listen()。另外prior在进入连接流程前会经过 client.ts 的validatePrior校验一个kind: legacy的裁决只要不含supportedVersions/discover成员即视为合法modern 分支则要求DiscoverResultSchema.safeParse通过。注释还指出blob is adopted verbatim即未知的嵌套成员会在getDiscoverResult()重新持久化时原样保留不会丢失。用request_count证明三次 connect 发送了零个请求示例的价值在于它用可验证的计数证明零往返不是口头承诺。客户端 client.ts 通过request_count工具做出一组精确断言bootstrap 探测 一次request_count调用后计数为2探测是第一个请求request_count自身是第二个三次 workerconnect({ prior }) 一次request_count调用后计数仍为3——证明三次 connect 在网络上发送了零个请求若每个 worker 都探测/初始化这里会读到 6每个 worker 可以立即callToolecho结果逐一校验三次echo调用 一次request_count调用后计数为73 3 次 echo 本次计数调用。client.ts 中还有两处对采用 prior语义的验证每个 worker 的getNegotiatedProtocolVersion()直接等于2026-07-28无需协商即已确定getServerVersion()返回{ name: gateway-target, version: 1.0.0 }——后者从 discover 结果的_meta[io.modelcontextprotocol/serverInfo]解析而来spec PR #3002 之后服务端身份为 SHOULD 级匿名服务器会得到undefined见 client.ts。何时使用prior原文档给出了三个典型的适用场景网关 / 代理持有到同一服务器的长连接池但为每个下游请求构造全新的Client——新客户端无需重复协商直接接入现有池水平扩展的主机一个 worker 的探测结果应播种整个集群把 blob 写入共享缓存如 Redis / config map / 进程本地缓存瞬态传输断线重连连接因网络抖动中断后重连时无需重新探测即可恢复。安全边界仅在同一授权上下文内复用这是该模式最重要的约束。只能在呈现与 bootstrap 客户端相同授权上下文的 worker 之间复用持久化的DiscoverResult——实践中应以凭据哈希作为 blob 的键。原因有二采用更宽泛的prior并不会授予访问权限——服务器仍会对每个请求做独立授权但它会误导客户端侧的能力门控若一个 worker 的凭据与探测者不同它基于陈旧DiscoverResult做出的能力判断可能与服务器实际授权结果不符。示例客户端注释也强调Do not share aDiscoverResultacross principals示例中所有客户端都指向同一无认证端点因此约束平凡成立。详细讨论见 docs/advanced/gateway.md 的 Reuse only within one authorization context 一节。两种裁决形态modern 与 legacy以及新鲜度责任PriorDiscovery即prior的类型在 probeClassifier.ts 中定义只有两个分支export type PriorDiscovery /** Adopt discover directly: zero round trips. */ | { kind: modern; discover: DiscoverResult } /** Known-legacy server: skip the server/discover probe and run the plain legacy initialize handshake. */ | { kind: legacy };modern 裁决是 modern-only 的prior: { kind: modern, discover }包裹的持久化DiscoverResult是仅限现代的若双方没有 2026-07-28 的共同协议修订则抛出SdkError(EraNegotiationFailed)正如前文所述该错误发生在任何网络流量之前。这对应ProbeVerdict中{ kind: modern; version; discover }的裁决形态probeClassifier.ts。legacy 裁决跳过探测直达 initialize对于已知是 legacy2025 时代的服务器应改传否定裁决prior: { kind: legacy }会跳过server/discover探测直接走普通initialize握手——其实现byte-identical to amode: legacyconnect见 client.ts。这在出带元数据已经确认服务器是 pre-2026如注册表条目、先前连接的结论时尤其有价值否则mode: auto的探测每次 connect 都会白白失败一次往返。新鲜度是宿主host的责任SDK 会原样采用你交给它的任何裁决。两种陈旧裁决的失效模式截然不同陈旧的 modern 裁决在第一次请求时响亮失败版本交集为空 →EraNegotiationFailed可被检测并触发重新探测陈旧的 legacy 裁决会永久静默成功——因为升级后的服务器仍然应答initialize没有任何机制会纠正它。因此必须在自己的存储中为缓存的 legacy 裁决打上日期并在超出策略期限后停止提供而完全不提供prior时mode: auto的客户端会重新探测从而自然发现服务器升级。完整的主机侧缓存循环含新鲜度窗口、wire 层探测计数验证见 docs/advanced/gateway.md 的 Caching discovery verdicts 一节。值得一提的还有探测分类器的保守性设计见 probeClassifier.ts 头部注释401/403授权拒绝被归类为类型化错误ClientHttpAuthentication/ClientHttpForbidden永远不会被当作时代裁决——这避免了把授权墙误存为 legacy 裁决而在集群层面复现静默 legacy 缺陷。小结connect(transport, { prior: { kind: modern, discover } })直接采用持久化的DiscoverResult零往返完成连接首次callTool即产生第一条线上流量广告来源可以是mode: auto/ pin 探测、显式client.discover()或一次 modern 裁决连接getDiscoverResult()读取它legacy 连接下其为undefined该值是纯 JSON可 stringify 进共享缓存、parse 后在任何前置同一服务器的进程中复用仅在与探测者相同授权上下文的客户端之间复用DiscoverResultmodern 裁决是 modern-only无 2026-07-28 交集即EraNegotiationFailed已知 legacy 服务器用prior: { kind: legacy }且必须自行管理其新鲜度modern 裁决客户端是 request-only 的需要通知时自行listen()。若需进一步深入可继续阅读完整的网关攻略 docs/advanced/gateway.md、协议版本与协商模式总览 docs/protocol-versions.md、以及 SDK 侧的核心实现 packages/client/src/client/client.ts 与探测分类器 packages/client/src/client/probeClassifier.ts。【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。