SEP-2243:Streamable HTTP 传输的 HTTP Header 标准化

SEP-2243:Streamable HTTP 传输的 HTTP Header 标准化

社区译文;来源: MCP 29affa6b

本 SEP 已达到 Final 状态,并作为已接受设计的历史记录保留。 定稿后对协议所做的更改不会反映在此处。权威要求请参阅当前规范 及其变更日志。
字段值
SEP2243
标题Streamable HTTP 传输的 HTTP Header 标准化
状态Final
类型标准跟踪
创建日期2026-02-04
作者MCP Transports Working Group
发起人无
PR#2243

摘要

本 SEP 建议在 Streamable HTTP 传输的标准 HTTP header 位置公开关键路由和上下文信息。通过将 JSON-RPC payload 中的关键字段镜像到 HTTP header,负载均衡器、代理和可观测性工具等网络中间件无需深度包检测即可路由和处理 MCP 流量,从而降低延迟与计算开销。

动机

当前基于 HTTP 的 MCP 实现将所有路由信息都隐藏在 JSON-RPC payload 内。这给网络基础设施带来阻碍:

  • 负载均衡器必须终止 TLS 并解析完整 JSON body,才能提取路由信息(例如区域、工具名称)
  • 代理和网关无法在不进行深度包检测的情况下作出路由决策
  • 可观测性工具对 MCP 流量模式的可见性有限
  • 限流器和 WAF 无法依据 MCP 特有字段应用策略

通过在 HTTP header 中公开关键字段,我们可以让标准网络基础设施使用现有且受到广泛支持的机制处理 MCP 流量。

规范

标准 Header

Streamable HTTP 传输将要求 POST 请求包含以下从 request body 镜像而来的 header:

Header 名称来源字段必须用于
Mcp-Methodmethod所有请求和通知
Mcp-Nameparams.name 或 params.uritools/call、resources/read、prompts/get 请求

这些 header 是遵循引入它们的 MCP 版本时的必需项。

服务器行为:处理 request body 的服务器 MUST 拒绝 header 中指定的值与 request body 中的值不匹配的请求。

理由:该要求可防止网络中的不同组件依赖不同真相源时可能产生的安全漏洞和错误情况。例如,负载均衡器或网关可能使用 header 值作出路由决策,而 MCP 服务器使用 body 值执行操作。该要求适用于任何处理 message body 的网络中间件,也适用于 MCP 服务器本身。

实现说明:验证整数参数值时,服务器 SHOULD 以数值而非字符串方式比较 header 值与 body 值(例如,42.0 和 42 视为相等)。

大小写敏感性:Header 名称(在 RFC 9110 中称为“field name”)不区分大小写。客户端和服务器 MUST 对 header 名称使用不区分大小写的比较。

示例:tools/call 请求

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"
    }
  }
}

示例:其他请求方法

对于不涉及工具、资源或提示词的请求,只需要 Mcp-Method header:

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 header:

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"
}

来自工具参数的自定义 Header

MCP 服务器 MAY 在工具 inputSchema 内的参数 schema 中使用 x-mcp-header 扩展属性,指定应镜像到 HTTP header 的特定工具参数。

客户端要求:服务器可以选择是否使用 x-mcp-header,但客户端 MUST 支持此功能。当服务器的工具定义包含 x-mcp-header 注解时,符合要求的客户端 MUST 按本文规定将指定参数值镜像到 HTTP header。

Schema 扩展

x-mcp-header 属性指定用于构造 header 名称 Mcp-Param-{name} 的名称部分。

x-mcp-header 值的约束:

  • MUST NOT 为空
  • MUST 符合 HTTP field-name token 语法(1*tchar,RFC 9110 第 5.1 节)
  • MUST NOT 包含控制字符,包括回车(CR,\r)或换行(LF,\n)
  • 在 inputSchema 的全部 x-mcp-header 值中,MUST 按不区分大小写的方式保持唯一
  • MUST 只应用于基本类型(integer、string、boolean)的参数。不允许应用于 number 类型的参数。整数值 MUST 位于 JavaScript 的安全范围内(−2^53+1 至 2^53−1)
  • MAY 应用于 inputSchema 中任意嵌套深度的属性,而不限于顶层属性

