news 2026/9/15 10:41:07

在 typescript-sdk 中通过 supportedProtocolVersions 声明自定义 MCP 协议版本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 typescript-sdk 中通过 supportedProtocolVersions 声明自定义 MCP 协议版本

在 typescript-sdk 中通过 supportedProtocolVersions 声明自定义 MCP 协议版本

【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk

本篇文章围绕@modelcontextprotocol/server提供的ServerOptions.supportedProtocolVersions配置项,以仓库中的 custom-version 示例 为实战主线,讲解如何让 MCP 服务器声明 SDK 尚未内置支持的协议版本、如何让列表中的首个版本充当客户端请求不兼容版本时的回退目标,以及这套机制在 initialize 握手与版本协商链路中的底层实现。读完本文,你将能够为你的 MCP 服务器接入未来的协议版本,同时保持对旧客户端的兼容,并理解版本协商的前置条件与边界。

一、为什么要自定义协议版本:MCP 版本协商的背景

Model Context Protocol 在客户端与服务端建立连接时,通过 JSON-RPC 的initialize请求完成一次协议版本协商:客户端声明自己使用的协议版本,服务端在响应中给出双方都能接受的版本。为了让不同时期实现的客户端与服务端能够互通,协议以日期字符串(如2025-03-262025-06-182025-11-25)作为版本标识。

SDK 在 packages/core/src/constants.ts 中维护了当前内置支持的版本集合:

export const LATEST_PROTOCOL_VERSION = '2025-11-25'; export const DEFAULT_NEGOTIATED_PROTOCOL_VERSION = '2025-03-26'; export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07'];

可以看到,SDK 默认支持从2024-10-072025-11-25共五个版本,并且默认协商版本为2025-03-26。当协议规范发布了比2025-11-25更新的修订版本、而当前 SDK 版本尚未将其纳入内置列表时,你就需要一种“前瞻式”声明能力——这正是supportedProtocolVersions存在的意义。

二、核心配置项:ServerOptions.supportedProtocolVersions

在 packages/core-internal/src/shared/protocol.ts 中,协议层的基础选项类型定义了该字段:

supportedProtocolVersions?: string[];

它的语义可以用一句话概括(与 custom-version README 的描述一致):

  • 声明支持 SDK 尚未内置的协议版本:数组中可以放入任何你希望服务器支持的版本字符串;
  • 列表中的第一个版本是回退版本(fallback):当客户端在initialize请求中声明的版本不在该列表中时,服务器会退回到列表的第一项完成协商。

该配置同时存在于两个层面:

配置位置类型说明
@modelcontextprotocol/serverServerOptionsstring[]高级 APIMcpServer与底层Server均可传入
packages/server/src/server/streamableHttp.ts 的 Streamable HTTP 传输选项string[]传输层面向新版本协商的配置入口

从源码结构看,底层Server在构造函数中将该列表保存在实例字段_supportedProtocolVersions上,后续的版本协商、server/discover处理器注册等逻辑都会读取它(见 packages/server/src/server/server.ts)。

三、示例全解析:custom-version 的服务器与客户端

示例位于 examples/custom-version/,包含四个文件:

examples/custom-version/ ├── README.md # 运行说明与核心概念 ├── client.ts # 验证客户端 ├── package.json # 依赖与脚本 └── server.ts # 自定义版本服务器

3.1 服务器端:声明自定义版本

server.ts 的核心逻辑非常精炼。首先,它导入 SDK 内置的版本常量,并在此基础上拼接出自定义版本列表:

