Envoy MCP HTTP 过滤器新增 server/discover、subscriptions/listen 与 tasks/* 方法组:方法分组与 params.taskId 提取实现解析
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
本篇技术指南基于 Envoy 开源仓库中 changelogs/current/new_features/mcp__new_method_groups.rst 这一变更记录展开。该变更向 Envoy 的 MCP(Model Context Protocol)HTTP 过滤器新增了server/discover、subscriptions/listen与tasks/*三组方法的识别能力,并在 MCP JSON 解析器中支持从请求体中提取params.taskId字段。读完本文,你将掌握这些新增方法组的默认提取规则、内置分组(discovery/subscription/task)的实现位置,以及如何通过ParserConfig自定义分组元数据、如何借助Mcp-Name请求头免解析提取任务标识,从而把 MCP 流量中的方法属性接入动态元数据与路由决策。
变更内容总览
原始变更记录全文仅一句话,但背后对应着一整条实现链路:
Added support for
server/discover,subscriptions/listen, andtasks/*method groups, and support for extractingparams.taskIdin MCP JSON parser.
拆解为三部分能力:
- 方法识别:MCP JSON 解析器新增识别
server/discover(服务器发现)、subscriptions/listen(订阅监听)两个具体方法,以及tasks/get、tasks/update、tasks/cancel三个任务类方法(即tasks/*)。 - 方法分组:在 MCP 过滤器内置的方法分组(method group)分类中,新增
discovery、subscription、task三个组,用于将方法归类到动态元数据。 - 字段提取:为
tasks/*方法新增默认提取路径params.taskId,解析器会在流式解析 JSON-RPC 请求体时把任务 ID 写入元数据,并可映射到Mcp-Name请求头。
这些能力全部落在 MCP HTTP 过滤器(扩展名envoy.filters.http.mcp)内部,核心实现在 source/extensions/filters/http/mcp/mcp_json_parser.cc 与 source/extensions/filters/http/mcp/mcp_json_parser.h,方法常量定义在 source/extensions/filters/common/mcp/constants.h。
背景:MCP 过滤器如何处理 JSON-RPC 流量
MCP(Model Context Protocol)基于 JSON-RPC 2.0 通信。Envoy 的 MCP 过滤器在不解码完整请求的前提下,对流经的 HTTP 请求体做流式 JSON 解析(底层使用ProtobufUtil::converter::JsonStreamParser,见 mcp_json_parser.cc),从中提取jsonrpc、method、id等核心字段以及各方法特有的业务字段,并把结果写入请求动态元数据(dynamic metadata)或过滤器状态(filter state),供路由选择、日志、追踪等下游环节使用。
解析器将每个方法需要提取的字段建模为“提取规则”(AttributeExtractionRule),规则以点分隔的 JSON 路径表示,例如params.name、params.uri。方法名与提取规则的默认绑定关系在McpParserConfig::initializeDefaults()中集中注册(mcp_json_parser.cc),本次变更正是在这份默认注册表中扩展了新的方法条目。
新增方法组:discovery、subscription、task
方法常量与分组常量
方法名与分组名的常量均定义于 source/extensions/filters/common/mcp/constants.h:
- 方法常量(
Methods命名空间,constants.h):SERVER_DISCOVER = "server/discover"SUBSCRIPTIONS_LISTEN = "subscriptions/listen"TASKS_GET = "tasks/get"、TASKS_UPDATE = "tasks/update"、TASKS_CANCEL = "tasks/cancel"
- 分组常量(
MethodGroups命名空间,constants.h):DISCOVERY = "discovery"SUBSCRIPTION = "subscription"TASK = "task"
加上此前已有的lifecycle、tool、resource、prompt、notification、logging、sampling、completion、unknown,内置分组一共 12 个。
内置分组判定逻辑
方法到分组的映射由McpParserConfig::getBuiltInMethodGroup()实现(mcp_json_parser.cc),本次新增的三段逻辑非常直观:
// Discovery methods if (method == SERVER_DISCOVER) { return std::string(DISCOVERY); } // Subscription methods if (method == SUBSCRIPTIONS_LISTEN) { return std::string(SUBSCRIPTION); } // Task methods if (method == TASKS_GET || method == TASKS_UPDATE || method == TASKS_CANCEL) { return std::string(TASK); }方法判定顺序为:lifecycle → discovery → subscription → task → tool → resource → prompt → logging → sampling → completion → 以notifications/前缀匹配的通用通知 →unknown。notifications/*走前缀匹配(mcp_json_parser.cc),其余均为精确匹配。
分组如何进入动态元数据
分组值由 MCP 过滤器在解析完成后写入动态元数据。关键代码在 source/extensions/filters/http/mcp/mcp_filter.cc:
const ParserConfig& active_parser_config = parserConfig(); const std::string& group_metadata_key = active_parser_config.groupMetadataKey(); if (!group_metadata_key.empty()) { std::string method_group = active_parser_config.getMethodGroup(effective_method); (*metadata.mutable_fields())[group_metadata_key].set_string_value(method_group); }也就是说:只有在配置中显式设置了group_metadata_key时,分组才会写入动态元数据;该 key 默认为空,即默认不启用分组输出。用户可以在ParserConfig.methods[]中为具体方法覆盖分组名(MethodConfig.group),覆盖优先级高于内置分组(精确匹配优先,见 mcp_json_parser.cc)。proto 定义参见 api/envoy/extensions/filters/http/mcp/v3/mcp.proto。
新增提取路径:params.taskId
默认提取规则注册
tasks/*三个方法统一提取params.taskId,在initializeDefaults()中注册(mcp_json_parser.cc):
// Tasks. addMethodConfig(Methods::TASKS_GET, {AttributeExtractionRule(std::string(Paths::PARAMS_TASK_ID))}); addMethodConfig(Methods::TASKS_UPDATE, {AttributeExtractionRule(std::string(Paths::PARAMS_TASK_ID))}); addMethodConfig(Methods::TASKS_CANCEL, {AttributeExtractionRule(std::string(Paths::PARAMS_TASK_ID))});路径常量PARAMS_TASK_ID = "params.taskId"定义在 constants.h。因此一个形如下面的tasks/get请求,解析后动态元数据的params.taskId会被填充为task-123:
{ "jsonrpc": "2.0", "method": "tasks/get", "params": { "taskId": "task-123" }, "id": 123 }taskId 与 Mcp-Name 请求头的关系
McpParserConfig::getNameAttributePath()返回“方法对应的命名属性路径”,即该方法的标识符在 JSON 中的位置(mcp_json_parser.cc):
if (method == TASKS_GET || method == TASKS_UPDATE || method == TASKS_CANCEL) { return std::string(PARAMS_TASK_ID); }其他方法的对应关系为:tools/call、prompts/get→params.name;resources/read→params.uri;其余方法返回空。这个映射有两个用途:
- HEADERS 提取模式:当
Mcp.attribute_source配置为HEADERS时,过滤器直接信任Mcp-Method与Mcp-Name请求头,将头部值写入元数据,避免解析请求体(mcp_filter.cc)。此时tasks/get携带Mcp-Name: task-123即可免解析获得params.taskId = task-123。 - VERIFY 模式:以头部为准,并校验解析出的 body 值与头部一致(proto 注释见 mcp.proto)。
关于三个tasks/*方法与Mcp-Name头配合的行为,在 test/extensions/filters/http/mcp/mcp_filter_test.cc 等测试中有覆盖:请求头{"mcp-method": "tasks/get", "mcp-name": "task-123"}会产出元数据method="tasks/get"、params.taskId="task-123"。
提取规则同样适用于用户自定义覆盖
用户可在ParserConfig.methods[].extraction_rules[]中为任意方法(包括本批新增方法)自定义提取路径,规则以点分隔路径形式给出,例如params.taskId、params.name。方法级配置会合并到内置规则之上:方法特有提取规则全部视为必选字段,而params._meta始终作为可选字段追加(见buildMethodRequirements(),mcp_json_parser.cc)。
源码级实现要点:流式提取如何命中新路径
新增方法之所以“开箱即用”,得益于McpFieldExtractor的路径匹配机制:
- 解析器在收到顶层
method字段后即确定method_,并调用updateActiveTargetPaths()预计算当前方法的目标路径集合(mcp_json_parser.cc)。 - 每个 JSON 字段渲染时通过
isPathInteresting()判定是否值得暂存:支持“目标路径的祖先/前缀”“目标路径的后代”两类模糊匹配(mcp_json_parser.cc),因此即使解析器尚未读完整段 JSON,也能增量判断params.taskId是否属于目标。 - 根对象闭合后调用
finalizeExtraction():先复制jsonrpc、method、id等始终提取字段,再按方法复制专属字段,最后校验必选字段是否齐全(mcp_json_parser.cc)。缺失必选字段会被记录在missing_required_fields_中并输出 debug 日志(mcp_json_parser.cc)。
同时,McpFieldExtractor在解析过程中维护has_jsonrpc_、has_method_、has_result_、has_error_等状态来判定消息是否为合法 MCP 请求或 JSON-RPC 响应(mcp_json_parser.h),新增方法无需额外处理即可参与这些校验。
测试验证:新增方法的覆盖情况
仓库为本次变更提供了完整的单测覆盖,可直接作为行为规范参考:
- test/extensions/filters/http/mcp/mcp_json_parser_test.cc:
ServerDiscoverExtraction:验证server/discover被识别为合法 MCP 请求且getMethod()返回SERVER_DISCOVER。SubscriptionsListenExtraction:验证subscriptions/listen的识别。TasksGetExtraction/TasksUpdateExtraction/TasksCancelExtraction:分别验证tasks/get、tasks/update、tasks/cancel的识别,并断言parser_->getNestedValue("params.taskId")提取出task-123/task-456/task-789。- 另见该文件第 1572-1574 行:
getNameAttributePath("tasks/get"|"tasks/update"|"tasks/cancel")均断言返回params.taskId。
- test/extensions/filters/http/mcp/mcp_filter_test.cc:覆盖了设置
group_metadata_key后,内置分组(如tool)与用户自定义分组(custom_override_group、custom_tools)写入动态元数据的完整链路。
典型配置示例
将方法分组与任务 ID 接入动态元数据的最小配置(HTTP 过滤器级):
http_filters: - name: envoy.filters.http.mcp typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.mcp.v3.Mcp traffic_mode: PASS_THROUGH parser_config: group_metadata_key: method_group # 启用分组输出,写入动态元数据 methods: - method: tasks/get # 覆盖 tasks/get 的分组(可选) group: my_task_group extraction_rules: - path: params.taskId # 默认已内置,可自定义增补group_metadata_key:非空时,过滤器将方法分组名(如discovery、subscription、task)写入该 key;为空则禁用分组输出(默认)。methods[].group:为空时使用内置分类(getBuiltInMethodGroup),非空时精确覆盖。extraction_rules[].path:点分隔 JSON 路径,方法特有规则会被视为必选字段。
若希望免解析请求体、直接通过请求头获取方法名与任务 ID,可将attribute_source设为HEADERS,此时客户端需携带Mcp-Method: tasks/get与Mcp-Name: task-123(两者对应的 HTTP 头名见 constants.h)。注意头部提取只覆盖method与Mcp-Name所代表的命名属性,其他自定义提取规则仍需解析 body(mcp.proto)。
适用前提与限制
- 本文所述方法常量、分组名与默认提取规则均以当前仓库(
main分支开发状态)为准;proto 文件顶部标注了work_in_progress = true(mcp.proto),说明该过滤器仍在演进中,升级 Envoy 版本后应以对应版本的 API 文档为准。 server/discover与subscriptions/listen属于较新的 MCP 规范方法,本文不涉及具体版本规范内容,仅聚焦 Envoy 侧的分组与提取实现。- 分组值只有在配置了
group_metadata_key时才会进入动态元数据;未配置时,新增方法组仅影响解析器的内部字段需求计算,不产生可见元数据。
综上,mcp__new_method_groups.rst这条看似简短的变更记录,在 Envoy 源码中对应着从方法常量、内置分组、默认提取规则到动态元数据写入、请求头映射与单测覆盖的一整套实现,读者可沿着 constants.h、mcp_json_parser.cc、mcp_filter.cc 与对应测试文件逐层深入。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考