使用 Streamable HTTP 传输的客户端 MUST 拒绝任何 x-mcp-header 值违反上述约束的工具定义。拒绝是指客户端 MUST 从 tools/list 的结果中排除无效工具。客户端拒绝工具定义时 SHOULD 记录警告,其中包含工具名称和拒绝原因。此行为确保单个格式错误的工具定义不会妨碍其他有效工具的使用。使用其他传输(例如 stdio)的客户端 MAY 完全忽略 x-mcp-header 注解。

工具定义示例:

{
  "name": "execute_sql",
  "description": "Execute SQL on Google Cloud Spanner",
  "inputSchema": {
    "type": "object",
    "properties": {
      "region": {
        "type": "string",
        "description": "The region to execute the query in",
        "x-mcp-header": "Region"
      },
      "query": {
        "type": "string",
        "description": "The SQL query to execute"
      }
    },
    "required": ["region", "query"]
  }
}

示例:地理分布式数据库

假设一个服务器为 Google Cloud Spanner 提供 execute_sql 工具,该工具要求 region 参数。

工具定义:

{
  "name": "execute_sql",
  "description": "Execute SQL on Google Cloud Spanner",
  "inputSchema": {
    "type": "object",
    "properties": {
      "region": {
        "type": "string",
        "description": "The region to execute the query in",
        "x-mcp-header": "Region"
      },
      "query": {
        "type": "string",
        "description": "The SQL query to execute"
      }
    },
    "required": ["region", "query"]
  }
}

场景:客户端请求在 us-west1 中执行 SQL。

当前阻碍:全局负载均衡器收到请求后,必须终止 TLS 并解析完整 JSON body,找到 "region": "us-west1" 后才能判断应将数据包路由至 Oregon 集群还是 Belgium 集群。

采用本提案后:客户端检测到 x-mcp-header 注解,并自动向 HTTP 请求添加 header Mcp-Param-Region: us-west1。负载均衡器现在无需解析 body,即可依据 header 进行路由。

请求:

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 平台公开操作不同客户租户的工具。通过在 header 中公开租户 ID,该平台可以将请求路由到租户专属的基础设施。

工具定义:

