首页
/ Envoy MCP JSON REST Bridge:为 headers-only 上游响应合成 JSON-RPC 响应体的修复解析

Envoy MCP JSON REST Bridge:为 headers-only 上游响应合成 JSON-RPC 响应体的修复解析

2026-09-11 17:31:45作者:侯霆垣

本篇文章聚焦 Envoy mcp_json_rest_bridge 过滤器(HTTP Filter)的一个关键 Bug 修复:当 REST 上游返回无响应体的 headers-only 响应(如 HTTP 204 No Content)时,过滤器会主动合成合法的 JSON-RPC 响应,避免 MCP 客户端(MCP SDK)因等待响应体而超时或抛出异常。读完本文,你将理解该 Bug 的成因、过滤器在响应编码链路上的合成逻辑与状态码改写规则,以及对应的单元测试与端到端集成测试如何验证这一行为。

问题背景:headers-only 响应如何击穿 MCP 传输层

Model Context Protocol(MCP)基于 JSON-RPC 2.0 通信,客户端发出 tools/calltools/list 等请求后,会严格按照 JSON-RPC 响应结构(jsonrpcidresult/error 字段)解析返回值。而 mcp_json_rest_bridge 过滤器的作用,是把 MCP JSON-RPC 请求转码为普通 HTTP REST 请求转发给上游后端,再把上游的 JSON REST 响应映射回 JSON-RPC 格式。

问题出在:并非所有 REST 后端都会返回响应体。例如:

  • 删除资源、触发任务、更新状态等操作,后端常返回 HTTP 204 No Content
  • 部分网关或中间层也会对 GET 类操作返回空 body。

当这类 headers-only 响应(end_stream 为 true 且无任何数据帧)被原样透传给 MCP 客户端时,客户端拿不到 JSON-RPC 响应体,会一直等待导致超时,或直接因解析失败抛出异常。这正是本 changelog 记录的核心缺陷:

mcp_json_rest_bridge: Fixed a bug where headers-only upstream responses (e.g., HTTP 204 No Content) were passed through to MCP clients without a JSON-RPC response body, causing MCP SDK timeouts or exceptions.

修复方案是让过滤器在响应编码阶段主动合成一个合法的 JSON-RPC 响应:对 tools/call 请求合成空 ToolResult,对 tools/list 请求合成服务器错误。

修复实现:encodeHeaders 中的合成逻辑

该修复位于过滤器核心实现 mcp_json_rest_bridge_filter.ccMcpJsonRestBridgeFilter::encodeHeaders 方法中(约第 621-654 行)。该方法在响应头到达过滤器时被调用,end_stream 参数为 true 意味着上游已发送全部内容(即 headers-only,没有 body)。

对 tools/call 合成空 ToolResult

mcp_operation_ToolsCall 且响应为 headers-only 时,过滤器调用 translateJsonRestResponseToJsonRpc(同文件第 77-90 行)合成响应:

if (mcp_operation_ == McpOperation::ToolsCall) {
  synthetic = translateJsonRestResponseToJsonRpc("", *session_id_, is_error).dump();
}

其中 is_error = response_code >= 400(400 及以上视为错误)。空 ToolResult 的实际 JSON 结构如下:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [{"type": "text", "text": ""}],
    "isError": false
  }
}

translateJsonRestResponseToJsonRpc 的实现把上游 body 文本包装进 result.content[0].text,并携带 isError 标记(mcp_json_rest_bridge_filter.cc 第 77-90 行);此处传入空字符串,即表示“操作成功执行但没有返回内容”,符合 MCP 规范对工具调用结果的定义。

对 tools/list 合成服务器错误

mcp_operation_ToolsList 且响应为 headers-only 时,说明后端没能返回工具列表,过滤器直接合成一个 JSON-RPC 错误响应:

} else if (mcp_operation_ == McpOperation::ToolsList) {
  // headers-only means no tools list is available; return a server error.
  json ret = {
      {McpConstants::JSONRPC_FIELD, McpConstants::JSONRPC_VERSION},
      {McpConstants::ID_FIELD, *session_id_},
      {McpConstants::ERROR_FIELD, generateErrorJsonResponse(-32000, "Server error")},
  };
  synthetic = ret.dump();
}

generateErrorJsonResponse(同文件第 115-120 行)生成 {"code": -32000, "message": "Server error"},其中 -32000 是 MCP 规范定义的“服务器端错误”保留错误码。合成后的完整响应为:

{"jsonrpc":"2.0","id":9,"error":{"code":-32000,"message":"Server error"}}

统一的状态码改写与响应头处理

无论合成哪种响应,过滤器都会执行统一的收尾逻辑(第 637-653 行):

  • 状态码改写为 200:除 401 Unauthorized403 Forbidden 外,其余非 200 状态码一律改写为 200 OK。原因是 MCP 客户端在传输层对非 200 响应会直接失败,而 401/403 按 MCP 授权规范必须保留,以驱动 OAuth 握手与 step-up scope 流程(源码注释引用了 MCP 2025-11-25 规范的 authorization error handling 章节)。
  • 关于 400 的处理:即使 MCP 授权规范中也提到 HTTP 400,源码注释明确说明其假设——授权检查(如 OAuth token 校验)发生在本过滤器之前的过滤器链上,因此来自 REST 后端的 400 一律视为 API 错误,被转成标准 JSON-RPC 错误而非透传。
  • 设置响应头Content-Type 强制为 application/json,并写入合成 body 的精确 Content-Length,保证 MCP 客户端按长度读取完整 body。
  • 注入合成 body:通过 encoder_callbacks_->addEncodedData(body_buffer, false) 把合成的 JSON 字节注入编码数据流(第 651-652 行)。

