请求头参数
大多数服务器都用不到这个。
服务器前面的网关或负载均衡器只能根据不解析请求体就能读到的内容来路由。用 x-mcp-header 标记一个工具参数,使用 2026-07-28 协议版本 的客户端就会把它的值同时作为 HTTP 请求头发送。
标记参数
这个标记就是参数的 JSON Schema 里多出的一个键。在 MCPServer 上,由 Field 把它放进去:
server.py
from typing import Annotated
from pydantic import Field
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.tool()
def check_stock(
title: str,
region: Annotated[str, Field(json_schema_extra={"x-mcp-header": "Region"})],
) -> str:
"""Count the copies of a book in one region's warehouses."""
return f"{title}: 3 copies in {region}."
- 在
2026-07-28的 Streamable HTTP 上,客户端会在请求体之外同时发送Mcp-Param-Region。两者不一致时,服务器会拒绝这次调用。 - 没有列出过该工具的客户端从未见过这个标记:它不会发送请求头,调用会遭到拒绝。这时本 SDK 的
Client会列出工具并重发一次调用,所以先列出工具只是省去一次往返。 - 其他所有连接都会忽略这个注解。
函数不用改:region 仍然作为参数传入。
哪些参数可以标记
str、int 和 bool 类型的参数。其他类型在注册工具时都会被拒绝,并抛出 InvalidSignature。
这也包括 str | None,因为它没有单一的类型。可选参数需要用 Pydantic 的 WithJsonSchema 把模式明确写出来:
region: Annotated[str | None, WithJsonSchema({"type": "string", "x-mcp-header": "Region"})] = None
在底层 Server 上
在那里 input_schema 是手写的,所以直接把这个键写进去:
server.py
from mcp.server import Server, ServerRequestContext
from mcp.types import (
CallToolRequestParams,
CallToolResult,
ListToolsResult,
PaginatedRequestParams,
TextContent,
Tool,
)
CHECK_STOCK = Tool(
name="check_stock",
description="Count the copies of a book in one region's warehouses.",
input_schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"region": {"type": "string", "x-mcp-header": "Region"},
},
"required": ["title", "region"],
},
)
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
return ListToolsResult(tools=[CHECK_STOCK])
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
args = params.arguments or {}
text = f"{args['title']}: 3 copies in {args['region']}."
return CallToolResult(content=[TextContent(type="text", text=text)])
server = Server("Bookshop", on_list_tools=list_tools, on_call_tool=call_tool)
app = server.streamable_http_app()
- 没有任何东西替你检查这个注解:无效的注解会照样提供出去,
2026-07-28客户端则会把这个工具从列表中剔除。
按名称获取模式
要检查请求头,SDK 需要在分发调用之前拿到工具的输入模式。没有 get_tool_input_schema 时,每次调用只要带有参数,SDK 就会运行你的 on_list_tools 处理函数来获取它,不管有没有工具被标记。
server.py
from typing import Any
from mcp.server import Server, ServerRequestContext
from mcp.types import (
CallToolRequestParams,
CallToolResult,
ListToolsResult,
PaginatedRequestParams,
TextContent,
Tool,
)
CHECK_STOCK = Tool(
name="check_stock",
description="Count the copies of a book in one region's warehouses.",
input_schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"region": {"type": "string", "x-mcp-header": "Region"},
},
"required": ["title", "region"],
},
)
TOOLS = {CHECK_STOCK.name: CHECK_STOCK}
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
return ListToolsResult(tools=list(TOOLS.values()))
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
args = params.arguments or {}
text = f"{args['title']}: 3 copies in {args['region']}."
return CallToolResult(content=[TextContent(type="text", text=text)])
def tool_input_schema(name: str) -> dict[str, Any] | None:
tool = TOOLS.get(name)
return tool.input_schema if tool else None
server = Server(
"Bookshop",
on_list_tools=list_tools,
on_call_tool=call_tool,
get_tool_input_schema=tool_input_schema,
)
app = server.streamable_http_app()
- 传入这个函数,就能直接用你手头已有的数据来回答。
- 对于没有需要检查内容的工具,返回
None。
回顾
- 给工具参数加上
x-mcp-header,2026-07-28客户端就会把它再作为Mcp-Param-*HTTP 请求头发送一遍。 - 请求头与请求体不一致的调用,服务器会拒绝。
- 只有
str、int和bool参数可以标记。遇到其他类型,MCPServer会抛出InvalidSignature。 - 底层
Server什么都不检查,而客户端会丢弃注解无效的工具。 get_tool_input_schema让底层Server不必在每次调用时都运行on_list_tools。
手写 Server API 的其余内容见 底层 Server。