{
  "name": "query_analytics",
  "description": "Query analytics data for a tenant",
  "inputSchema": {
    "type": "object",
    "properties": {
      "tenant_id": {
        "type": "string",
        "description": "The tenant identifier",
        "x-mcp-header": "TenantId"
      },
      "metric": {
        "type": "string",
        "description": "The metric to query"
      },
      "start_date": {
        "type": "string",
        "description": "Start date for the query range"
      },
      "end_date": {
        "type": "string",
        "description": "End date for the query range"
      }
    },
    "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": "Generate a complex report",
  "inputSchema": {
    "type": "object",
    "properties": {
      "report_type": {
        "type": "string",
        "description": "Type of report to generate"
      },
      "priority": {
        "type": "string",
        "description": "Request priority: low, normal, or 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"
    }
  }
}

Header 处理

值编码

客户端在将参数值放入 HTTP header 前 MUST 对其编码,以确保安全传输并防止注入攻击。

字符限制

根据 RFC 9110,HTTP header field value 必须由可见 ASCII 字符(0x21-0x7E)、空格(0x20)和水平制表符(0x09)组成。明确禁止以下字符:

  • 回车(\r,0x0D)
  • 换行(\n,0x0A)
  • 空字符(\0,0x00)
  • ASCII 范围以外的任何字符(> 0x7F)

空白处理

HTTP 解析器通常会移除 header 值开头和结尾的空白。为保留参数值开头和结尾的空格,客户端在值符合以下情况时 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 header 值时,客户端 MUST 使用以下格式,对该值的 UTF-8 表示进行 Base64 编码:

Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=

前缀 =?base64? 和后缀 ?= 表示该值采用 Base64 编码。这些标记区分大小写,并且 MUST 严格按所示形式(小写)出现。需要检查这些值的服务器和中间件 MUST 相应地进行解码。

为避免歧义,任何匹配哨兵模式的纯 ASCII 值(即以 =?base64? 开头并以 ?= 结尾)也 MUST 由客户端进行 Base64 编码。

示例:

原始值原因编码后的 Header 值
"us-west1"纯 ASCIIMcp-Param-Region: us-west1
"Hello, 世界"包含非 ASCII 字符Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=
" padded "开头/结尾有空格Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=
"line1\nline2"包含换行符Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=
"=?base64?literal?="匹配哨兵模式Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=

客户端行为

通过 HTTP 传输构造 tools/call 请求时,客户端 MUST:

  1. 从 request body 中提取所有标准 header 的值(例如 method、params.name、params.uri)
  2. 向请求附加 Mcp-Method header,并在适用时附加 Mcp-Name header
  3. 检查工具 inputSchema 中标有 x-mcp-header 的属性,并提取各参数的值
  4. 按值编码中的规则对值进行编码
  5. 向请求附加 Mcp-Param-{Name}: {Value} header

实现说明:客户端 MUST 使用最近一次取得的工具 inputSchema 构造 Mcp-Param-* header。从未取得过该工具 inputSchema 的客户端 SHOULD 发送不含 Mcp-Param-* header 的请求。如果服务器因必需的 Mcp-Param-* header 缺失或与 body 不匹配而拒绝请求,客户端 SHOULD 调用 tools/list 取得当前 inputSchema,随后使用适当的 header 重试原始请求。客户端 MAY 通过其他方式预加载工具定义(例如从先前会话或配置中读取),以便在事先未调用 tools/list 的情况下发出 header。

服务器行为

服务器收到请求时,MUST 拒绝含有无效字符的 Mcp-Param-{Name} header(参见值编码一节中的“字符限制”)。

任何处理 message body(而非只是转发)的服务器都 MUST 验证编码后的 header 值在必要时完成 Base64 解码后,与 request body 中对应的值匹配。任何验证失败时,服务器 MUST 以 400 Bad Request HTTP 状态拒绝请求。

错误码

服务器因 header 验证失败而拒绝请求时,MUST 返回包含以下错误码的 JSON-RPC 错误响应:

代码名称说明
-32001HeaderMismatchHTTP header 与 request body 中对应的值不匹配,或必需 header 缺失/格式错误。

该错误码位于 JSON-RPC 由实现定义的服务器错误范围(-32000 至 -32099)内。

错误响应格式:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
  }
}

验证失败条件:

  • 必需的标准 header(Mcp-Method、Mcp-Name 等)缺失
  • header 值与 request body 值不匹配
  • Base64 编码的值无法解码
  • header 值包含无效字符

说明:中间件发生验证失败时 MUST 返回适当的 HTTP 错误状态(例如 400 Bad Request),但不要求返回 JSON-RPC 错误响应。

说明:依据镜像 header 实施策略(例如按租户路由或限流)的中间件 SHOULD 验证 MCP-Protocol-Version header 指明的版本要求进行 header-body 验证。如果版本更旧或该 header 缺失,中间件 SHOULD 拒绝请求,而不是信任未经验证的 header 值。

自定义 Header 处理:

自定义 header(通过 x-mcp-header 定义)遵循与标准 header 相同的验证规则:

场景客户端行为服务器行为
提供参数值客户端 MUST 包含 header服务器 MUST 验证 header 与 body 匹配
参数值为 null客户端 MUST 省略 header服务器 MUST NOT 期待该 header
arguments 中没有该参数客户端 MUST 省略 header服务器 MUST NOT 期待该 header
客户端省略 header,但 body 中存在值不符合要求的客户端服务器 MUST 拒绝请求

服务器因自定义 header 缺失或无效而拒绝请求时,MUST 返回 HTTP 状态 400 Bad Request 和 JSON-RPC 错误码 -32001(HeaderMismatch)。

理由

Header 与路径的比较

本提案将请求数据镜像到 header,而不是将其编码到 URL 路径中。