import { createMcpHandler, McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server'; // Add support for a newer protocol version (first in list is fallback). const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS];

2026-01-01是一个当前 SDK 尚未内置的假设性协议版本。将它放在数组首位,意味着:

  • 服务器宣称自己支持2026-01-01以及 SDK 内置的全部五个版本;
  • 当某个客户端请求了列表之外的版本(例如更早的2024-01-01)时,协商结果回退到2026-01-01

接着,在构造McpServer时传入该列表:

const server = new McpServer( { name: 'custom-protocol-server', version: '1.0.0' }, { supportedProtocolVersions: CUSTOM_VERSIONS, capabilities: { tools: {} } } ); server.registerTool('get-protocol-info', { description: 'Returns protocol version configuration' }, async () => ({ content: [{ type: 'text', text: JSON.stringify({ supportedVersions: CUSTOM_VERSIONS }) }] }));

服务器注册了一个名为get-protocol-info的工具,把CUSTOM_VERSIONS以 JSON 字符串的形式返回给客户端,用于让客户端断言服务器确实广告了自定义版本。

示例还展示了“一份代码,两种传输”的写法:通过parseExampleArgs()(来自@mcp-examples/shared,见 examples/shared/src/args.ts)解析命令行参数,在 stdio 与 HTTP 之间切换:

const { transport, port } = parseExampleArgs(); if (transport === 'stdio') { void serveStdio(buildServer); console.error('[server] serving over stdio'); } else { const handler = createMcpHandler(buildServer); // `createMcpHonoApp()` binds the endpoint behind localhost host/origin // validation by default, matching the framework factories' defaults. const app = createMcpHonoApp(); app.all('/mcp', c => handler.fetch(c.req.raw)); serve({ fetch: app.fetch, port, hostname: '127.0.0.1' }, () => { console.error(`[server] listening on http://127.0.0.1:${port}/mcp`); }); }
  • 使用serveStdio走标准输入输出(适合作为子进程被客户端拉起);
  • 使用createMcpHonoApp()结合 Hono 的@hono/node-server暴露POST /mcp端点,且默认绑定127.0.0.1,与框架工厂的默认行为保持一致。

3.2 客户端端:以 legacy 模式验证协商与回退

client.ts 的注释点明了它的设计意图:用“一个普通客户端”来验证服务器广告的自定义版本,同时借助一个不在服务器支持列表中的版本触发回退路径。

const client = new Client( { name: 'custom-version-example-client', version: '1.0.0' }, { versionNegotiation: { mode: 'legacy' } } );

这里显式设置了versionNegotiation: { mode: 'legacy' }。结合示例 package.json 中example.era字段的说明可以确认:supportedProtocolVersions与版本协商属于 2025 时代(era)的 initialize 握手机制;2026-07-28 起的新时代拥有自己独立的协商叙事,对应仓库中的 dual-era 示例。因此在涉及自定义协议版本的 legacy 场景中,客户端必须显式选择 legacy 协商模式,而不能依赖现代时代(modern era)的协商路径。

随后,客户端根据传输方式连接服务器,并调用get-protocol-info工具断言服务器广告的版本列表:

const result = await client.callTool({ name: 'get-protocol-info' }); const text = result.content?.[0]?.type === 'text' ? result.content[0].text : '{}'; const info = JSON.parse(text) as { supportedVersions: string[] }; check.ok(info.supportedVersions.includes('2026-01-01')); check.ok(info.supportedVersions.length > 1); await client.close();

两个断言分别验证:服务器确实声明了自定义版本2026-01-01,且列表不止一个元素(说明内置版本仍然保留、向后兼容性未被破坏)。

3.3 依赖与运行脚本

package.json 提供了两个便捷脚本:

"scripts": { "server": "tsx server.ts", "client": "tsx client.ts" }

运行整个示例(仓库根目录下执行,等价于先起服务器再运行客户端):

pnpm tsx examples/custom-version/client.ts

四、版本协商与回退的底层原理

4.1 内置版本集合的导出路径

SUPPORTED_PROTOCOL_VERSIONS@modelcontextprotocol/core的 constants.ts 定义,并经@modelcontextprotocol/server对外导出。服务器端示例直接import { SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server',就可以拿到 SDK 当前认可的完整版本集合作为自定义列表的基座。

4.2 协商时的版本归类:legacy 与 modern

从 packages/core-internal/src/shared/protocolEras.ts 的源码可以看到,SDK 内部用legacyProtocolVersionsmodernProtocolVersions两个函数对支持列表做归类筛选。底层Server的初始化流程(packages/server/src/server/server.ts)中,版本协商与能力暴露逻辑会基于这些归类结果运行:

  • 若支持列表中存在modern 版本(2026 时代修订),服务器会自动注册server/discover处理器;
  • 若支持列表中只有legacy 版本(2025 时代及以前),服务器保持纯 legacy 行为,server/discover请求会得到-32601(方法未找到)的响应。

custom-version 示例中的2026-01-01虽然看起来像是“更新”的日期,但它被写入的是 2025 时代 initialize 握手的支持列表,并不等同于把服务器切换到 2026-07-28 现代时代——这正是示例中era: "legacy"标注的语义。

4.3 第一个版本即回退版本的实现语义

README 中“first version in the list is the fallback”的规则,由协议层的协商算法体现:当客户端在initialize请求中给出的协议版本不在服务器的supportedProtocolVersions列表中时,服务器不会直接拒绝连接,而是回退到列表的第一项作为协商结果返回。这种“降级而非拒绝”的设计,保证了支持自定义新版本的服务器仍然能与行为保守的旧客户端正常建立会话。

4.4 一个值得注意的实现细节:共享常量不做原地修改

在 packages/server/src/server/server.ts 的installDiscoverHandler实现中,有一段明确注释的工程约束:

const missing = servedModernVersions.filter(version => !server._supportedProtocolVersions.includes(version)); if (missing.length > 0) { // Never mutate the existing array in place: the default supported-versions // list is a shared module constant. server._supportedProtocolVersions = [...server._supportedProtocolVersions, ...missing]; }

默认的SUPPORTED_PROTOCOL_VERSIONS是模块级共享常量,SDK 在需要扩展支持列表时永远通过展开运算符复制出新数组,绝不原地修改,避免一个实例的配置污染所有实例。你在自己拼接CUSTOM_VERSIONS时也应遵循同样的不可变风格(示例中的['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS]正是如此)。

五、实战指南:为你的服务器接入新协议版本

综合示例代码与源码语义,接入一个 SDK 尚未内置的协议版本需要以下步骤:

  1. 确定自定义版本号:协议版本号使用 ISO 日期字符串(如2026-01-01),需与 MCP 规范中实际发布的修订版本一致;
  2. 拼接支持列表:将新版本放在数组首位(作为回退目标),其后展开SUPPORTED_PROTOCOL_VERSIONS保留全部内置版本:
import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server'; const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS]; const server = new McpServer( { name: 'my-server', version: '1.0.0' }, { supportedProtocolVersions: CUSTOM_VERSIONS, capabilities: { tools: {} } } );
  1. 注意 era 边界supportedProtocolVersions作用于 2025 时代的 initialize 握手;如果你的目标是完整支持 2026-07-28 现代时代的协商,需要走新时代的协商入口(如 streamableHttp.ts 中传输层对应的supportedProtocolVersions选项),并参考 dual-era 示例 的双时代共存方案;
  2. 验证广告结果:模仿示例中的get-protocol-info工具,让客户端在运行时读取服务器实际广告的版本列表,用断言确认自定义版本已生效、内置版本仍被保留;
  3. 测试回退行为:用一个声明了列表外版本的客户端发起连接,确认服务器回退到列表第一项而不是报错中断。

六、验证路径与测试参考

如果你希望进一步验证这套机制的实现正确性,仓库中提供了直接的测试参照:

  • packages/server/test/server/streamableHttpUnsupportedVersionLiteral.test.ts:针对“客户端请求不支持的字面版本”场景的测试,其中connectedTransport(supportedProtocolVersions?)辅助函数接受自定义版本列表,验证了 Streamable HTTP 传输层的版本处理行为;
  • packages/core-internal/test/shared/protocolEras.test.ts:覆盖legacyProtocolVersions/modernProtocolVersions的归类逻辑;
  • packages/server/test/server/server.test.ts:包含对SUPPORTED_PROTOCOL_VERSIONS相关行为的断言。

七、小结

ServerOptions.supportedProtocolVersions是 typescript-sdk 为“协议演进先行者”准备的扩展点:通过一个简单的字符串数组,你就能让服务器前瞻性地声明 SDK 尚未内置的协议版本,并利用“首项回退”规则优雅地兼容所有旧客户端。结合 custom-version 示例,你可以在几分钟内完成从“内置版本”到“自定义版本”的切换,同时通过get-protocol-info工具与现有测试体系获得可靠的验证闭环。需要注意的是,该机制属于 2025 时代的 initialize 握手语义,面向 2026-07-28 新时代的协商请移步 dual-era 示例 了解独立方案。

【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:33:41

开会汇报高效出图:快速生成逻辑清晰思维导图APP全指南

文章摘要:本教程围绕职场开会汇报场景,讲解借助 AI 快速产出逻辑严谨思维导图的底层原理,横向对比百度文库、Xmind AI、EdrawMind、知犀、GitMind、boardmix、ProcessOn、万兴脑图共 8 款主流 APP 的能力差异,提供各工具分步实操流…

作者头像 李华
网站建设 2026/9/15 10:33:24

AOP+自定义注解,实现redis缓存

添加如下依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifact…

作者头像 李华
网站建设 2026/9/15 10:32:33

Android buildTypes和productFlavors实现差异化打包

什么是buildTypes和productFlavors https://developer.android.com/build/build-variants?hlzh-cn 1. buildTypes buildTypes 主要用于定义不同的构建变体&#xff0c;通常用于区分不同的构建模式&#xff0c;如开发版、发布版等。每个 buildType 都会有一组配置项&#xf…

作者头像 李华
网站建设 2026/9/15 10:31:58

低功耗策略的收益与风险平衡:工程落地黄金点判定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华