在 Model Context Protocol TypeScript SDK 中实现双向自定义 JSON-RPC 方法:从 vendor 前缀请求到进度通知与扩展能力
在 Model Context Protocol TypeScript SDK 中实现双向自定义 JSON-RPC 方法从 vendor 前缀请求到进度通知与扩展能力【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk本指南以 examples/custom-methods/README.md 为骨架系统讲解在 Model Context ProtocolMCPTypeScript SDK 中编写非规范non-spec自定义 JSON-RPC 方法的完整链路服务端如何用server.setRequestHandler处理 vendor 前缀请求、如何在处理器内通过ctx.mcpReq.notify反向推送通知客户端如何用client.request(method, schema)发起类型化请求、用client.setNotificationHandler订阅通知以及如何通过capabilities.extensions声明扩展能力。读完你即可在 MCP 服务器与客户端之间实现规范之外的双向通信并让每个方法都享受 Schema 校验与类型推断。什么是自定义方法vendor 前缀是铁律MCP 规范定义了initialize、tools/call、resources/read等标准 JSON-RPC 方法。当业务需要规范之外的通信例如专属搜索引擎、内部 RPC、任务进度上报时就可以定义自定义方法custom method——它是 JSON-RPC 层面的普通请求或通知只是方法名不在 MCP 规范中。规范对命名有一条硬性要求必须带 vendor 命名空间前缀。文档原文如此强调Prefix it with a vendor namespace —acme/search, never a baresearch——写acme/search绝不写裸的search。前缀的目的是保证任何自定义方法都不可能和未来的规范方法发生碰撞collide。这一规则同样适用于扩展能力标识符如com.example/feature-flags和自定义通知方法名如acme/searchProgress。从仓库代码结构看SDK 对自定义方法没有任何特殊后门它在低层Serverpackages/server/src/server/server.ts上通过通用的请求分发机制承载方法名只是一个字符串键。这正是为什么命名空间规则如此重要——没有前缀保护一个search就可能在未来与规范方法冲突。服务端用setRequestHandler处理 vendor 前缀请求setRequestHandler位于低层Server上从高层McpServer可以通过mcp.server访问到它。对于非规范方法必须显式传入 schema 包第二个参数是{ params, result }处理器收到的params是已经解析并校验过的对象而不是原始的 JSON-RPC 请求。以下代码来自 examples/guides/advanced/custom-methods.examples.ts与 docs/advanced/custom-methods.md 中的代码片段同源由pnpm sync:snippets同步import { McpServer } from modelcontextprotocol/server; import * as z from zod/v4; const SearchParams z.object({ query: z.string(), limit: z.number().int().default(10) }); const SearchResult z.object({ items: z.array(z.string()) }); const mcp new McpServer({ name: acme-search, version: 1.0.0 }); mcp.server.setRequestHandler(acme/search, { params: SearchParams, result: SearchResult }, async ({ query, limit }) { return { items: Array.from({ length: limit }, (_, index) ${query}-${index}) }; });这里的关键语义params: SearchParamsSDK 在处理器运行之前就对入站params做 Zod 校验。文档明确给出一个可验证的行为发送query: 42这类非法参数请求会在处理器执行前失败调用方收到Invalid params的 JSON-RPC 错误错误消息形如Invalid params for acme/search: query: Invalid input: expected string, received number这个行为不是文档的一面之词——examples/guides/advanced/custom-methods.examples.ts 的 harness 部分专门构造了一次{ query: 42 }的请求断言其必须被拒绝否则整个示例以非零退出码失败try { await client.request({ method: acme/search, params: { query: 42 } }, SearchResult); } catch (error) { rejection error as Error; } if (!rejection) { throw new Error(custom-methods.md tip claim failed: invalid params were accepted); }result: SearchResult为处理器的返回值提供类型约束同时参与出站编码。规范方法不传 schemasetRequestHandler(tools/call, handler)这类调用从不携带 schema 包——SDK 会根据方法名自动解析其 schema。这一点是自定义方法与规范方法在 API 形态上的分界线。一个二合一示例stdio 与 HTTP 双传输examples/custom-methods/server.ts 展示了同一份 handler 逻辑如何在 stdio 与 Streamable HTTP 两种传输下复用。核心工厂函数buildServer就是上面setRequestHandler注册逻辑的完整版function buildServer(): McpServer { const mcp new McpServer({ name: acme-search, version: 0.0.0 }); mcp.server.setRequestHandler(acme/search, { params: SearchParams, result: SearchResult }, async (params, ctx) { await ctx.mcpReq.notify({ method: acme/searchProgress, params: { stage: start, pct: 0 } }); const items Array.from({ length: params.limit }, (_, i) ${params.query}-${i}); await ctx.mcpReq.notify({ method: acme/searchProgress, params: { stage: done, pct: 1 } }); return { items }; }); return mcp; } const { transport, port } parseExampleArgs(); if (transport stdio) { void serveStdio(buildServer); console.error([server] serving over stdio); } else { const handler createMcpHandler(buildServer); const app createMcpHonoApp(); // 默认启用 localhost host/origin 校验 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); }); }注意setRequestHandler的处理器签名在这里接收(params, ctx)两个参数params是已校验的入参对象ctx是ServerContext其中ctx.mcpReq提供了向当前正在处理的这个请求的对端发回通知的能力见下文。客户端用client.request(method, schema)发起类型化请求调用自定义方法同样要遵守非规范方法必须带 schema的对称规则。client.request的第一个参数是请求对象第二个参数是result schemaSDK 会在 Promise resolve 之前用该 schema 校验响应并从它推断返回类型const result await client.request({ method: acme/search, params: { query: mcp, limit: 3 } }, SearchResult); console.log(result);校验并类型化后的返回值{ items: [ mcp-0, mcp-1, mcp-2 ] }同样的对称规则适用于规范方法client.request({ method: tools/list })不需要 schemaSDK 会根据方法名解析与服务端setRequestHandler的行为完全一致。examples/custom-methods/client.ts 给出了完整的客户端形态——它默认通过 stdio 拉起同目录的server.ts也可以--http url连接一个正在运行的服务端点并据此选择传输import { Client, StreamableHTTPClientTransport } from modelcontextprotocol/client; import { StdioClientTransport } from modelcontextprotocol/client/stdio; import { z } from zod/v4; const SearchResult z.object({ items: z.array(z.string()) }); const SearchProgressParams z.object({ stage: z.string(), pct: z.number() }); const client new Client( { name: custom-methods-example-client, version: 1.0.0 }, { versionNegotiation: { mode: era modern ? auto : legacy } } ); await client.connect( transport stdio ? new StdioClientTransport({ command: npx, args: [-y, tsx, siblingPath(import.meta.url, server.ts)] }) : new StreamableHTTPClientTransport(new URL(url)) );关于协议时代的细节注释里有一句值得注意的说明vendor 前缀方法会原样穿过两种服务入口serving entry——2025 时代的客户端发送裸 JSON-RPC 请求2026-07-28 时代的客户端则携带 per-request envelope而服务端setRequestHandler对两者一视同仁。换句话说自定义方法的处理逻辑与协议版本协商解耦你不需要为时代差异写两套 handler。从处理器反向推送ctx.mcpReq.notify自定义**通知notification**是自定义请求的单向镜像同样 vendor 前缀、同样没有 result。服务端要推送通知就用处理器上下文里的ctx.mcpReq.notify。文档明确说明其语义它把每条通知发送给其请求正在被处理的那个对端且复用同一条连接——即通知绑定到当前正在处理的请求in-flight exchange上而不是随随便便发到某个会话。重复注册同一个方法名会替换原有 handler。因此把上一节的acme/searchhandler 替换为下面这个带进度上报的版本即可mcp.server.setRequestHandler(acme/search, { params: SearchParams, result: SearchResult }, async ({ query, limit }, ctx) { await ctx.mcpReq.notify({ method: acme/searchProgress, params: { stage: start, pct: 0 } }); const items Array.from({ length: limit }, (_, index) ${query}-${index}); await ctx.mcpReq.notify({ method: acme/searchProgress, params: { stage: done, pct: 1 } }); return { items }; });在仓库的服务端实现里ctx.mcpReq.notify与进度上报、日志通知走同一条related-request通道packages/server/src/server/server.ts 中log方法的实现注释明确指出请求相关的通知如 progress 和ctx.mcpReq.notify会携带 related-request 标记从而在 in-flight exchange 上投递如果没有这个标记per-request 托管createMcpHandler无论哪个时代会因为找不到会话级的流而静默丢弃消息。这从实现层面印证了通知跟随请求、走同一连接的行为保证。客户端接收通知setNotificationHandler客户端接收规则与setRequestHandler完全对称非规范通知方法需要{ params }schemahandler 收到的是解析后的 params 对象const SearchProgressParams z.object({ stage: z.string(), pct: z.number() }); client.setNotificationHandler(acme/searchProgress, { params: SearchProgressParams }, params { console.log(params); }); await client.request({ method: acme/search, params: { query: mcp, limit: 1 } }, SearchResult);上面这一次请求调用会依次打印两个阶段的进度{ stage: start, pct: 0 } { stage: done, pct: 1 }在 examples/custom-methods/client.ts 中通知 handler 被用来累积阶段信息并与预期比对check.deepEqual(stages, [start, done])同时校验结果check.deepEqual(result.items, [mcp-0, mcp-1, mcp-2])整个示例本身就是请求-通知-结果全链路的可运行测试。扩展能力registerCapabilities声明、getServerCapabilities读取除方法级通信外SDK 还支持在**能力协商capability negotiation**层面暴露 vendor 特性capabilities.extensions是一个映射把带前缀限定的扩展标识符映射到该扩展的设置对象。服务端声明必须在连接前通过registerCapabilities声明且必须在连接 transport 之前调用mcp.server.registerCapabilities({ extensions: { com.example/feature-flags: { flags: [dark-mode, beta-search] } } });连接前调用不是口头约定而是硬性约束在 packages/server/src/server/server.ts 中registerCapabilities的实现开头就会检查if (this.transport)一旦已经连接就直接抛出SdkError(SdkErrorCode.AlreadyConnected, Cannot register capabilities after connecting to transport)。连接前调用时新能力会与已有能力合并mergeCapabilities而非整体覆盖。设置值本身是自由格式 JSON{}表示支持该扩展但无设置。客户端读取协商结果连接建立后客户端通过client.getServerCapabilities()读取服务端宣告的能力映射const extensions client.getServerCapabilities()?.extensions ?? {}; console.log(extensions);读到的映射与服务端声明的完全一致{ com.example/feature-flags: { flags: [ dark-mode, beta-search ] } }文档还指出了该能力在不同协议时代的承载位置legacy2025连接在initialize结果中宣告2026-07-28 连接则在server/discover中宣告——详见 协议版本说明。从客户端源码结构看getServerCapabilities返回的就是协商后缓存的服务器能力对象extensions只是其中的一个可选字段因此用?? {}兜底是稳妥的写法。一键运行克隆示例并观察双向通信仓库中的 examples/custom-methods 目录自带package.json客户端与服务端共享同一套 schema 定义。在仓库根目录需要先安装依赖、并已具备 pnpm 与 tsx运行pnpm tsx examples/custom-methods/client.ts客户端会默认通过 stdio 拉起同目录的server.ts随后注册acme/searchProgress通知处理器 → 发起acme/search请求 → 服务端处理器先推{ stage: start, pct: 0 }再推{ stage: done, pct: 1 }→ 返回{ items: [mcp-0, mcp-1, mcp-2] }→ 客户端用check.deepEqual逐一断言通过。若要验证 HTTP 形态可先启动服务端再指定 URL 连接server.ts支持--http --port N默认 stdio监听127.0.0.1。要点回顾自定义方法/通知/扩展标识符都必须vendor 前缀限定acme/search、com.example/feature-flags绝不使用裸词。服务端setRequestHandler(method, { params, result }, handler)处理非规范方法SDK 在 handler 运行前校验params失败即返回Invalid params错误result约束返回值的类型。规范方法永不携带 schema 包。客户端client.request(request, ResultSchema)是调用侧SDK 在 resolve 前校验响应并据此推断返回类型。自定义通知是自定义请求的单向镜像一端ctx.mcpReq.notify跟随当前请求、同一连接投递另一端setNotificationHandler(method, { params }, handler)。扩展能力连接前用registerCapabilities({ extensions: {...} })声明连接后调用会抛AlreadyConnected错误连接后用client.getServerCapabilities()?.extensions读取legacy 时代承载于initialize结果2026-07-28 时代承载于server/discover。更完整的文档可继续阅读 docs/advanced/custom-methods.md其全部代码片段均从可独立运行的 examples/guides/advanced/custom-methods.examples.ts 同步而来。【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Your Developer Growth Report

Your Developer Growth Report

Your Developer Growth Report 【免费下载链接】awesome-codex-skills A curated list of practical Codex skills for automating workflows across the Codex CLI and API. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-codex-skills Report Period: [Y…

2026/10/31 19:15:59 阅读更多 →
Docker部署ClickHouse实战:从容器配置到OLAP查询优化

Docker部署ClickHouse实战:从容器配置到OLAP查询优化

1. 先搞清楚:为什么我要用 Docker 来跑 ClickHouse做数据相关工作的朋友应该对 ClickHouse 不陌生,它是一个标准的列式 OLAP 数据库,单机就能扛住每秒百万行级别的写入,聚合查询比传统行式数据库快一到两个数量级。但很多人在第一…

2026/11/6 4:34:35 阅读更多 →
DeepEP V2 完全指南:基于 ElasticBuffer 的高性能专家并行通信库实践

DeepEP V2 完全指南:基于 ElasticBuffer 的高性能专家并行通信库实践

DeepEP V2 完全指南:基于 ElasticBuffer 的高性能专家并行通信库实践 【免费下载链接】DeepEP DeepEP: an efficient expert-parallel communication library 项目地址: https://gitcode.com/GitHub_Trending/de/DeepEP DeepEP(DeepEveryParallel…

2026/11/1 18:59:52 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/6 15:21:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/7 12:08:40 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/6 22:01:11 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/6 22:01:11 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/7 12:08:40 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/6 22:01:09 阅读更多 →

日新闻

周新闻

月新闻