Header 的优点:

  1. 简单:所有广泛使用的网络负载均衡器都支持基于 HTTP header 的路由
  2. 多版本支持:客户端和服务器更容易支持多个 MCP 版本
  3. 兼容性:Header 可与现有 Streamable HTTP 传输设计配合,不必更改 endpoint 结构
  4. 值不受限:Header 值可以包含在 URL 中需要编码的字符(例如 /、?、#)
  5. 没有 URL 长度限制:可以传输很长的值,而不会触及 URL 长度限制

基于路径的路由的优点:

  1. 框架简单性:许多 Web 框架(Flask、Express、Django、Rails)原生支持基于路径的路由,所需配置很少
  2. 日志记录:URL 路径通常会默认记录,调试更容易

权衡和框架注意事项:

框架基于 Header 的路由基于路径的路由
Flask (Python)路由前需要中间件或装饰器提取 header通过 @app.route('/mcp/<method>') 原生支持
Express (Node.js)可通过 req.headers 轻松实现,但需要自定义路由逻辑通过 app.post('/mcp/:method') 原生支持
Django (Python)需要自定义中间件原生 URL pattern
Go (net/http)可通过 r.Header.Get() 轻松实现通过路径 pattern 原生支持
ASP.NET Core可通过 [FromHeader] 属性轻松实现通过路由模板原生支持

对于 Flask 等非常偏向基于路径路由的框架,实现基于 header 的路由需要额外代码:

# Flask example: Header-based routing requires manual dispatch
@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)
    # ... etc

