FEATURED · 精选文章

WebSocket与JSON-RPC 2.0:构建AI模型与MCP Server的稳定通信协议

发布时间 / 2026/8/18 5:00:17
来源 / 创域科博编辑部
栏目 / 资讯中心
WebSocket与JSON-RPC 2.0:构建AI模型与MCP Server的稳定通信协议 1. 从一次“哑火”的对话说起为什么需要深究这个协议最近在折腾一个智能体项目想把一个本地部署的“小鸿AI”模型代号WS63的能力通过一个标准的MCPModel Context Protocol Server暴露出去让外部的工具链能方便地调用。想法很美好本地模型负责思考MCP Server负责标准化接口中间用WebSocket连起来数据流不就通了吗结果一跑起来就傻眼了。客户端发来的请求模型那边收到了但返回的响应要么格式不对被MCP Server拒收要么干脆石沉大海连接倒是没断但对话就是进行不下去。查日志两边都在报错一个说“无法解析消息体”另一个说“期望的字段缺失”。那一刻我意识到我犯了一个很多开发者都会犯的错误想当然地认为“连上就能用”却忽略了连接之上那套看不见的“语言规则”——通信协议。“小鸿AI WS63 ↔ MCP Server WebSocket 通信协议”这个标题听起来很技术很底层。但它本质上解决的就是“怎么说话”的问题。WS63模型是一个“思考者”MCP Server是一个“翻译官兼接线员”WebSocket是它们之间的“电话线”。协议就是它们通话时必须遵守的语法、词汇和对话流程。如果语法错误、词汇不对、或者该你说话时你沉默了整个对话就会崩溃。所以这篇文章不是一份干巴巴的协议说明书。我会结合我实际踩坑、调试、最终让整个链路跑通的经历把这份“对话规则”掰开揉碎了讲清楚。你会看到WebSocket连接建立时握手阶段暗藏了哪些玄机数据以什么格式JSONMessagePack在线上飞每个字段到底什么意思MCP Server期望收到什么样的请求WS63模型又应该返回什么样的响应心跳、错误处理、连接保活这些“后勤保障”是怎么做的最关键的是当对话“哑火”时我们应该按照什么步骤去排查无论你是正在集成类似本地模型与MCP生态的开发者还是对AI应用后端通信细节感兴趣的技术人员理解这套协议都能让你避免我走过的弯路真正实现“可控”的AI能力调用。我们这就开始。2. 连接基石WebSocket握手、心跳与连接状态管理在讨论具体的数据包之前我们必须先把“电话线”搭稳。WebSocket连接并非一劳永逸它需要精心维护。2.1 握手不止于“Hello”WebSocket的握手始于一个HTTP Upgrade请求。对于小鸿AI WS63作为客户端主动连接MCP Server的场景这个请求看起来是这样的GET /mcp-endpoint HTTP/1.1 Host: your-mcp-server-host:port Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: mcp-v1这里有几个关键点是很多初级实现会忽略的Sec-WebSocket-Protocol子协议协商这个字段至关重要。我们在这里声明希望使用mcp-v1子协议。MCP Server在返回的握手响应中必须包含Sec-WebSocket-Protocol: mcp-v1来确认双方使用同一套“语言”的上层协议。如果缺失或不匹配即使连接建立后续通信也会因协议解析不一致而出错。踩坑点有些WebSocket库默认不添加或处理这个头需要显式配置。Sec-WebSocket-Key与验证这是一个Base64编码的随机字符串服务器会用固定的算法处理它并返回Sec-WebSocket-Accept头用于验证握手有效性。虽然库通常自动处理但在自定义或调试时需要确保算法正确。MCP Server的正确响应应该是HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbKxOo Sec-WebSocket-Protocol: mcp-v1看到101状态码和匹配的Sec-WebSocket-Protocol握手才算真正成功。2.2 心跳连接的“生命体征”WebSocket连接可能因为网络波动、代理超时、服务器重启等无数原因静默断开。心跳Ping/Pong机制就是定期检查连接是否存活的手段。谁发起心跳通常由客户端WS63定期向服务器MCP Server发送Ping帧。这是一个设计考量让更关心连接状态的主动方来负责保活。频率建议在25-30秒一次太频繁浪费资源太慢则可能因超时导致连接被中间设备切断。Pong是必须回复的服务器收到Ping后必须尽快回复一个Pong帧载荷内容通常与收到的Ping相同。重要经验很多WebSocket库的API会隐藏Ping/Pong的发送细节提供setKeepAliveInterval之类的方法。但务必查阅文档确认它是自动发送Ping还是需要你手动调用sendPing。我曾用一个库它默认不发送Ping导致连接在闲置一分钟后被Nginx代理无情掐断。超时与重连客户端发送Ping后应启动一个计时器例如10秒。如果超时未收到Pong则应认为连接已失效触发onClose事件并开始指数退避重连。重连逻辑必须健壮避免网络闪断时疯狂重连加重服务器负担。2.3 连接状态管理一个状态机的实践在你的代码中必须显式管理连接状态。一个简单的状态机可以避免很多竞态条件错误// 伪代码示例 class WS63Connector { constructor() { this.state DISCONNECTED; // DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING this.ws null; } async connect() { if (this.state CONNECTING || this.state CONNECTED) { return; } this.state CONNECTING; try { this.ws new WebSocket(ws://server-address, [mcp-v1]); this.ws.onopen this._handleOpen.bind(this); this.ws.onmessage this._handleMessage.bind(this); this.ws.onclose this._handleClose.bind(this); this.ws.onerror this._handleError.bind(this); } catch (err) { this.state DISCONNECTED; // 触发重连 } } _handleOpen() { this.state CONNECTED; this._startHeartbeat(); // 开始发送心跳 // 发送初始化消息或通知上层应用 } _handleClose(event) { this.state DISCONNECTED; this._stopHeartbeat(); // 根据event.code和reason判断是正常关闭还是异常决定是否重连 if (!this._isNormalClose(event)) { this._scheduleReconnect(); } } }核心技巧在_handleClose中检查event.code。1000正常关闭或1001端点离开通常意味着不需要重连。而1006异常关闭则大概率需要。重连时使用指数退避算法如1s, 2s, 4s, 8s...直到最大间隔并在成功连接后重置退避时间。3. 消息格式深潜JSON结构、字段语义与序列化连接稳定后真正的对话开始。所有通过WebSocket传输的应用层数据都必须遵循MCP协议定义的消息格式。目前主流是JSON因其可读性好、生态成熟。也有少数追求极致性能的场景用MessagePack但会增加复杂性。3.1 消息信封每一封信都需要地址每个消息都是一个JSON对象我们称之为“信封”。它包含两个核心部分元数据头部和载荷正文。一个最基础的消息结构如下{ jsonrpc: 2.0, id: req_123456, method: tools/call, params: { // ... 具体参数 } }jsonrpc: “2.0”声明使用JSON-RPC 2.0规范。这是MCP协议的底层传输框架。务必准确一些严格的服务器会校验这个字段。id: 请求标识符可以是字符串、数字。它的核心作用是匹配请求与响应。客户端发送一个带id的请求服务器返回的响应或错误必须包含完全相同的id。这是实现异步通信的关键。我建议使用UUID或“前缀时间戳随机数”来生成确保全局唯一。method: 方法名定义要执行的操作。MCP协议预定义了一系列方法如initialize: 连接初始化交换能力信息。tools/list: 列出服务器提供的所有工具。tools/call: 调用一个具体工具。notifications/...: 各种通知。params: 参数对象承载调用该方法所需的具体数据。其结构完全由method决定。3.2 核心方法参数解析以tools/call为例这是最核心的交互。当MCP Server收到一个工具调用请求并需要转发给WS63模型执行时params的结构决定了WS63模型能拿到什么信息。一个典型的、从MCP Server发往WS63的tools/call请求可能如下{ jsonrpc: 2.0, id: call_789, method: tools/call, params: { tool: web_search, arguments: { query: 2024年人工智能领域最新突破, max_results: 5 }, callId: call_789_internal, context: { sessionId: sess_abc, userId: user_123, conversationHistory: [ {role: user, content: 帮我找找AI的最新进展} ] } } }tool: 字符串指定要调用的工具名称。WS63需要知道它要模拟哪个工具的行为。arguments: 对象调用该工具所需的参数。这是工具定义的“输入接口”。WS63模型需要理解这些参数的含义并基于此生成“执行结果”。callId: 可选的内部调用ID用于在MCP Server内部跟踪此次调用WS63可以在响应中原样返回方便关联。context:这是黄金字段它提供了本次调用的上下文信息。sessionId和userId有助于WS63进行个性化或会话连贯性处理。conversationHistory尤其重要它可能包含了之前的对话轮次让WS63模型能理解用户的完整意图而不仅仅是当前这一个工具调用。很多模型效果不好就是因为缺少足够的上下文。3.3 WS63的响应格式如何“回信”WS63模型处理完“思考”后需要将结果封装成MCP Server能理解的响应格式通过WebSocket发回。成功响应{ jsonrpc: 2.0, id: call_789, // 必须与请求的id一致 result: { content: [ { type: text, text: 根据最新的学术新闻和论文2024年AI领域在...这里是WS63模型生成的自然语言总结 } ], isError: false, metadata: { model: WS63, thinking_time_ms: 1250, citations: [https://arxiv.org/abs/xxxx.xxxxx] } } }id: 严格对应请求ID这是匹配响应的唯一依据。result: 成功响应的结果。content: 一个数组通常包含一个或多个内容块。type: “text”是最常见的text字段就是模型生成的文本。未来可能支持image、audio等类型。isError: 明确指示这不是一个错误结果。metadata: 可选的元数据可以包含模型信息、处理耗时、引用来源等。这对于监控、调试和提升透明度非常有帮助。错误响应 如果WS63在处理过程中遇到问题如不理解参数、内部计算错误不应返回一个成功的result而应返回标准的JSON-RPC错误对象。{ jsonrpc: 2.0, id: call_789, error: { code: -32603, // JSON-RPC内部错误码也可用自定义码 message: Model failed to generate response due to context length limit., data: { max_allowed_tokens: 8192, requested_tokens: 9500 } } }error: 错误对象。code: 错误码。遵循JSON-RPC规范如-32600解析错误-32601方法未找到-32602参数无效-32603内部错误。message: 人类可读的错误描述。data: 可选提供额外的错误详情对于调试极其有用。一个关键陷阱WS63模型本身可能“胡言乱语”或产生不符合事实的内容但这在协议层面属于“成功”的result。业务逻辑上的“错误”和通信协议的“错误”是两回事。协议错误意味着通信或处理流程本身出了问题。4. 双向通信与通知不只是请求-响应MCP协议不仅仅是简单的“一问一答”。它支持服务器主动向客户端发送通知Notification这是实现实时交互、状态同步的关键。4.1 通知没有“回执”的消息通知是一种特殊的消息它没有id字段因为客户端不需要也不能对它进行直接响应。其method通常以notifications/开头。例如MCP Server可能会在工具执行开始和结束时发送通知// 开始执行通知 { jsonrpc: 2.0, method: notifications/tool_call_started, params: { callId: call_789_internal, toolName: web_search } } // 执行进度通知如果支持 { jsonrpc: 2.0, method: notifications/progress, params: { callId: call_789_internal, progress: 0.65, status: fetching_results } }对于WS63客户端来说收到这些通知后可以更新内部状态或UI如果有但不需要发送一个JSON-RPC响应回去。实现时要注意你的消息处理分支必须能区分带id的请求/响应和不带id的通知。4.2 初始化握手交换“名片”连接建立后第一件正式的事就是initialize握手。这通常是客户端WS63适配器主动发起的。客户端 - 服务器:{ jsonrpc: 2.0, id: init_1, method: initialize, params: { protocolVersion: 2024-11-05, // MCP协议版本 capabilities: { tools: { supportedHandlers: [call] // 声明客户端支持处理工具调用 }, roots: { /* ... */ } }, clientInfo: { name: ws63-mcp-adapter, version: 0.1.0 } } }服务器 - 客户端:{ jsonrpc: 2.0, id: init_1, result: { protocolVersion: 2024-11-05, capabilities: { tools: { listChanged: true // 服务器声明其工具列表可能会动态变化 } }, serverInfo: { name: mcp-server-demo, version: 1.0.0 } } }这个交换过程确定了双方都支持哪些协议特性。例如如果服务器声明了tools.listChanged那么当有新的工具被注册或旧工具被移除时它就会发送notifications/tools/list_changed通知客户端应该随后调用tools/list来刷新本地工具缓存。忽略能力协商可能会导致客户端无法感知到服务器的动态变化。5. 实战调试从“哑火”到“对话”的排查手册理论说再多不如一次实际的调试。让我们回到开头那个“哑火”的场景看看如何一步步定位和解决问题。5.1 第一步检查物理连接与握手工具浏览器开发者工具Network - WS或wscat命令行工具。现象WebSocket连接无法建立一直停留在CONNECTING状态或立即关闭。排查使用wscat -c ws://your-server尝试连接。如果失败检查服务器地址、端口、防火墙、Nginx/反向代理配置是否支持WebSocket升级。连接成功后观察握手请求和响应头。重点看Sec-WebSocket-Protocol头是否在请求和响应中都正确出现并匹配。我曾遇到Nginx配置漏了proxy_set_header Sec-WebSocket-Protocol $http_sec_websocket_protocol;导致子协议信息丢失握手虽成功但后续通信混乱。检查服务器日志看是否在Upgrade阶段有错误如路由错误、认证失败。5.2 第二步捕获与分析原始消息帧工具Wireshark需要解密TLS、专业的WebSocket调试客户端如Postman的WebSocket功能、websocat。现象连接已建立但发送消息后无响应或收到无法解析的错误。排查开启详细日志在你的WS63适配器和MCP Server中将进出WebSocket的原始字符串打印到日志文件。这是最直接的证据。对比消息格式将捕获到的发送消息与协议规范逐字段对比。常见问题JSON格式错误缺少逗号、引号不匹配、尾随逗号。使用JSON在线校验工具检查。字段名拼写错误jsonrpc写成jsonRpcmethod写成methood。字段类型错误id应该是字符串或数字但传了对象params应该是对象但传了数组或空值。缺少必需字段通知消息不小心带上了id或者响应消息漏了id。我的踩坑案例最初我在WS63的响应中把result直接设为了一个字符串。而MCP Server期望result是一个对象里面包含content等字段。于是服务器报错“Invalid params”。日志显示我发送的是{jsonrpc:2.0,id:...,result:这里是文本}而正确格式应是{jsonrpc:2.0,id:...,result:{content:[{type:text,text:这里是文本}]}}。一字之差天壤之别。5.3 第三步模拟与单元测试工具脚本Python/Node.js、单元测试框架。策略不要总是启动完整的WS63模型和MCP Server来测试。编写一个简单的“模拟客户端”和“模拟服务器”。模拟客户端模拟MCP Server向你的WS63适配器发送格式正确的请求验证其响应格式。模拟服务器模拟WS63适配器接收你的MCP Server发来的请求并返回预设的响应验证服务器能否正确处理。好处隔离了网络、模型加载等不稳定因素能快速聚焦于协议层的逻辑正确性。你可以构造各种边界用例如超长消息、缺失字段、错误数据等测试你的实现的健壮性。5.4 第四步时序与状态问题工具带时序的日志记录每个消息进出和内部状态变化的时间戳、流程图。现象偶尔出现响应错乱响应A匹配了请求B或连接莫名断开。排查检查id管理确保每个发出的请求都有一个唯一ID并且响应处理逻辑严格根据ID匹配。在异步环境下要防止ID重复或匹配逻辑错误。检查心跳与超时在闲置期是否按时收到了Pong网络延迟是否导致超时重连逻辑是否过于激进检查并发如果WS63模型处理速度慢而MCP Server又快速发送了多个请求你的适配器是否能妥善处理并发消息队列是否必要响应顺序是否必须保证6. 性能优化与进阶考量当基本通信跑通后我们会关注如何让它更高效、更可靠。6.1 消息压缩节省带宽对于频繁交互或返回内容很长的场景如模型生成大段文本可以考虑在WebSocket上启用Per-Message Deflate扩展RFC 7692。这能在传输前对每个消息进行压缩显著减少带宽占用。在建立连接时客户端和服务器可以通过Sec-WebSocket-Extensions头来协商是否启用压缩。大多数现代WebSocket库都支持此功能只需在配置中开启即可。但要注意压缩和解压会消耗少量CPU资源需要权衡。6.2 连接池与多路复用在高并发场景下单个WebSocket连接可能成为瓶颈。可以考虑连接池维护一个到同一MCP Server的WebSocket连接池。新的请求从池中获取一个空闲连接使用用完归还。这需要处理好连接的状态管理和请求的负载均衡。多路复用在单个连接上通过id字段区分并发的多个请求/响应流。JSON-RPC 2.0本身支持异步天然适合多路复用。关键在于客户端和服务器都要能正确处理并发消息的匹配确保不会张冠李戴。这通常比连接池更轻量但逻辑更复杂。6.3 安全性增强WSS (WebSocket Secure)在生产环境必须使用wss://即基于TLS加密的WebSocket防止消息被窃听或篡改。认证与授权可以在握手阶段的HTTP头中添加认证信息如Bearer Token。MCP Server在响应Upgrade请求前应先验证这些凭证。GET /mcp-endpoint HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: ... Sec-WebSocket-Protocol: mcp-v1 Authorization: Bearer your-secret-token-here消息签名对于极高安全要求的场景可以考虑对消息体进行签名确保端到端的完整性和不可否认性。但这会引入额外的计算开销和复杂度。6.4 与WS63模型的内部接口设计最后别忘了WS63适配器本身。它的一端是WebSocketMCP协议另一端是WS63模型的本地API可能是HTTP、gRPC或进程间通信。这里的设计也影响整体稳定性和性能。缓冲与流式传输如果WS63模型支持流式输出token-by-token你的适配器是等模型完全生成完再封装成一个大的result消息返回还是每生成一段就发送一个notifications/progress通知或分片的result后者用户体验更好但协议处理更复杂。错误转换将模型内部的错误如GPU内存不足、输入过长准确地映射到JSON-RPC的错误码和消息对于问题诊断至关重要。资源管理确保在WebSocket连接断开时能正确取消正在进行的模型推理任务释放资源。让“小鸿AI WS63”通过WebSocket与“MCP Server”流畅对话远不止是调用一个WebSocket库那么简单。它要求我们对从TCP握手到应用层协议、从消息格式到状态管理、从错误处理到性能优化的整个链条有清晰的认识。协议是约束也是保障。理解并正确实现这套“对话规则”是构建稳定、可扩展的AI能力桥接器的第一步。当你看到模型的想法通过这条精心维护的通道无缝转化为远端的工具调用结果时你会觉得这些繁琐的细节都是值得的。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