Skip to main content
最终版标准轨道

摘要

本 SEP 提议为可流式 HTTP 传输在标准 HTTP 头位置中暴露关键路由和上下文信息。通过将 JSON-RPC 载荷中的关键字段镜像到 HTTP 头中,负载均衡器、代理和可观测性工具等网络中介可以在无需深度包检测的情况下对 MCP 流量进行路由和处理,从而降低延迟和计算开销。

动机

当前通过 HTTP 实现的 MCP 将所有路由信息都隐藏在 JSON-RPC 载荷中。这给网络基础设施带来了摩擦:
  • 负载均衡器 必须终止 TLS 并解析整个 JSON 正文才能提取路由信息(例如区域、工具名称)
  • 代理和网关 无法在不进行深度包检测的情况下做出路由决策
  • 可观测性工具 对 MCP 流量模式的可见性有限
  • 限流器和 WAF 无法基于 MCP 特定字段应用策略
通过在 HTTP 头中暴露关键字段,我们使标准网络基础设施能够使用现有且成熟的机制来处理 MCP 流量。

规范

标准头

可流式 HTTP 传输将要求 POST 请求包含以下从请求体镜像而来的头: 这些头在引入它们的 MCP 版本中是必需的 服务器行为:处理请求体的服务器 MUST 拒绝头中指定的值与请求体中的值不匹配的请求。
理由:该要求可防止当网络中的不同组件依赖不同事实来源时可能出现的安全漏洞和错误条件。例如,负载均衡器或网关可能使用头值做路由决策,而 MCP 服务器使用正文值执行操作。该要求适用于任何处理消息正文的网络中介,以及 MCP 服务器本身。
实施说明:在验证整数参数值时,服务器 SHOULD 按数值而不是按字符串比较头值和正文值(例如,42.042 被视为相等)。
大小写敏感性:头名称(在 RFC 9110 中称为“字段名”)不区分大小写。客户端和服务器 MUST 对头名称使用不区分大小写的比较。

示例:tools/call 请求

示例:resources/read 请求

示例:prompts/get 请求

示例:其他请求方法

对于不涉及 tools、resources 或 prompts 的请求,只需要 Mcp-Method 头:

示例:通知

通知同样需要 Mcp-Method 头:

来自工具参数的自定义头

MCP 服务器 MAY 使用工具的 inputSchema 中参数 schema 里的 x-mcp-header 扩展属性,将特定工具参数指定为镜像到 HTTP 头中。 客户端要求:虽然 x-mcp-header 的使用对服务器而言是可选的,但客户端 MUST 支持此功能。当服务器的工具定义包含 x-mcp-header 注解时,符合规范的客户端 MUST 按照本文档所述,将指定参数值镜像到 HTTP 头中。

Schema 扩展

x-mcp-header 属性指定用于构造头名称 Mcp-Param-{name} 的名称部分。 x-mcp-header 值的约束
  • MUST NOT be empty
  • MUST match HTTP field-name token syntax (1*tchar, RFC 9110 Section 5.1)
  • MUST NOT contain control characters, including carriage return (CR, \r) or line feed (LF, \n)
  • MUST be case-insensitively unique among all x-mcp-header values in the inputSchema
  • MUST only be applied to parameters with primitive types (integer, string, boolean). Parameters with type number are not permitted. Integer values MUST be within the safe range for JavaScript (−2^53+1 to 2^53−1)
  • MAY be applied to properties at any nesting depth within the inputSchema, not only top-level properties
使用 Streamable HTTP 传输的客户端 MUST 拒绝任何 x-mcp-header 值违反这些约束的工具定义。拒绝意味着客户端 MUST 将无效工具从 tools/list 的结果中排除。客户端 SHOULD 在拒绝工具定义时记录警告,包括工具名称和拒绝原因。此行为可确保单个格式错误的工具定义不会阻止其他有效工具的使用。使用其他传输方式(例如 stdio)的客户端 MAY 完全忽略 x-mcp-header 注解。 工具定义示例

示例:地理分布式数据库