尽管某些框架会因此增加复杂性,最终仍选择基于 header 的路由,原因如下:

  1. 向后兼容性:引入基于路径的路由将要求所有现有 MCP 服务器进行重大更新,并可能需要同时支持两组 endpoint 才能支持多个版本。即使 SDK 能够掩盖这一差异,测试、指标等额外运维工作仍不可避免。基于 header 的路由只需要很小的客户端改动。未选择加入的客户端仍可正常工作。

  2. 基础设施收益大于框架复杂性:主要目标是让网络基础设施(负载均衡器、代理、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 名称

该设计在 x-mcp-header 中使用显式名称值,而不是从参数名派生 header 名称,原因如下:

  1. 大小写敏感性不匹配:Header 名称不区分大小写,而 JSON Schema 属性名称区分大小写
  2. 字符集约束:Header 名称仅限 ASCII 字符,但工具参数名可以包含任意 Unicode 字符
  3. 简单性:无需使用复杂方案从嵌套属性构造 header 名称

在 JSON Schema 中的位置

x-mcp-header 扩展直接放在要镜像的属性对应的 JSON Schema 中,而不是放在 schema 外部的独立 metadata 字段中。这项设计有以下优点:

  1. 就近放置:Header 映射与其影响的属性定义在一起,可立即看出哪个参数会被镜像。开发者无需在 schema 和独立 metadata 结构之间交叉查找。

  2. 既有模式:JSON Schema 明确支持扩展关键字(以 x- 开头的属性),该模式在 OpenAPI 等生态系统中广泛使用。工具作者和 SDK 开发者已经熟悉这种方式。

  3. Schema 可组合性:使用 $ref 组合、扩展或引用 schema 时,x-mcp-header 注解会随属性定义一同传递。独立的 metadata 结构需要复杂的同步逻辑才能保持一致。

  4. 工具兼容性:现有 JSON Schema 验证器默认忽略未知关键字,因此添加 x-mcp-header 不会破坏现有 schema 验证。不理解此扩展的工具只会跳过它。

  5. 降低复杂性:独立的 metadata 结构需要定义映射机制(例如 JSON Pointer 或属性路径)来关联 header 和属性,从而增加实现复杂性和出错可能。

范围:仅限工具

x-mcp-header 机制目前只适用于 tools/call 请求,因为工具是唯一具有支持 JSON Schema 扩展关键字的 inputSchema 的 MCP primitive。资源和提示词没有对应的 schema 结构:resources/read 只接受一个 uri(已通过 Mcp-Name 公开),而 prompts/get 将 arguments 定义为简单的 {name, description, required} 数组,不具备 JSON Schema 可扩展性。要将自定义 header 映射推广到这些 primitive,需要为资源和提示词增加类似 inputSchema 的定义,这是一项更大的规范变更。此项被记为潜在的未来扩展。

规范层面不设 Header 大小限制

本规范有意不限制单个 header 值的长度、MCP header 总大小或自定义 header 数量。Header 完全属于 HTTP 概念,而 HTTP 本身(RFC 9110)不规定 header 大小限制。常见 HTTP 基础设施会施加自己的限制,从某些服务器的 4–8 KB(例如 Apache 约 8190 字节)到其他服务的 128 KB(例如 Cloudflare)不等;但合适的限制取决于部署环境,只有服务运营者能够确定。

定义规范层面的限制(例如“省略超过 8192 字节的 header”)会引入以下问题:

  1. 任意阈值:任何选定值对某些部署而言都过低,对另一些部署则没有意义。“正确”的限制随基础设施而异。
  2. 适得其反的省略:如果客户端因 header 超过规范规定的限制而省略它,依赖该 header 进行路由的服务器和中间件就只能解析 body 或拒绝请求,这会削弱在 header 中公开值的核心目的。
  3. 不必要的 SDK 负担:SDK 维护者需要为实践中很少触发的约束实现并测试限制检查逻辑。
  4. 与 HTTP 重复:服务器和中间件已经会使用标准 HTTP 状态码拒绝过大的 header(413 Request Entity Too Large、431 Request Header Fields Too Large),客户端无论如何都必须处理这些响应。

给实现者的说明:服务器、中间件和客户端 MAY 根据各自部署环境,独立限制单个 header 大小、MCP header 总大小或自定义 header 数量。服务器 SHOULD 记录其施加的所有限制。客户端 SHOULD 妥善处理 413 Request Entity Too Large 或 431 Request Header Fields Too Large 响应。工具作者 SHOULD 仅为能够带来明确基础设施收益的参数添加 x-mcp-header 注解。

不安全值的编码方式

对于无法安全表示为纯 ASCII header 值的参数值(非 ASCII 字符、开头/结尾空白、控制字符),曾考虑四种编码方式:

  1. 哨兵包装(选定方案):在同一个 Mcp-Param-{Name} header 内使用 =?base64?{value}?= 前缀/后缀,表示采用 Base64 编码的值。

  2. 独立 header 名称:为编码值使用不同的 header 名称,例如 Mcp-ParamEncoded-{Name},由 header 名称而非值格式指示编码方式。

  3. 隐式编码:让解析器从工具 schema 推断编码方式,例如通过工具定义中的 "x-mcp-header-encoding": "base64" 注解。

  4. 始终编码:无条件对每个 Mcp-Param-{Name} 值进行 Base64 编码。

方式优点缺点
哨兵包装每个参数只使用一个 header 名称;常见情况(纯 ASCII)可直接阅读;中间件无需解码即可依据纯文本值路由带内信号理论上可能与字面值冲突;每个读取方都必须检查前缀
独立 header 名称不存在带内歧义;header 名称本身即可说明编码方式header 命名空间扩大一倍;每个中间件都必须检查每个参数的两个 header 名称;两者同时存在时需要冲突规则
隐式编码wire format 最简单;不需要哨兵或额外 header中间件需要访问工具 schema 才知道是否解码,这违背了在 header 中公开值的目的;按参数静态决定无法妥善处理混合情况
始终编码规则最简单;没有条件逻辑或歧义纯 ASCII 值变得不可读;中间件必须解码 Base64 才能检查任何值,严重削弱本 SEP 的核心动机

结论:哨兵包装方式提供了最佳权衡。自定义 header 的主要用例是让中间件能够依据区域名称和租户 ID 等简单、可读的值进行路由和筛选;这些值几乎总是纯 ASCII,不会触发 Base64 编码。方案 4 会使所有值对中间件不透明。方案 3 会使无法访问工具 schema 的中间件无法区分编码值与字面值。方案 2 消除了带内歧义,但使 header 命名空间扩大一倍,要求中间件检查每个参数的两个可能 header 名称,并在两者同时出现时增加冲突规则。方案 1 中哨兵的理论冲突风险可以忽略,因为 =?base64?...?= 在实践中极少成为参数字面值。

向后兼容性

标准 Header

现有客户端和 SDK 在使用新的 MCP 版本时,将必须包含标准 header。这只是一项小幅增加,因为客户端已经包含 Mcp-Protocol-Version 等 header,每条消息只需增加一到两个新 header。

实现新版本的服务器 MUST 拒绝缺少必需 header 的请求。服务器在协商使用旧协议版本时,MAY 通过接受不含这些 header 的请求来支持旧客户端。

来自工具参数的自定义 Header

服务器可以选择是否使用 x-mcp-header 扩展。不含此属性的现有工具继续原样工作。但是,实现包含本规范的 MCP 版本的客户端 MUST 支持此功能。不支持 x-mcp-header 的旧客户端仍能运行,但无法提供服务器可能依赖的基于 header 的路由收益。

安全影响

Header 注入

当含有控制字符(尤其是 \r\n)的恶意值被放入 header 时,可能发生 header 注入攻击,攻击者可能借此注入额外 header,或提前终止 header 区段。

客户端 MUST 遵循本规范定义的值编码规则。这些规则确保:

  • header 值绝不包含控制字符
  • 非 ASCII 值使用 Base64 安全编码
  • 超过安全长度限制的值会被省略

Header 欺骗

服务器 MUST 验证 header 值与 request body 中对应的值匹配。这可以防止客户端发送不匹配的 header 来操纵路由,同时执行不同操作。

例如,恶意客户端可能尝试:

  • 将请求路由到安全性较低的区域,同时执行面向高安全区域的操作
  • 伪造租户标识符以绕过限流
  • 谎报正在执行的操作以规避安全策略

信息泄露

指定放入 header 的工具参数值将对网络中间件(负载均衡器、代理、日志系统)可见。服务器开发者:

  • SHOULD NOT 使用 x-mcp-header 标记敏感参数(密码、API 密钥、token、PII)
  • SHOULD 记录哪些参数会通过 header 公开
  • SHOULD 注意 Base64 编码不提供保密性,它只是一种编码,并非加密

信任 Header 值

Header 值来自工具调用 arguments,可能受到 LLM 或恶意客户端影响。中间件和服务器 MUST NOT 将这些值视为安全敏感决策中的可信输入。尤其是:

  • 暗示可访问特定资源的 header 值(例如租户 ID、区域名称)在授予资源访问权限前,MUST 针对已认证用户的权限进行独立验证。
  • 在服务器端未实施限流和配额的情况下,header 值 MUST NOT 作为授予更高权限的唯一依据。
  • 部署环境 SHOULD 在管线早期拒绝 header 过大或过多的请求,在执行 Base64 解码或 body 解析之前缓解构造 payload 引发的拒绝服务风险。

一致性测试用例

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

标准 Header 边界情况

大小写敏感性

测试用例输入预期行为
Header 名称大小写变化mcp-method: tools/call服务器 MUST 接受(header 名称不区分大小写)
Header 名称混合大小写MCP-METHOD: tools/call服务器 MUST 接受
Method 值大小写Mcp-Method: TOOLS/CALL服务器 MUST 拒绝(method 值区分大小写)

Header/Body 不匹配

测试用例Header 值Body 值预期行为
Method 不匹配Mcp-Method: tools/call"method": "prompts/get"服务器 MUST 以 400 和错误码 -32001 拒绝
工具名称不匹配Mcp-Name: foo"params": {"name": "bar"}服务器 MUST 以 400 和错误码 -32001 拒绝
缺少必需 header(无 Mcp-Method)有效 body服务器 MUST 以 400 和错误码 -32001 拒绝
Header 中有额外空白Mcp-Name: foo"params": {"name": "foo"}服务器 MUST 接受(按 HTTP 规范移除空白)

值中的特殊字符

测试用例值预期行为
工具名称含连字符my-tool-name客户端原样发送;服务器接受
工具名称含下划线my_tool_name客户端原样发送;服务器接受
资源 URI 含特殊字符file:///path/to/file%20name.txt客户端原样发送;服务器接受
资源 URI 含查询字符串https://example.com/resource?id=123客户端原样发送;服务器接受

自定义 Header 边界情况

x-mcp-header 名称冲突

测试用例Schema预期行为
重复的 header 名称(大小写相同)两个属性都含 "x-mcp-header": "Region"客户端 MUST 拒绝工具定义
重复的 header 名称(大小写不同)"x-mcp-header": "Region" 和 "x-mcp-header": "REGION"客户端 MUST 拒绝工具定义(不区分大小写的唯一性)
Header 名称与标准 header 匹配"x-mcp-header": "Method"允许(生成 Mcp-Param-Method,而非 Mcp-Method)
Header 名称为空"x-mcp-header": ""客户端 MUST 拒绝工具定义

无效的 x-mcp-header 值

测试用例x-mcp-header 值预期行为
包含空格"x-mcp-header": "My Region"客户端 MUST 拒绝工具定义
包含冒号"x-mcp-header": "Region:Primary"客户端 MUST 拒绝工具定义
包含非 ASCII 字符"x-mcp-header": "Région"客户端 MUST 拒绝工具定义
包含控制字符"x-mcp-header": "Region\t1"客户端 MUST 拒绝工具定义

值编码边界情况

测试用例参数值预期 Header 值
纯 ASCII 字符串"us-west1"Mcp-Param-Region: us-west1
字符串以空格开头" us-west1"Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?=
字符串以空格结尾"us-west1 "Mcp-Param-Region: =?base64?dXMtd2VzdDEg?=
字符串开头和结尾均有空格" us-west1 "Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?=
字符串只含内部空格"us west 1"Mcp-Param-Region: us west 1
布尔值 truetrueMcp-Param-Flag: true
布尔值 falsefalseMcp-Param-Flag: false
整数42Mcp-Param-Count: 42
浮点数3.14159Mcp-Param-Value: 3.14159
非 ASCII 字符"日本語"Mcp-Param-Text: =?base64?5pel5pys6Kqe?=
字符串含换行符"line1\nline2"Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=
字符串含回车符"line1\r\nline2"Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?=
字符串以制表符开头"\tindented"Mcp-Param-Text: =?base64?CWluZGVudGVk?=
空字符串""Mcp-Param-Name: (空值)

类型限制违规

测试用例属性类型存在 x-mcp-header预期行为
Array 类型"type": "array"是服务器 MUST 拒绝工具定义
Object 类型"type": "object"是服务器 MUST 拒绝工具定义
Null 类型"type": "null"是服务器 MUST 拒绝工具定义
嵌套属性object 内的属性是服务器 MUST 拒绝工具定义

服务器验证边界情况

Base64 解码

测试用例Header 值预期行为
有效 Base64=?base64?SGVsbG8=?=服务器解码为 "Hello" 并验证
无效 Base64 padding=?base64?SGVsbG8?=服务器 MUST 以 400 和错误码 -32001 拒绝;中间件 MAY 以 400 状态码拒绝
无效 Base64 字符=?base64?SGVs!!!bG8=?=服务器 MUST 以 400 和错误码 -32001 拒绝;中间件 MAY 以 400 状态码拒绝
缺少前缀SGVsbG8=服务器将其视为字面值,而非 Base64
缺少后缀=?base64?SGVsbG8=服务器将其视为字面值,而非 Base64
前缀不是小写=?BASE64?SGVsbG8=?=服务器将其视为字面值,而非 Base64

Null 和缺失值

测试用例场景预期行为
含 x-mcp-header 的参数为 null"region": null客户端 MUST 省略 header
含 x-mcp-header 的参数缺失arguments 中没有该参数客户端 MUST 省略 header
存在可选参数提供了可选参数客户端 MUST 包含 header

Body 中有值但缺少自定义 Header

测试用例是否存在 HeaderBody 值预期行为
省略标准 header,但 body 中有值无 Mcp-Name"params": {"name": "foo"}服务器 MUST 以 400 和错误码 -32001 拒绝;中间件 MAY 以 400 状态码拒绝
省略自定义 header,但 body 中有值无 Mcp-Param-Region"region": "us-west1"服务器 MUST 以 400 和错误码 -32001 拒绝;中间件 MAY 以 400 状态码拒绝

参考实现

将在本 SEP 达到 Final 状态前提供。

实现要求:

  • 服务器 SDK:提供使用 x-mcp-header 标记参数的机制(属性/装饰器)
  • 客户端 SDK:实现提取和编码 header 值的客户端行为
  • 验证:双方都必须验证 header/body 一致性

SEP 达到 Final 状态后的变更

本 SEP 作为已接受内容的历史记录保留。以下列表记录本 SEP 达到 Final 状态后对规范所做的更改。权威且最新的要求请参阅当前规范。

  • HeaderMismatch 错误码从 -32001 重新分配为 -32020。 本 SEP 最初将 HeaderMismatch 分配为 -32001。#2907 中的错误码分配更新将 HeaderMismatch 重新分配为 -32020。针对当前规范实现时,上文对 -32001 的所有引用都应解读为 -32020。