测试验证:单元测试与集成测试的双重覆盖

该修复在仓库中有完整的测试支撑,分为两个层级。

过滤器单元测试

单元测试位于 mcp_json_rest_bridge_filter_test.cc,覆盖三类场景:

测试用例 上游响应 期望输出
ToolCallHeadersOnly204EmitsSyntheticSuccessResult(L2213) 204tools/call {"id":7,"jsonrpc":"2.0","result":{"content":[{"text":"","type":"text"}],"isError":false}},状态改写为 200
ToolCallHeadersOnly5xxEmitsSyntheticErrorResult(L2239) 503tools/call 同上但 isError:true,状态改写为 200
ToolsListHeadersOnly204EmitsSyntheticServerError(L2265) 204tools/list {"error":{"code":-32000,"message":"Server error"},"id":9,"jsonrpc":"2.0"},状态改写为 200

以第一个用例为例,测试先构造 tools/call 请求并断言 decodeHeaders 返回 StopIteration,随后以 :status 204 构造响应头并调用 encodeHeaders(response_headers_, true),最终断言:

  • 编码数据与期望的 JSON-RPC 结构完全一致(addEncodedData 被调用一次);
  • Content-Typeapplication/json
  • Content-Length 等于合成 body 的精确长度;
  • 响应状态码为 200

第三个用例(L2265)则验证 tools/list 场景不会透传 204 空响应,而是合成带 -32000 错误码的 JSON-RPC 错误对象。

端到端集成测试

集成测试位于 mcp_json_rest_bridge_integration_test.cc,基于 HttpIntegrationTest 启动真实 Envoy 与上游模拟服务器,验证从下游 MCP 客户端到上游 REST 后端再到客户端的完整链路:

  • ToolsCallHeadersOnly204SyntheticSuccessResult(L2146):配置工具 create_api_keyPOST /v1/{parent=projects/*}/keys,body 为 key),上游收到转码后的 POST /v1/projects/foo/keys 请求后,用 encodeHeaders(headers, true) 发送 204 headers-only 响应;下游最终收到 200 + application/json + 精确 Content-Length,body 为 {"jsonrpc":"2.0","id":7,"result":{"content":[{"type":"text","text":""}],"isError":false}}
  • ToolsCallHeadersOnly5xxSyntheticErrorResult(L2212):上游返回 503,合成结果 isError:true,状态码同样被改写为 200
  • ToolsListHeadersOnly204SyntheticServerError(L2342):配置 tool_list_http_rule: get: "/v1/tools",上游确认收到转码后的 GET /v1/tools 请求并返回 204,下游收到 {"jsonrpc":"2.0","id":9,"error":{"code":-32000,"message":"Server error"}}

集成测试同时验证了转码的正确性(路径、方法、Content-LengthContent-Type)与合成响应的完备性,确保该修复在真实 HTTP/2 连接上成立。

触发合成的完整链路与相关配置

理解修复位置后,再看整个过滤器的工作链路,可以更清楚合成逻辑发生在哪一步。过滤器定义在 mcp_json_rest_bridge.proto(扩展名 envoy.filters.http.mcp_json_rest_bridge,注册于 well_known_names.h),其核心流程为:

  1. decodeHeaders:仅接受 POST 方法(其他方法直接返回 405 Method Not Allowed),并根据 host/path 是否命中配置的 endpoint 决定是否进入 MCP 处理(mcp_json_rest_bridge_filter.cc 第 476-526 行);
  2. decodeData:缓冲并解析请求体 JSON,按方法分派到 handleMcpMethod——tools/call 通过 http_request_builder.cc 依据 HttpRule 构建 REST 请求(路径模板、query 参数、body 映射),tools/list 则按 tool_list_http_rule 转码为 GET 请求(第 865-992 行);
  3. encodeHeaders此处即本次修复的合成逻辑——非 streaming 模式下若 end_stream 为 true,说明上游没有 body,立即合成 JSON-RPC 响应(第 621-654 行);
  4. encodeData/encodeTrailers:对存在 body 的正常响应做 JSON-RPC 转码(encodeJsonRpcData,第 994-1104 行)。

此外需注意,合成逻辑只针对非流式(text_content_streaming_enabled 未开启)路径;流式模式下 encodeHeaders 走的是另一条分支,预构建 JSON-RPC 前缀/后缀并剥离 Content-Length(第 602-619 行),不在此次修复范围内。

运维建议与适用前提

  • 若你的 REST 后端存在“成功但无返回体”的接口(典型如 204 No Content201 Created 带空 body),升级到包含此修复的 Envoy 版本后,MCP 客户端将不再因等待 body 而超时,tools/call 会得到 isError:false 的空结果,tools/list 会得到明确的 -32000 服务器错误。
  • 若某工具的操作语义是“必须返回内容才算成功”,可结合 proto 配置中的 max_response_body_sizeHttpRule 映射以及过滤器动态元数据(request_storage_mode: DYNAMIC_METADATA,可记录 bridge_statusmethodparamsbackend_response_code 等字段,见 mcp_json_rest_bridge_filter.cc 第 1242-1296 行)来观测此类合成响应,判断后端行为是否需要调整。
  • 状态码改写规则(除 401/403 外一律 200)是 MCP 传输层的通用约束,运维时应确保授权类检查放在本过滤器之前的过滤器链中,以配合 400/401/403 语义的正确分流。
  • 若需自定义 tools/list 的返回行为(而非在 204 时返回服务器错误),可配置 tool_list_local 让过滤器本地生成工具列表,或配置 tool_list_http_rule 将请求转码给后端——详见 mcp_json_rest_bridge.protoServerToolConfig 的说明。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23