考虑一个暴露 execute_sql 工具的服务器,例如用于 Google Cloud Spanner,并且需要 region 参数。 工具定义
场景:客户端请求在 us-west1 中执行 SQL。 当前痛点:全局负载均衡器接收到请求,但必须终止 TLS 并解析整个 JSON 正文,才能在知道是否应将数据包路由到 Oregon 还是 Belgium 集群之前找到 "region": "us-west1" 采用本提案后:客户端检测到 x-mcp-header 注解,并自动将 Mcp-Param-Region: us-west1 头添加到 HTTP 请求中。负载均衡器现在可以基于该头进行路由,而无需解析正文。 请求

示例:多租户 SaaS 应用

某 SaaS 平台暴露在不同客户租户上运行的工具。通过将租户 ID 暴露在头中,平台可以将请求路由到租户专属基础设施。 工具定义
请求

示例:基于优先级的请求处理

服务器可以暴露一个优先级参数,以便基础设施对某些请求进行优先处理。 工具定义
请求

头处理

值编码

客户端 MUST 在将参数值包含到 HTTP 头中之前对其进行编码,以确保安全传输并防止注入攻击。 字符限制 根据 RFC 9110,HTTP 头字段值必须由可见 ASCII 字符(0x21-0x7E)、空格(0x20)和水平制表符(0x09)组成。以下字符被明确禁止:
  • 回车符(\r,0x0D)
  • 换行符(\n,0x0A)
  • 空字符(\0,0x00)
  • ASCII 范围之外的任何字符(> 0x7F)
空白处理 HTTP 解析器通常会裁剪头值前后的空白。为了保留参数值中的前导和尾随空格,客户端 MUST 在值满足以下情况时使用 Base64 编码:
  • 以空格(0x20)或水平制表符(0x09)开头
  • 以空格(0x20)或水平制表符(0x09)结尾
