Documentation Index
Fetch the complete documentation index at: https://mcp.zhcndoc.com/llms.txt
Use this file to discover all available pages before exploring further.
最终版标准轨道
| Field | Value |
|---|
| SEP | 2243 |
| Title | 用于可流式 HTTP 传输的 HTTP 头标准化 |
| Status | 最终版 |
| Type | 标准轨道 |
| Created | 2026-02-04 |
| Author(s) | MCP Transports 工作组 |
| Sponsor | 无 |
| PR | #2243 |
本 SEP 提议为可流式 HTTP 传输在标准 HTTP 头位置中暴露关键路由和上下文信息。通过将 JSON-RPC 载荷中的关键字段镜像到 HTTP 头中,负载均衡器、代理和可观测性工具等网络中介可以在无需深度包检测的情况下对 MCP 流量进行路由和处理,从而降低延迟和计算开销。
当前通过 HTTP 实现的 MCP 将所有路由信息都隐藏在 JSON-RPC 载荷中。这给网络基础设施带来了摩擦:
- 负载均衡器 必须终止 TLS 并解析整个 JSON 正文才能提取路由信息(例如区域、工具名称)
- 代理和网关 无法在不进行深度包检测的情况下做出路由决策
- 可观测性工具 对 MCP 流量模式的可见性有限
- 限流器和 WAF 无法基于 MCP 特定字段应用策略
通过在 HTTP 头中暴露关键字段,我们使标准网络基础设施能够使用现有且成熟的机制来处理 MCP 流量。
标准头
可流式 HTTP 传输将要求 POST 请求包含以下从请求体镜像而来的头:
| Header Name | Source Field | Required For |
|---|
Mcp-Method | method | 所有请求和通知 |
Mcp-Name | params.name or params.uri | tools/call、resources/read、prompts/get 请求 |
这些头在引入它们的 MCP 版本中是必需的。
服务器行为:处理请求体的服务器 MUST 拒绝头中指定的值与请求体中的值不匹配的请求。
理由:该要求可防止当网络中的不同组件依赖不同事实来源时可能出现的安全漏洞和错误条件。例如,负载均衡器或网关可能使用头值做路由决策,而 MCP 服务器使用正文值执行操作。此要求适用于任何处理消息正文的网络中介,以及 MCP 服务器本身。
大小写敏感性:头名称(在 RFC 9110 中称为“field names”)不区分大小写。客户端和服务器 MUST 对头名称使用不区分大小写的比较。
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "Seattle, WA"
}
}
}
示例:resources/read 请求
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: resources/read
Mcp-Name: file:///projects/myapp/config.json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///projects/myapp/config.json"
}
}
示例:prompts/get 请求
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: prompts/get
Mcp-Name: code_review
{
"jsonrpc": "2.0",
"id": 3,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"language": "python"
}
}
}
示例:其他请求方法
对于不涉及 tools、resources 或 prompts 的请求,只需要 Mcp-Method 头:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Method: initialize
{
"jsonrpc": "2.0",
"id": 4,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
示例:通知
通知同样需要 Mcp-Method 头:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
来自工具参数的自定义头
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 为空
- MUST 仅包含 ASCII 字符(不含空格和
:)
- 在
inputSchema 中所有 x-mcp-header 值之间 MUST 以不区分大小写的方式唯一
- MUST 仅适用于原始类型参数(number、string、boolean)
客户端 MUST 拒绝任何 x-mcp-header 值违反这些约束的工具定义。拒绝意味着客户端 MUST 将无效工具从 tools/list 的结果中排除。客户端 SHOULD 在拒绝工具定义时记录警告,包括工具名称和拒绝原因。此行为可确保单个格式错误的工具定义不会阻止其他有效工具被使用。
工具定义示例:
{
"name": "execute_sql",
"description": "在 Google Cloud Spanner 上执行 SQL",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "执行查询的区域",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "要执行的 SQL 查询"
}
},
"required": ["region", "query"]
}
}
示例:地理分布式数据库
考虑一个暴露 execute_sql 工具的服务器,例如用于 Google Cloud Spanner,并且需要 region 参数。
工具定义:
{
"name": "execute_sql",
"description": "在 Google Cloud Spanner 上执行 SQL",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "执行查询的区域",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "要执行的 SQL 查询"
}
},
"required": ["region", "query"]
}
}
场景:客户端请求在 us-west1 中执行 SQL。
当前痛点:全局负载均衡器接收到请求,但必须终止 TLS 并解析整个 JSON 正文,才能在知道是否应将数据包路由到 Oregon 还是 Belgium 集群之前找到 "region": "us-west1"。
采用本提案后:客户端检测到 x-mcp-header 注解,并自动将 Mcp-Param-Region: us-west1 头添加到 HTTP 请求中。负载均衡器现在可以基于该头进行路由,而无需解析正文。
请求:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_sql",
"arguments": {
"region": "us-west1",
"query": "SELECT * FROM users"
}
}
}
示例:多租户 SaaS 应用
某 SaaS 平台暴露在不同客户租户上运行的工具。通过将租户 ID 暴露在头中,平台可以将请求路由到租户专属基础设施。
工具定义:
{
"name": "query_analytics",
"description": "查询某个租户的分析数据",
"inputSchema": {
"type": "object",
"properties": {
"tenant_id": {
"type": "string",
"description": "租户标识符",
"x-mcp-header": "TenantId"
},
"metric": {
"type": "string",
"description": "要查询的指标"
},
"start_date": {
"type": "string",
"description": "查询范围的开始日期"
},
"end_date": {
"type": "string",
"description": "查询范围的结束日期"
}
},
"required": ["tenant_id", "metric", "start_date", "end_date"]
}
}
请求:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: query_analytics
Mcp-Param-TenantId: acme-corp
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "query_analytics",
"arguments": {
"tenant_id": "acme-corp",
"metric": "page_views",
"start_date": "2026-01-01",
"end_date": "2026-01-31"
}
}
}
示例:基于优先级的请求处理
服务器可以暴露一个优先级参数,以便基础设施对某些请求进行优先处理。
工具定义:
{
"name": "generate_report",
"description": "生成复杂报告",
"inputSchema": {
"type": "object",
"properties": {
"report_type": {
"type": "string",
"description": "要生成的报告类型"
},
"priority": {
"type": "string",
"description": "请求优先级:low、normal 或 high",
"x-mcp-header": "Priority"
}
},
"required": ["report_type"]
}
}
请求:
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: generate_report
Mcp-Param-Priority: high
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "generate_report",
"arguments": {
"report_type": "quarterly_summary",
"priority": "high"
}
}
}
头处理
值编码
客户端 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 按以下顺序应用编码规则:
-
类型转换:将参数值转换为其字符串表示:
string:按原样使用该值
number:转换为十进制字符串表示(例如,42、3.14)
boolean:转换为小写 "true" 或 "false"
-
空白检查:如果字符串以空白字符(空格或制表符)开头或结尾:
-
ASCII 校验:检查字符串是否仅包含有效 ASCII 字符(0x20-0x7E):
- 如果有效,继续执行第 4 步
- 如果无效(包含非 ASCII 字符),应用 Base64 编码(见下文)
-
控制字符检查:如果字符串包含任何控制字符(0x00-0x1F 或 0x7F):
对不安全值进行 Base64 编码
当值无法安全地表示为普通 ASCII 头值时,客户端 MUST 使用该值的 UTF-8 表示进行 Base64 编码,格式如下:
Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=
前缀 =?base64? 和后缀 ?= 表示该值已进行 Base64 编码。需要检查这些值的服务器和中介 MUST 相应地解码它们。
示例:
| Original Value | Reason | Encoded Header Value |
|---|
"us-west1" | 纯 ASCII | Mcp-Param-Region: us-west1 |
"Hello, 世界" | 包含非 ASCII | Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
" padded " | 前导/尾随空格 | Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | 包含换行符 | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
客户端行为
当通过 HTTP 传输构造 tools/call 请求时,客户端 MUST:
- 从请求体中提取任何标准头的值(例如,
method、params.name、params.uri)
- 将
Mcp-Method 头以及(如适用)Mcp-Name 头追加到请求中
- 检查工具的
inputSchema 中标记有 x-mcp-header 的属性,并提取每个参数的值
- 按照 值编码 中的规则对这些值进行编码
- 将
Mcp-Param-{Name}: {Value} 头追加到请求中:
服务器行为
在接收请求时,服务器 MUST 拒绝包含无效字符的 Mcp-Param-{Name} 头(见 值编码 部分中的“字符限制”)。
任何处理消息正文(而不仅仅是转发它)的服务器 MUST 验证编码后的头值在 Base64 解码(如适用)后与请求体中的对应值匹配。若任何验证失败,服务器 MUST 以 400 Bad Request HTTP 状态拒绝请求。
错误代码
当由于头验证失败而拒绝请求时,服务器 MUST 返回具有以下错误代码的 JSON-RPC 错误响应:
| Code | Name | Description |
|---|
-32001 | HeaderMismatch | HTTP 头与请求体中的对应值不匹配,或所需头缺失/格式错误。 |
此错误代码属于 JSON-RPC 由实现定义的服务器错误范围(-32000 到 -32099)。
错误响应格式:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32001,
"message": "Header mismatch: Mcp-Name 头值 'foo' 与正文值 'bar' 不匹配"
}
}
验证失败条件:
- 缺少必需的标准头(
Mcp-Method、Mcp-Name 等)
- 头值与请求体值不匹配
- Base64 编码的值无法解码
- 头值包含无效字符
注意:中介 MUST 在验证失败时返回适当的 HTTP 错误状态(例如 400 Bad Request),但不要求返回 JSON-RPC 错误响应。
自定义头处理:
自定义头(通过 x-mcp-header 定义的头)遵循与标准头相同的验证规则:
| Scenario | Client Behavior | Server Behavior |
|---|
| 提供了参数值 | 客户端 MUST 包含该头 | 服务器 MUST 验证头与正文匹配 |
参数值为 null | 客户端 MUST 省略该头 | 服务器 MUST NOT 期待该头 |
| 参数不在 arguments 中 | 客户端 MUST 省略该头 | 服务器 MUST NOT 期待该头 |
| 客户端省略了头但正文中存在值 | 不符合规范的客户端 | 服务器 MUST 拒绝该请求 |
当由于缺少或无效的自定义头而拒绝请求时,服务器 MUST 返回 HTTP 状态 400 Bad Request,并带有 JSON-RPC 错误代码 -32001(HeaderMismatch)。
本提案将请求数据镜像到 header 中,而不是将其编码到 URL path 中。
Header 的优势:
- 简单性:所有广泛使用的网络负载均衡器都支持基于 HTTP header 的路由
- 多版本支持:在客户端和服务器中更容易支持多个 MCP 版本
- 兼容性:Header 可与现有的 Streamable HTTP 传输设计配合使用,而无需更改 endpoint 结构
- 值无限制:Header 值可以包含在 URL 中需要编码的字符(例如
/、?、#)
- 无 URL 长度限制:很长的值也可以传输,而不会触及 URL 长度限制
基于 Path 路由的优势:
- 框架简单性:许多 Web 框架(Flask、Express、Django、Rails)都内置支持基于 path 的路由,配置极少
- 日志记录:URL path 通常默认会被记录,便于调试
权衡与框架考量:
| Framework | 基于 Header 的路由 | 基于 Path 的路由 |
|---|
| Flask (Python) | 需要中间件或装饰器在路由前提取 header | 原生支持 @app.route('/mcp/<method>') |
| Express (Node.js) | 可通过 req.headers 轻松实现,但需要自定义路由逻辑 | 原生支持 app.post('/mcp/:method') |
| Django (Python) | 需要自定义中间件 | 原生 URL 模式 |
| Go (net/http) | 可通过 r.Header.Get() 轻松实现 | 可通过 path 模式原生支持 |
| ASP.NET Core | 可通过 [FromHeader] 属性轻松实现 | 可通过路由模板原生支持 |
对于像 Flask 这样强烈偏好基于 path 路由的框架,实现基于 header 的路由需要额外代码:
# Flask 示例:基于 Header 的路由需要手动分发
@app.route('/mcp', methods=['POST'])
def mcp_handler():
method = request.headers.get('Mcp-Method')
if method == 'tools/call':
return handle_tools_call(request)
elif method == 'resources/read':
return handle_resources_read(request)
# ... 等等
尽管某些框架中会增加额外复杂性,但最终仍选择基于 header 的路由,原因如下:
-
向后兼容 引入基于 path 的路由将要求所有现有 MCP Server 进行重大更新,并且可能需要同时支持两套 endpoint 来支持多个版本。即使 SDK 可以在一定程度上掩盖这些问题,测试、指标等额外运维关注点仍然需要处理。基于 header 的路由只需极少的客户端改动。未选择 opt in 的客户端也仍可正常工作。
-
基础设施收益大于框架复杂度:主要目标是让网络基础设施(负载均衡器、代理、WAF)无需解析 body 就能路由和处理请求。这一收益与服务器所使用的框架无关。
基础设施支持
基于 HTTP header 的路由和处理已被以下组件支持:
- 负载均衡器:所有主流负载均衡器(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 header 中提取数据
该设计在 x-mcp-header 中使用显式名称值,而不是从参数名推导 header 名称,原因如下:
- 大小写敏感性不匹配:Header 名称不区分大小写,但 JSON Schema 属性名区分大小写
- 字符集限制:Header 名称仅限 ASCII 字符,而工具参数名可能包含任意 Unicode
- 简单性:无需用复杂方案从嵌套属性构造 header 名称
在 JSON Schema 中的位置
x-mcp-header 扩展直接放置在要镜像的属性的 JSON Schema 内部,而不是放在 schema 外部的单独元数据字段中。该设计选择有几个优点:
-
共置性:header 映射与其影响的属性定义在一起,使其立即清楚哪个参数会被镜像。开发者无需在 schema 与单独的元数据结构之间来回查找。
-
成熟模式:JSON Schema 明确支持扩展关键字(以
x- 开头的属性),这一模式在 OpenAPI 等生态中被广泛使用。工具作者和 SDK 开发者对此方式已经很熟悉。
-
Schema 可组合性:当 schema 被组合、扩展或通过
$ref 引用时,x-mcp-header 注释会随属性定义一起传递。单独的元数据结构则需要复杂的同步逻辑来保持一致性。
-
工具兼容性:现有 JSON Schema 校验器默认会忽略未知关键字,因此添加
x-mcp-header 不会破坏现有 schema 校验。不理解此扩展的工具会直接跳过它。
-
降低复杂度:单独的元数据结构需要定义一种映射机制(例如 JSON Pointer 或属性路径)来将 headers 与属性关联起来,这会增加实现复杂度并带来出错风险。
x-mcp-header 机制目前仅适用于 tools/call 请求,因为 tools 是唯一带有 inputSchema 且支持 JSON Schema 扩展关键字的 MCP 原语。Resources 和 prompts 没有等价的 schema 结构:resources/read 只接收一个 uri(已通过 Mcp-Name 暴露),而 prompts/get 将参数定义为一个简单的 {name, description, required} 数组,不具备 JSON Schema 可扩展性。若要将自定义 header 映射泛化到这些原语,需要为 resources 和 prompts 增加类似 inputSchema 的定义,这属于更大的规范变更。这被记录为未来可能的扩展。
本规范有意不定义单个 header 值长度、MCP header 总大小或自定义 header 数量的限制。Header 纯粹是 HTTP 概念,而 HTTP 本身(RFC 9110)并未规定 header 大小限制。常见 HTTP 基础设施会施加自己的限制——从某些服务器的 4–8 KB(例如 Apache 约 8190 字节)到其他系统的 128 KB(例如 Cloudflare)不等——但合适的限制取决于部署环境,而只有服务运营者才能确定这一点。
定义规范层面的限制(例如“省略超过 8192 字节的 header”)会带来问题:
- 任意阈值:无论选择什么数值,对某些部署来说都太低,对另一些则无关紧要。合适的“正确”限制因基础设施而异。
- 适得其反的省略:如果客户端因为某个 header 超过规范定义的限制而省略它,那么依赖该 header 进行路由的服务器和中间件就必须解析 body 或拒绝请求——这会削弱将值暴露在 header 中的核心目的。
- 不必要的 SDK 负担:SDK 维护者需要为一个在实践中很少适用的约束实现并测试限制检查逻辑。
- 与 HTTP 重复:服务器和中间件已经会使用标准 HTTP 状态码拒绝过大的 header(
413 Request Entity Too Large、431 Request Header Fields Too Large),客户端无论如何都必须处理这些响应。
给实现者的注记:服务器、中间件和客户端可以根据各自的部署环境独立对单个 header 大小、MCP header 总大小或自定义 header 数量施加限制。服务器应记录其施加的任何限制。客户端应优雅地处理 413 Request Entity Too Large 或 431 Request Header Fields Too Large 响应。工具作者应将 x-mcp-header 注释限制在那些能带来明确基础设施收益的参数上。
不安全值的编码方式
对于无法安全表示为普通 ASCII header 值的参数值(非 ASCII 字符、前导/尾随空白字符、控制字符),我们考虑了四种编码方式:
-
Sentinel 包裹(所选方案):在同一个
Mcp-Param-{Name} header 内使用 =?base64?{value}?= 前缀/后缀来标识 Base64 编码值。
-
单独的 header 名称:为编码值使用不同的 header 名称,例如
Mcp-ParamEncoded-{Name},通过 header 名称而不是值格式来表示编码。
-
隐式编码:让解析器根据工具 schema 推断编码,例如在工具定义中使用
"x-mcp-header-encoding": "base64" 注释。
-
始终编码:对每个
Mcp-Param-{Name} 值都无条件进行 Base64 编码。
| 方案 | 优点 | 缺点 |
|---|
| Sentinel 包裹 | 每个参数只需一个 header 名称;常见情况(纯 ASCII)可读性强;中间件无需解码即可基于普通值进行路由 | 带内信号在理论上可能与字面值冲突;每个读取方都必须检查该前缀 |
| 单独的 header 名称 | 不存在带内歧义;编码方式可从 header 名称自解释 | header 命名空间翻倍;每个中间件都必须为每个参数检查两个 header 名称;如果两者都存在,还需要冲突规则 |
| 隐式编码 | 最简单的线格式;无需 sentinel 或额外 header | 中间件需要访问工具 schema 才知道是否解码——这违背了在 header 中暴露值的目的;按参数静态决定无法很好处理混合情况 |
| 始终编码 | 规则最简单;无需条件逻辑或歧义 | 纯 ASCII 值会变得不可读;中间件必须解码 Base64 才能检查任何值,这会显著削弱本 SEP 的核心动机 |
结论:Sentinel 包裹方案提供了最佳权衡。自定义 header 的主要用途是让中间件能够对地区名称、租户 ID 之类简单、可读的值进行路由和过滤——这些值几乎总是纯 ASCII,且永远不会触发 Base64 编码。方案 4 使所有值对中间件都变得不可见。方案 3 在没有工具 schema 访问权限的情况下,使中间件无法区分编码值与字面值。方案 2 消除了带内歧义,但会使 header 命名空间翻倍,要求中间件为每个参数检查两种可能的 header 名称,并在两者同时存在时增加冲突规则。方案 1 中 sentinel 的理论碰撞风险可以忽略不计,因为 =?base64?...?= 在实践中成为字面参数值的可能性极低。
向后兼容性
标准头
在使用新的 MCP 版本时,现有客户端和 SDK 将被要求包含标准头。这是一个小幅度的新增,因为客户端已经包含了诸如 Mcp-Protocol-Version 之类的头,因此每条消息只需额外增加一到两个新头。
实现新版本的服务器 MUST 拒绝缺少必需头的请求。服务器 MAY 在协商较旧协议版本时,通过接受不带头的请求来支持旧客户端。
来自工具参数的自定义头
x-mcp-header 扩展对服务器而言是可选的。没有此属性的现有工具将继续按原样工作。不过,实现了包含本规范的 MCP 版本的客户端 MUST 支持该特性。不支持 x-mcp-header 的旧客户端仍可正常工作,但无法提供服务器可能依赖的基于头的路由优势。
安全影响
头注入
当包含控制字符(尤其是 \r\n)的恶意值被包含在头中时,就会发生头注入攻击,这可能允许攻击者注入额外的头或提前终止头部区域。
客户端 MUST 遵循本规范中定义的 Value Encoding 规则。这些规则确保:
- 控制字符绝不会包含在头值中
- 非 ASCII 值会使用 Base64 安全编码
- 超过安全长度限制的值会被省略
头伪造
服务器 MUST 验证头值与请求体中的对应值一致。这可防止客户端发送不匹配的头,以在执行不同操作时操纵路由。
例如,恶意客户端可能会尝试:
- 在执行面向高安全区域的操作时,将请求路由到安全性较低的区域
- 通过伪造租户标识来绕过速率限制
- 通过虚报正在执行的操作来规避安全策略
信息泄露
为头指定的工具参数值将对网络中间设备(负载均衡器、代理、日志系统)可见。服务器开发者:
- SHOULD NOT 将敏感参数(密码、API 密钥、令牌、PII)标记为
x-mcp-header
- SHOULD 记录哪些参数会作为头暴露
- SHOULD 考虑到 Base64 编码不提供机密性——它只是编码,不是加密
信任头值
头值来源于工具调用参数,这些参数可能受到 LLM 或恶意客户端的影响。中间设备和服务器 MUST NOT 将这些值视为用于安全敏感决策的可信输入。尤其是:
- 暗示可访问特定资源的头值(例如租户 ID、区域名称)在授予对这些资源的访问权限之前,MUST 先由身份验证用户的权限进行独立验证。
- 头值 MUST NOT 作为授予提升权限的唯一依据;必须由服务器端强制执行速率限制和配额。
- 部署 SHOULD 尽早在流水线中拒绝带有过大或过多头的请求——在执行 Base64 解码或正文解析之前——以缓解精心构造的载荷带来的拒绝服务风险。
一致性测试用例
本节定义了一致性测试 MUST 覆盖的边界情况,以确保实现之间的互操作性。
标准头边界情况
大小写敏感性
| Test Case | Input | Expected Behavior |
|---|
| Header name case variation | mcp-method: tools/call | Server MUST accept (header names are case-insensitive) |
| Header name mixed case | MCP-METHOD: tools/call | Server MUST accept |
| Method value case | Mcp-Method: TOOLS/CALL | Server MUST reject (method values are case-sensitive) |
头/正文不匹配
| Test Case | Header Value | Body Value | Expected Behavior |
|---|
| Method mismatch | Mcp-Method: tools/call | "method": "prompts/get" | Server MUST reject with 400 and error code -32001 |
| Tool name mismatch | Mcp-Name: foo | "params": {"name": "bar"} | Server MUST reject with 400 and error code -32001 |
| Missing required header | (no Mcp-Method) | Valid body | Server MUST reject with 400 and error code -32001 |
| Extra whitespace in header | Mcp-Name: foo | "params": {"name": "foo"} | Server MUST accept (trim whitespace per HTTP spec) |
值中的特殊字符
| Test Case | Value | Expected Behavior |
|---|
| Tool name with hyphen | my-tool-name | Client sends as-is; server accepts |
| Tool name with underscore | my_tool_name | Client sends as-is; server accepts |
| Resource URI with special chars | file:///path/to/file%20name.txt | Client sends as-is; server accepts |
| Resource URI with query string | https://example.com/resource?id=123 | Client sends as-is; server accepts |
自定义头边界情况
| Test Case | Schema | Expected Behavior |
|---|
| Duplicate header names (same case) | Two properties with "x-mcp-header": "Region" | Client MUST reject tool definition |
| Duplicate header names (different case) | "x-mcp-header": "Region" and "x-mcp-header": "REGION" | Client MUST reject tool definition (case-insensitive uniqueness) |
| Header name matches standard header | "x-mcp-header": "Method" | Allowed (produces Mcp-Param-Method, not Mcp-Method) |
| Empty header name | "x-mcp-header": "" | Client MUST reject tool definition |
| Test Case | x-mcp-header Value | Expected Behavior |
|---|
| Contains space | "x-mcp-header": "My Region" | Client MUST reject tool definition |
| Contains colon | "x-mcp-header": "Region:Primary" | Client MUST reject tool definition |
| Contains non-ASCII | "x-mcp-header": "Région" | Client MUST reject tool definition |
| Contains control character | "x-mcp-header": "Region\t1" | Client MUST reject tool definition |
值编码边界情况
| Test Case | Parameter Value | Expected Header Value |
|---|
| Plain ASCII string | "us-west1" | Mcp-Param-Region: us-west1 |
| String with leading space | " us-west1" | Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?= |
| String with trailing space | "us-west1 " | Mcp-Param-Region: =?base64?dXMtd2VzdDEg?= |
| String with leading/trailing spaces | " us-west1 " | Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?= |
| String with internal spaces only | "us west 1" | Mcp-Param-Region: us west 1 |
| Boolean true | true | Mcp-Param-Flag: true |
| Boolean false | false | Mcp-Param-Flag: false |
| Integer | 42 | Mcp-Param-Count: 42 |
| Floating point | 3.14159 | Mcp-Param-Value: 3.14159 |
| Non-ASCII characters | "日本語" | Mcp-Param-Text: =?base64?5pel5pys6Kqe?= |
| String with newline | "line1\nline2" | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
| String with carriage return | "line1\r\nline2" | Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?= |
| String with leading tab | "\tindented" | Mcp-Param-Text: =?base64?CWluZGVudGVk?= |
| Empty string | "" | Mcp-Param-Name: (empty value) |
类型限制违规
| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
|---|
| Array type | "type": "array" | Yes | Server MUST reject tool definition |
| Object type | "type": "object" | Yes | Server MUST reject tool definition |
| Null type | "type": "null" | Yes | Server MUST reject tool definition |
| Nested property | Property inside object | Yes | Server MUST reject tool definition |
服务器验证边界情况
Base64 解码
| Test Case | Header Value | Expected Behavior |
|---|
| Valid Base64 | =?base64?SGVsbG8=?= | Server decodes to "Hello" and validates |
| Invalid Base64 padding | =?base64?SGVsbG8?= | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Invalid Base64 characters | =?base64?SGVs!!!bG8=?= | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Missing prefix | SGVsbG8= | Server treats as literal value, not Base64 |
| Missing suffix | =?base64?SGVsbG8= | Server treats as literal value, not Base64 |
| Malformed wrapper | =?BASE64?SGVsbG8=?= | Server MUST accept (case-insensitive prefix) |
空值与缺失值
| Test Case | Scenario | Expected Behavior |
|---|
| Parameter with x-mcp-header is null | "region": null | Client MUST omit header |
| Parameter with x-mcp-header is missing | Parameter not in arguments | Client MUST omit header |
| Optional parameter present | Optional parameter provided | Client MUST include header |
当正文中存在值但缺少自定义头时
| Test Case | Header Present | Body Value | Expected Behavior |
|---|
| Standard header omitted, value in body | No Mcp-Name | "params": {"name": "foo"} | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Custom header omitted, value in body | No Mcp-Param-Region | "region": "us-west1" | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
参考实现
将在此 SEP 达到最终状态之前提供。
实现要求:
- 服务器 SDK:提供一种机制(属性/装饰器)来标记带有
x-mcp-header 的参数
- 客户端 SDK:实现提取和编码头部值的客户端行为
- 验证:双方都必须验证头部/正文一致性