编码规则 客户端 MUST 按以下顺序应用编码规则:
  1. 类型转换:将参数值转换为其字符串表示:
    • string:按原样使用该值
    • integer:转换为十进制字符串表示(例如,42-7
    • boolean:转换为小写的 "true""false"
  2. 空白检查:如果字符串以空白字符(空格或制表符)开头或结尾:
    • 应用 Base64 编码(见下文)
  3. ASCII 校验:检查字符串是否仅包含有效 ASCII 字符(0x20-0x7E):
    • 如果有效,继续执行第 4 步
    • 如果无效(包含非 ASCII 字符),应用 Base64 编码(见下文)
  4. 控制字符检查:如果字符串包含任何控制字符(0x00-0x1F 或 0x7F):
    • 应用 Base64 编码(见下文)
对不安全值进行 Base64 编码 当值无法安全地表示为普通 ASCII 头值时,客户端 MUST 使用该值的 UTF-8 表示进行 Base64 编码,格式如下:
前缀 =?base64? 和后缀 ?= 表示该值已进行 Base64 编码。这些标记区分大小写,并且 MUST 按照所示形式精确出现(小写)。需要检查这些值的服务器和中介 MUST 相应地对其解码。 为避免歧义,客户端 MUST 还对任何匹配哨兵模式的纯 ASCII 值进行 Base64 编码(即以 =?base64? 开头并以 ?= 结尾)。 示例

客户端行为

当通过 HTTP 传输构造 tools/call 请求时,客户端 MUST:
  1. 从请求体中提取任何标准头的值(例如,methodparams.nameparams.uri
  2. Mcp-Method 头以及(如适用)Mcp-Name 头追加到请求中
  3. 检查工具的 inputSchema 中标记有 x-mcp-header 的属性,并提取每个参数的值
  4. 按照 值编码 中的规则对这些值进行编码
  5. Mcp-Param-{Name}: {Value} 头追加到请求中:
实现说明:如果客户端没有工具的 inputSchema(例如尚未调用 tools/list)或者缓存的 schema 已过期(例如 TTL 已到期),客户端 SHOULD 在不带自定义 Mcp-Param-* 头的情况下发送请求。如果服务器因为缺少必需的自定义头而拒绝该请求,客户端 SHOULD 调用 tools/list 以获取当前的 inputSchema,然后使用适当的头重试原始请求。客户端 MAY 通过其他方式预加载工具定义(例如来自先前会话或配置),以便在未先调用 tools/list 的情况下也能发送头。

服务器行为

在接收请求时,服务器 MUST 拒绝包含无效字符的 Mcp-Param-{Name} 头(见 值编码 部分中的“字符限制”)。 任何处理消息正文(而不仅仅是转发它)的服务器 MUST 验证编码后的头值在 Base64 解码(如适用)后与请求体中的对应值匹配。若任何验证失败,服务器 MUST 以 400 Bad Request HTTP 状态拒绝请求。 错误代码 当由于头验证失败而拒绝请求时,服务器 MUST 返回具有以下错误代码的 JSON-RPC 错误响应: 此错误代码属于 JSON-RPC 由实现定义的服务器错误范围(-32000-32099)。 错误响应格式
验证失败条件
  • 缺少必需的标准头(Mcp-MethodMcp-Name 等)
  • 头值与请求体值不匹配
  • Base64 编码的值无法解码
  • 头值包含无效字符
注意:中介 MUST 在验证失败时返回适当的 HTTP 错误状态(例如 400 Bad Request),但不要求返回 JSON-RPC 错误响应。
注意:基于镜像头执行策略的中介(例如按租户进行路由或限流)SHOULD 验证 MCP-Protocol-Version 头是否指示了一个需要头-正文验证的版本。如果版本较旧或该头缺失,中介 SHOULD 拒绝该请求,而不是信任未验证的头值。
自定义头处理 自定义头(通过 x-mcp-header 定义的头)遵循与标准头相同的验证规则: 当由于缺少或无效的自定义头而拒绝请求时,服务器 MUST 返回 HTTP 状态 400 Bad Request,并带有 JSON-RPC 错误代码 -32001HeaderMismatch)。

理由

请求头与路径

本提案将请求数据镜像到请求头中,而不是将其编码到 URL 路径中。 请求头的优势
  1. 简单性:所有广泛使用的网络负载均衡器都支持基于 HTTP 请求头的路由
  2. 多版本支持:在客户端和服务器中更容易支持多个 MCP 版本
  3. 兼容性:请求头可与现有的 Streamable HTTP 传输设计配合使用,而无需更改端点结构
  4. 值无限制:请求头值可以包含在 URL 中需要编码的字符(例如 /?#
  5. 无 URL 长度限制:很长的值也可以传输,而不会触及 URL 长度限制
基于路径路由的优势
  1. 框架简单性:许多 Web 框架(Flask、Express、Django、Rails)都内置支持基于路径的路由,配置极少
  2. 日志记录:URL 路径通常默认会被记录,便于调试
权衡与框架考量 对于像 Flask 这样强烈偏好基于路径路由的框架,实现基于请求头的路由需要额外代码:
尽管某些框架中会增加额外复杂性,但最终仍选择基于请求头的路由,原因如下:
  1. 向后兼容:引入基于路径的路由将要求所有现有 MCP 服务器进行重大更新,并且可能需要同时支持两套端点来支持多个版本。即使 SDK 可以在一定程度上掩盖这些问题,测试、指标等额外运维关注点仍然需要处理。基于请求头的路由只需极少的客户端改动。未选择加入的客户端也仍可正常工作。
  2. 基础设施收益大于框架复杂度:主要目标是让网络基础设施(负载均衡器、代理、WAF)无需解析正文就能路由和处理请求。这一收益与服务器所使用的框架无关。

基础设施支持

基于 HTTP 请求头的路由和处理已被以下组件支持:
  • 负载均衡器:所有主流负载均衡器(HAProxy、NGINX、Cloudflare、F5、Envoy/Istio)
  • 限流:11 个流行限流方案中的 9 个
  • 授权:Kong、Tyk、AWS API Gateway、Google Cloud Apigee、Azure API Gateway、NGINX、Apache APISIX、Istio/Envoy
  • Web 应用防火墙:Cloudflare WAF、AWS WAF、Azure WAF、F5 Advanced WAF、FortiWeb、Imperva WAF、Barracuda WAF、ModSecurity、Akamai、Wallarm
  • 可观测性:大多数可观测性方案都可以从 HTTP 请求头中提取数据

x-mcp-header 中显式的请求头名称

该设计在 x-mcp-header 中使用显式名称值,而不是从参数名推导请求头名称,原因如下:
  1. 大小写敏感性不匹配:请求头名称不区分大小写,但 JSON Schema 属性名区分大小写
  2. 字符集限制:请求头名称仅限 ASCII 字符,而工具参数名可能包含任意 Unicode
  3. 简单性:无需用复杂方案从嵌套属性构造请求头名称

在 JSON Schema 中的位置

x-mcp-header 扩展直接放置在要镜像的属性的 JSON Schema 内部,而不是放在 schema 外部的单独元数据字段中。该设计选择有几个优点:
  1. 共置性:请求头映射与其影响的属性定义在一起,使其立即清楚哪个参数会被镜像。开发者无需在 schema 与单独的元数据结构之间来回查找。
  2. 成熟模式:JSON Schema 明确支持扩展关键字(以 x- 开头的属性),这一模式在 OpenAPI 等生态中被广泛使用。工具作者和 SDK 开发者对此方式已经很熟悉。
  3. Schema 可组合性:当 schema 被组合、扩展或通过 $ref 引用时,x-mcp-header 注释会随属性定义一起传递。单独的元数据结构则需要复杂的同步逻辑来保持一致性。
  4. 工具兼容性:现有 JSON Schema 校验器默认会忽略未知关键字,因此添加 x-mcp-header 不会破坏现有 schema 校验。不理解此扩展的工具会直接跳过它。
  5. 降低复杂度:单独的元数据结构需要定义一种映射机制(例如 JSON Pointer 或属性路径)来将请求头与属性关联起来,这会增加实现复杂度并带来出错风险。

范围:仅适用于工具

x-mcp-header 机制目前仅适用于 tools/call 请求,因为工具是唯一带有 inputSchema 且支持 JSON Schema 扩展关键字的 MCP 原语。资源和提示没有等价的 schema 结构:resources/read 只接收一个 uri(已通过 Mcp-Name 暴露),而 prompts/get 将参数定义为一个简单的 {name, description, required} 数组,不具备 JSON Schema 可扩展性。若要将自定义请求头映射泛化到这些原语,需要为资源和提示增加类似 inputSchema 的定义,这属于更大的规范变更。这被记录为未来可能的扩展。

不定义规范层面的请求头大小限制

本规范有意不定义单个请求头值长度、MCP 请求头总大小或自定义请求头数量的限制。请求头纯粹是 HTTP 概念,而 HTTP 本身(RFC 9110)并未规定请求头大小限制。常见 HTTP 基础设施会施加自己的限制——从某些服务器的 4–8 KB(例如 Apache 约 8190 字节)到其他系统的 128 KB(例如 Cloudflare)不等——但合适的限制取决于部署环境,而只有服务运营者才能确定这一点。 定义规范层面的限制(例如“省略超过 8192 字节的请求头”)会带来问题:
  1. 任意阈值:无论选择什么数值,对某些部署来说都太低,对另一些则无关紧要。合适的“正确”限制因基础设施而异。
  2. 适得其反的省略:如果客户端因为某个请求头超过规范定义的限制而省略它,那么依赖该请求头进行路由的服务器和中间件就必须解析正文或拒绝请求——这会削弱将值暴露在请求头中的核心目的。
  3. 不必要的 SDK 负担:SDK 维护者需要为一个在实践中很少适用的约束实现并测试限制检查逻辑。
  4. 与 HTTP 重复:服务器和中间件已经会使用标准 HTTP 状态码拒绝过大的请求头(413 Request Entity Too Large431 Request Header Fields Too Large),客户端无论如何都必须处理这些响应。
给实现者的注记:服务器、中间件和客户端可以根据各自的部署环境独立对单个请求头大小、MCP 请求头总大小或自定义请求头数量施加限制。服务器应记录其施加的任何限制。客户端应优雅地处理 413 Request Entity Too Large431 Request Header Fields Too Large 响应。工具作者应将 x-mcp-header 注释限制在那些能带来明确基础设施收益的参数上。

不安全值的编码方式

对于无法安全表示为普通 ASCII 请求头值的参数值(非 ASCII 字符、前导/尾随空白字符、控制字符),我们考虑了四种编码方式:
  1. Sentinel 包裹(所选方案):在同一个 Mcp-Param-{Name} 请求头内使用 =?base64?{value}?= 前缀/后缀来标识 Base64 编码值。
  2. 单独的请求头名称:为编码值使用不同的请求头名称,例如 Mcp-ParamEncoded-{Name},通过请求头名称而不是值格式来表示编码。
  3. 隐式编码:让解析器根据工具 schema 推断编码,例如在工具定义中使用 "x-mcp-header-encoding": "base64" 注释。
  4. 始终编码:对每个 Mcp-Param-{Name} 值都无条件进行 Base64 编码。
结论:Sentinel 包裹方案提供了最佳权衡。自定义请求头的主要用途是让中间件能够对地区名称、租户 ID 之类简单、可读的值进行路由和过滤——这些值几乎总是纯 ASCII,且永远不会触发 Base64 编码。方案 4 使所有值对中间件都变得不可见。方案 3 在没有工具 schema 访问权限的情况下,使中间件无法区分编码值与字面值。方案 2 消除了带内歧义,但会使请求头命名空间翻倍,要求中间件为每个参数检查两种可能的请求头名称,并在两者同时存在时增加冲突规则。方案 1 中 sentinel 的理论碰撞风险可以忽略不计,因为 =?base64?...?= 在实践中成为字面参数值的可能性极低。

向后兼容性

标准头

在使用新的 MCP 版本时,现有客户端和 SDK 将被要求包含标准头。这是一个小幅度的新增,因为客户端已经包含了诸如 Mcp-Protocol-Version 之类的头,因此每条消息只需额外增加一到两个新头。 实现新版本的服务器 MUST 拒绝缺少必需头的请求。服务器 MAY 在协商较旧协议版本时,通过接受不带头的请求来支持旧客户端。

来自工具参数的自定义头

x-mcp-header 扩展对服务器而言是可选的。没有此属性的现有工具将继续按原样工作。不过,实现了包含本规范的 MCP 版本的客户端 MUST 支持该特性。不支持 x-mcp-header 的旧客户端仍可正常工作,但无法提供服务器可能依赖的基于头的路由优势。

安全影响

头注入

当包含控制字符(尤其是 \r\n)的恶意值被包含在头中时,就会发生头注入攻击,这可能允许攻击者注入额外的头或提前终止头部区域。 客户端 MUST 遵循本规范中定义的 值编码 规则。这些规则确保:
  • 控制字符绝不会包含在头值中
  • 非 ASCII 值会使用 Base64 安全编码
  • 超过安全长度限制的值会被省略

头伪造

服务器 MUST 验证头值与请求体中的对应值一致。这可防止客户端发送不匹配的头,以在执行不同操作时操纵路由。 例如,恶意客户端可能会尝试:
  • 在执行面向高安全区域的操作时,将请求路由到安全性较低的区域
  • 通过伪造租户标识来绕过速率限制
  • 通过虚报正在执行的操作来规避安全策略

信息泄露

为头指定的工具参数值将对网络中间设备(负载均衡器、代理、日志系统)可见。服务器开发者:
  • SHOULD NOT 将敏感参数(密码、API 密钥、令牌、PII)标记为 x-mcp-header
  • SHOULD 记录哪些参数会作为头暴露
  • SHOULD 考虑到 Base64 编码不提供机密性——它只是编码,不是加密

信任头值

头值来源于工具调用参数,这些参数可能受到 LLM 或恶意客户端的影响。中间设备和服务器 MUST NOT 将这些值视为用于安全敏感决策的可信输入。尤其是:
  • 暗示可访问特定资源的头值(例如租户 ID、区域名称)在授予对这些资源的访问权限之前,MUST 先由身份验证用户的权限进行独立验证。
  • 头值 MUST NOT 作为授予提升权限的唯一依据;必须由服务器端强制执行速率限制和配额。
  • 部署 SHOULD 尽早在流水线中拒绝带有过大或过多头的请求——在执行 Base64 解码或正文解析之前——以缓解精心构造的载荷带来的拒绝服务风险。

一致性测试用例

本节定义了一致性测试 MUST 覆盖的边界情况,以确保实现之间的互操作性。

标准头边界情况

大小写敏感性

头/正文不匹配

值中的特殊字符

自定义头边界情况

x-mcp-header 名称冲突

无效的 x-mcp-header 值

值编码边界情况

类型限制违规

服务器验证边界情况

Base64 解码

空值与缺失值

当正文中存在值但缺少自定义头时

参考实现

将在此 SEP 达到最终状态之前提供。 实现要求:
  • 服务器 SDK:提供一种机制(属性/装饰器)来标记带有 x-mcp-header 的参数
  • 客户端 SDK:实现提取和编码头部值的客户端行为
  • 验证:双方都必须验证头部/正文一致性。