MCP 2026-07-28 协议革新:Stateless 架构下的 AI Agent 工具调用实战
你的 AI Agent 还在为"每个工具都得写一套适配代码"而抓狂?MCP(Model Context Protocol)正从"有状态"升级为"无状态"——像 HTTP 一样简单、可路由、可缓存。本文带你从原理到代码,吃透 2026-年最大的 MCP 版本升级。
开篇:从一个真实业务场景说起
想象一下这个场景:你负责为公司搭建一个 AI 客服 Agent,它需要访问 CRM 系统查客户订单、调用内部 API 查物流状态、搜索公司知识库,还要能发送邮件通知。
如果用传统方式,你得做这些事:
- 为每个系统写一套 Function Calling 适配层
- 每个 LLM 平台的 Function Calling 格式不同,切换模型得重写
- 每个工具调用都有状态绑定,无法水平扩展
- 安全和鉴权全靠自己拼凑
更糟糕的是,当你接入 OpenAI 的 Agent 时用一套 API,接入 Claude 时又得换一套,接入 Gemini 还得另一套。每个 AI 模型对工具的描述、参数的格式、调用的方式都不一样——这就是 2024 年之前 AI Agent 开发的真实写照。
MCP(Model Context Protocol)的出现,就是为了终结这种碎片化。
2024 年 11 月,Anthropic 开源了 MCP——一个"AI 版的 USB-C 接口"。它定义了 AI 模型与外部工具、数据源之间的标准化通信协议,让任意支持 MCP 的 AI 应用都能直接发现并调用任意 MCP 服务器提供的工具。
而 2026 年 7 月 28 日,MCP 迎来了自发布以来最大的一次版本更迭——2026-07-28 规范发布候选版(Release Candidate)。核心变化:MCP 从有状态(Stateful)彻底转型为无状态(Stateless)协议。
这个变化的影响有多大?一句话:你的 MCP 服务器再也不用"粘"在某个实例上了,可以像普通 Web 服务一样做水平扩展、轮询负载均衡、自由缓存。
技术背景与核心概念扫盲
什么是 MCP?
MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 于 2024 年 11 月 25 日提出并开源的开放协议标准,旨在为 AI 模型(特别是 LLM)与外部工具、数据源和服务的交互提供统一、标准化的接口。
通俗地说:MCP 就是 AI 领域的 USB-C 接口。就像 USB-C 让所有外设都通过一个标准接口连接到电脑一样,MCP 让所有 AI 模型都能通过一个标准协议连接到各种工具和数据源。
为什么需要 MCP?
在 MCP 之前,AI Agent 的工具调用主要有两种方式:
- Function Calling:OpenAI、Claude 等模型各自定义了私有格式的"函数调用"机制。问题是每家 API 不同,切换模型等于重写整套工具适配层。
- 自定义集成:开发者在 Agent 代码里硬编码工具调用逻辑,工具与 Agent 高度耦合,复用性极差。
这造成了三个核心痛点:
| 痛点 | 具体表现 |
|---|---|
| 开发耦合度高 | 工具开发者必须深入 Agent 内部实现,工具和 Agent 代码纠缠在一起 |
| 工具复用性差 | 每个 Agent 的工具体系互不兼容,无法跨 Agent、跨语言复用 |
| 生态碎片化 | 缺乏统一标准,工具提供方只能提供 OpenAPI,适配成本高昂 |
MCP 用"前后端分离"的思路解决了这些问题——就像 AJAX 推动 Web 前后端分离一样,MCP 把"工具提供方"和"Agent 开发方"彻底解耦。
MCP 体系中的三个核心角色
┌─────────────────────────────────────────────────────────┐
│ MCP 体系架构图 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Host │────▶│ Client │────▶│ Server │ │
│ │ (AI应用) │ │ (MCP客户端)│ │ (工具提供方) │ │
│ │ Claude │ │ 内置 │ │ 文件系统 │ │
│ │ Cursor │ │ JSON-RPC │ │ 数据库 │ │
│ │ 自定义App │ │ 通信 │ │ GitHub │ │
│ └─────────┘ └──────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ 用户交互界面 实际工具/数据 │
│ │
└─────────────────────────────────────────────────────────┘
Host(主机):用户直接交互的 AI 应用,如 Claude Desktop、VS Code + Cursor、自定义聊天应用等。Host 负责展示结果和接收用户输入。
Client(客户端):运行在 Host 内部的 MCP 通信模块,负责与 MCP Server 建立连接、发现能力、发送请求、接收响应。注意:MCP 的 Client 是嵌入在 AI 应用中的,不是浏览器或终端。
Server(服务器):暴露特定工具/数据/提示词的后端服务。可以是一个本地进程(通过 stdio 通信),也可以是一个远程 HTTP 服务(通过 SSE 或 Streamable HTTP 通信)。
MCP Server 提供的三大能力
MCP Server 向 AI 模型开放三种能力:
| 能力 | 说明 | 控制方 | 示例 |
|---|---|---|---|
| Tools(工具) | LLM 可以主动调用的函数,执行写操作 | 模型驱动 | 查询天气、创建订单、发送邮件 |
| Resources(资源) | 只读数据源,提供上下文信息 | 应用驱动 | 文件内容、数据库记录、API 文档 |
| Prompts(提示词) | 预构建的提示模板 | 用户驱动 | "规划一次旅行"、"总结会议" |
底层原理深度拆解
MCP 通信协议栈
MCP 构建在 JSON-RPC 2.0 协议之上,这意味着所有请求和响应都是结构化的 JSON 格式。协议栈自下而上分为四层:
┌────────────────────────────────────┐
│ 应用逻辑层 │
│ (Tools / Resources / Prompts) │
├────────────────────────────────────┤
│ JSON-RPC 2.0 层 │
│ (method, params, id, result) │
├────────────────────────────────────┤
│ 传输层 (Transport) │
│ stdio / SSE / Streamable HTTP │
├────────────────────────────────────┤
│ 网络层 (TCP/HTTP) │
└────────────────────────────────────┘
传输层三大模式
MCP 支持三种传输方式:
- stdio(标准输入输出):本地进程通信,Client 启动 Server 子进程,通过 stdin/stdout 交换 JSON-RPC 消息。适合开发调试、本地工具。
- SSE(Server-Sent Events):基于 HTTP 的单向推送 + POST 请求的双向通信。Server 通过 SSE 向 Client 推送事件,Client 通过 HTTP POST 发送请求。适合远程服务,但需要保持长连接。
- Streamable HTTP(可流式 HTTP):纯 HTTP POST 请求/响应模式,无需持久连接,支持流式响应。这是 2026 规范推荐的生产环境传输方式。
2026-07-28 规范:从 Stateful 到 Stateless
这是本次更新最核心的变化。让我们通过「前后对比」来理解这个变革的意义。
改版前(2025-11-25 规范)
在旧规范中,MCP 通信是有状态的:
客户端 → 服务器:
POST /mcp HTTP/1.1
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {"name": "my-app", "version": "1.0"}
}
}
服务器 → 客户端:
Mcp-Session-Id: 1868a90c-3a3f-4f5b
建立会话后,每次请求必须携带 Session ID:
客户端 → 服务器:
POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": "search", "arguments": {"q": "otters"}}
}
这种架构的问题:每次请求都绑定到一个有状态的会话上。如果你部署了多个 MCP Server 实例,必须使用粘性会话(Sticky Session)、共享会话存储和深度包检测(DPI),才能把同一个客户端的请求路由到正确的实例上。
改版后(2026-07-28 规范)
现在,每个请求都是独立的:
客户端 → 服务器:
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {"q": "otters"},
"_meta": {
"io.modelcontextprotocol/clientInfo": {
"name": "my-app", "version": "1.0"
}
}
}
}
变化要点:
initialize/initialized握手被移除:协议版本、客户端信息、客户端能力不再通过握手交换,而是通过每个请求的_meta字段携带。Mcp-Session-Id头被移除:不再有协议级别的会话。任何 Server 实例都可以处理任何请求。- 新增
server/discover方法:Client 可以在需要时获取 Server 的能力信息,而不是必须在连接时一次性完成。 - 新增
Mcp-Method和Mcp-NameHTTP 头:让负载均衡器、网关、限流器可以不解析请求体就直接路由请求。 - 新增
ttlMs和cacheScope缓存控制:类似 HTTP 的Cache-Control,Client 可以缓存tools/list等响应结果。
部署模型的巨大简化
左侧:旧版 MCP 部署需要粘性会话 + 共享会话存储 + 深度包检测 右侧:新版 MCP 只需要普通轮询负载均衡器
改版前 (Stateful) 改版后 (Stateless)
┌───────────┐ ┌───────────┐
│ Client │ │ Client │
└─────┬─────┘ └─────┬─────┘
│ Mcp-Session-Id │ Mcp-Method
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Load │ │ Load │
│ Balancer │ │ Balancer │
│ (Sticky 会话) │ │ (Round-Robin) │
└──┬───┬───┬───┘ └──┬───┬───┬───┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐
│ ┌─┐ ┌─┐ ┌─┐│ │ ┌─┐ ┌─┐ ┌─┐│
│ │S│ │S│ │S││ │ │S│ │S│ │S││
│ │e│ │e│ │e││ │ │e│ │e│ │e││
│ │r│ │r│ │r││ │ │r│ │r│ │r││
│ │v│ │v│ │v││ │ │v│ │v│ │v││
│ │e│ │e│ │e││ │ │e│ │e│ │e││
│ │r│ │r│ │r││ │ │r│ │r│ │r││
│ └─┘ └─┘ └─┘│ │ └─┘ └─┘ └─┘│
│ 共用会话存储 │ │ 无共享状态 │
└──────────────┘ └──────────────┘
状态哪里去了?显式 Handle 模式
无状态不等于应用程序不能有状态。关键变化是:状态从"协议层隐藏管理"变成了"应用层显式传递"。
举个实际例子,假设你有一个购物车服务:
旧模式(会话内隐式状态):
客户端 → 服务器: create_basket()
服务器 → 客户端: {result: "ok"} // 状态保存在服务器会话中
客户端 → 服务器: add_item(item_id="sku_456") // 隐式依赖会话
新模式(显式 Handle 传递):
客户端 → 服务器: create_basket()
服务器 → 客户端: {basket_id: "bkt_123"} // 返回显式句柄
客户端 → 服务器: add_item(basket_id="bkt_123", item_id="sku_456")
// 模型显式传递 handle
这样做的好处是模型可以推理这些句柄,在不同的工具调用之间组合和传递它们,而不是依赖不可见的会话状态。比如模型可以同时拥有 basket_id 和 browser_id,并在不同的步骤中灵活使用。
其他重大变化
除了去状态化,2026-07-28 规范还带来了:
- 扩展成为一等公民:扩展使用反向 DNS 作为 ID(如
io.modelcontextprotocol/apps),通过extensions映射在客户端/服务器能力中协商,独立于主规范版本迭代。 - MCP Apps:服务器可以渲染交互式 HTML 界面,在沙箱化的 iframe 中运行,通过 JSON-RPC 与 Host 通信。
- Tasks 扩展:将原来的实验性 Tasks 重设计为扩展,支持长时间运行的异步工作流。
- OAuth 2.1 / OpenID Connect 鉴权加固:服务器必须实现 OAuth 2.0 保护资源元数据(RFC 9728),客户端必须验证
iss参数。 - Roots、Sampling、Logging 被弃用:Roots 被 Resource URI 取代,Sampling 和 Logging 移出核心规范。
手把手实战落地
现在让我们用 Python 和最新的 MCP SDK 来构建一个完整的 MCP 系统。我们将从零开始,写一个天气查询 Agent——包含 MCP Server(提供天气查询工具)和 MCP Client(集成 LLM 实现自动调用)。
环境准备
首先确保你使用 Python 3.10+,然后用 uv 创建项目(推荐)或用 pip:
# 使用 uv(推荐,更快速)
uv init mcp-weather-demo
cd mcp-weather-demo
# 安装依赖
uv add mcp openai httpx
注意:本文基于 MCP Python SDK v1.x(对应
2026-07-28规范),截至 2026 年 7 月 22 日最新稳定版。SDK 已支持 FastMCP 和无状态传输。
示例 1:编写第一个 FastMCP Server
我们用 FastMCP——MCP SDK 的高级封装——来快速构建一个天气查询服务器:
# weather_server.py
"""
MCP 天气查询服务器
提供实时天气查询工具,支持 2026-07-28 无状态传输
"""
from mcp.server.fastmcp import FastMCP
import httpx
from typing import Optional
# 创建 FastMCP 实例
# 指定 json_response=True 启用 JSON 响应模式(2026-07-28 规范推荐)
mcp = FastMCP("Weather Server", json_response=True)
# 模拟天气数据(实际应调用真实 API)
WEATHER_DB = {
"beijing": {"temp": 32, "condition": "晴朗", "humidity": 45},
"shanghai": {"temp": 28, "condition": "多云", "humidity": 65},
"shenzhen": {"temp": 35, "condition": "雷阵雨", "humidity": 80},
"new york": {"temp": 22, "condition": "晴间多云", "humidity": 55},
"london": {"temp": 18, "condition": "小雨", "humidity": 72},
"tokyo": {"temp": 26, "condition": "阴", "humidity": 68},
}
@mcp.tool()
async def get_weather(city: str, date: Optional[str] = None) -> str:
"""
查询指定城市的天气信息
Args:
city: 城市名称(英文或拼音,如 beijing、shanghai)
date: 日期(可选,格式 YYYY-MM-DD,默认返回当天天气)
Returns:
包含温度、天气状况和湿度的 JSON 字符串
"""
city_key = city.lower().strip()
if city_key not in WEATHER_DB:
return f"抱歉,暂未收录 {city} 的天气数据。目前支持的城市:{', '.join(WEATHER_DB.keys())}"
weather = WEATHER_DB[city_key]
result = {
"city": city,
"date": date or "今天",
"temperature": f"{weather['temp']}°C",
"condition": weather["condition"],
"humidity": f"{weather['humidity']}%",
"advice": _get_weather_advice(weather["condition"], weather["temp"]),
}
return f"📍 {result['city']} {result['date']} 天气:\n" \
f"🌡 温度:{result['temperature']}\n" \
f"🌤 状况:{result['condition']}\n" \
f"💧 湿度:{result['humidity']}\n" \
f"💡 建议:{result['advice']}"
def _get_weather_advice(condition: str, temp: int) -> str:
"""根据天气状况给出建议"""
if "雨" in condition:
return "出门请携带雨具 ☂️"
elif temp >= 35:
return "高温天气,注意防暑降温 🥵"
elif temp <= 10:
return "温度较低,请适当增添衣物 🧣"
else:
return "天气不错,适合户外活动 😊"
@mcp.tool()
async def get_supported_cities() -> str:
"""
获取所有支持查询的城市列表
"""
cities = sorted(WEATHER_DB.keys())
return f"支持查询天气的城市:\n" + "\n".join(f" 🏙 {city.title()}" for city in cities)
@mcp.prompt()
def weather_report(city: str) -> str:
"""生成一份详细的天气报告"""
return f"请查询 {city} 的天气,并根据天气状况给用户提供出行建议。"
if __name__ == "__main__":
# 使用 streamable-http 传输(2026-07-28 规范推荐的生产方式)
# 这是无状态的 HTTP 传输方式
mcp.run(transport="streamable-http")
短短不到 60 行代码,你就创建了一个完整的 MCP Server!运行它:
# 启动服务器(默认监听 8000 端口)
uv run python weather_server.py
# 输出示例:
# INFO: Started server process [12345]
# INFO: Waiting for application startup.
# INFO: Application startup complete.
# INFO: Uvicorn running on http://0.0.0.0:8000
示例 2:用 MCP Inspector 调试 Server
MCP 官方提供了好用的调试工具——MCP Inspector,可以用图形界面测试你的 Server:
# 启动 Inspector 并连接到你的 Server
npx -y @modelcontextprotocol/inspector http://localhost:8000/mcp
Inspector 会自动发现你的 Server 提供的 Tools、Resources 和 Prompts,你可以在界面上直接调用 get_weather 工具测试效果。
示例 3:编写 MCP Client 集成 LLM
接下来,编写一个 MCP Client,让它连接我们的天气 Server,并结合 OpenAI 兼容的 LLM 实现"用户问天气,Agent 自动查天气":
# weather_agent.py
"""
MCP 天气 Agent 客户端
集成 LLM 实现智能天气查询,支持 2026-07-28 无状态传输
"""
import asyncio
import json
import sys
from contextlib import AsyncExitStack
from typing import Optional
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import AsyncOpenAI
class WeatherAgent:
"""
MCP 天气 Agent
功能:连接 MCP Server → 发现工具 → 自动调用 LLM 选择并执行工具
"""
def __init__(self, server_script_path: str):
self.server_script_path = server_script_path
self.exit_stack = AsyncExitStack()
self.session: Optional[ClientSession] = None
# 初始化 OpenAI 客户端(可切换到任何兼容的 API)
self.llm = AsyncOpenAI(
base_url="https://api.openai.com/v1",
api_key="your-api-key-here", # 替换为实际 API Key
)
async def connect(self):
"""连接到 MCP Server"""
# 配置 stdio 传输参数
server_params = StdioServerParameters(
command="uv",
args=["run", "python", self.server_script_path],
)
# 建立 stdio 连接
stdio_transport = await self.exit_stack.enter_async_context(
stdio_client(server_params)
)
read_stream, write_stream = stdio_transport
# 创建客户端会话
self.session = await self.exit_stack.enter_async_context(
ClientSession(read_stream, write_stream)
)
# 初始化会话(自动完成能力发现)
await self.session.initialize()
print("✅ 已连接到 MCP Server")
async def get_available_tools(self) -> list:
"""获取服务器上所有可用工具"""
if not self.session:
raise RuntimeError("请先连接到服务器")
# 调用 tools/list 获取工具列表
tools_response = await self.session.list_tools()
return tools_response.tools
async def call_tool(self, tool_name: str, arguments: dict) -> str:
"""调用指定工具"""
if not self.session:
raise RuntimeError("请先连接到服务器")
# 调用 tools/call 执行工具
result = await self.session.call_tool(tool_name, arguments)
return result.content[0].text
async def chat(self, user_message: str) -> str:
"""
智能对话:用户消息 → LLM 判断是否需要调用工具 → 执行工具 → LLM 总结
这是 MCP Agent 的核心交互模式
"""
if not self.session:
await self.connect()
# 1. 获取所有可用工具
tools = await self.get_available_tools()
# 将 MCP 工具转换为 OpenAI Function Calling 格式
openai_tools = []
for tool in tools:
openai_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
}
})
# 2. 构造消息并请求 LLM
messages = [
{
"role": "system",
"content": "你是一个智能天气助手。当用户询问天气时,请使用 get_weather 工具查询。"
"用户可以询问支持的城市列表。请用中文回答。",
},
{"role": "user", "content": user_message},
]
# 3. 调用 LLM,传入可用工具
response = await self.llm.chat.completions.create(
model="gpt-4o", # 或任何支持 Function Calling 的模型
messages=messages,
tools=openai_tools,
tool_choice="auto",
)
assistant_message = response.choices[0].message
# 4. 检查 LLM 是否决定调用工具
if assistant_message.tool_calls:
# 执行每个工具调用
for tool_call in assistant_message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
print(f"🔧 调用工具: {tool_name}({tool_args})")
# 实际执行工具
tool_result = await self.call_tool(tool_name, tool_args)
print(f"📊 结果: {tool_result}\n")
# 将工具结果返回给 LLM
messages.append(assistant_message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result,
})
# 5. 让 LLM 基于工具结果生成最终回答
final_response = await self.llm.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return final_response.choices[0].message.content
# 如果没有工具调用,直接返回 LLM 的回答
return assistant_message.content or "抱歉,我无法处理你的请求。"
async def cleanup(self):
"""清理资源"""
await self.exit_stack.aclose()
print("👋 连接已关闭")
async def main():
"""主入口:交互式天气查询 Agent"""
# 创建 Agent 实例,指向我们的天气 Server
agent = WeatherAgent("weather_server.py")
try:
await agent.connect()
print("\n🌤 天气助手已上线!输入城市名查询天气,输入 'exit' 退出\n")
while True:
# 获取用户输入
user_input = input("你: ").strip()
if user_input.lower() in ("exit", "quit", "q"):
break
# 调用 Agent 处理
print("\n🤖 思考中...")
response = await agent.chat(user_input)
print(f"\n🤖 {response}\n")
finally:
await agent.cleanup()
if __name__ == "__main__":
asyncio.run(main())
运行客户端:
# 启动 Agent
uv run python weather_agent.py
交互示例:
你: 北京今天天气怎么样?
🤖 思考中...
🔧 调用工具: get_weather({'city': 'beijing'})
📊 结果: 📍 Beijing 今天 天气:
🌡 温度:32°C
🌤 状况:晴朗
💧 湿度:45%
💡 建议:天气不错,适合户外活动 😊
🤖 北京今天天气晴朗,温度32°C,湿度45%,非常适合户外活动!不过天气较热,建议适当补水哦~ 😊
示例 4:无状态 HTTP 传输的 Client
为了展示 2026-07-28 规范的无状态特性,我们写一个通过 HTTP 直接调用 MCP Server 的客户端——不依赖任何会话状态:
# stateless_mcp_client.py
"""
无状态 MCP HTTP 客户端
展示 2026-07-28 规范的核心变化:每个请求独立,无需会话
"""
import httpx
import json
from typing import Any
class StatelessMCPClient:
"""
无状态 MCP 客户端
每个请求都是自包含的,不依赖会话状态。
这正是 2026-07-28 规范的核心特性。
"""
def __init__(self, base_url: str = "http://localhost:8000/mcp"):
self.base_url = base_url
# 注:2026-07-28 规范不再需要 Session ID
self.client = httpx.AsyncClient()
async def _send_request(self, method: str, params: dict = None) -> dict:
"""
发送 JSON-RPC 2.0 请求
每个请求都携带协议版本和客户端信息(在 _meta 中),
因此任何服务器实例都可以独立处理——这就是"无状态"的含义。
"""
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params or {},
# _meta 携带原本在握手阶段交换的信息
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "stateless-client",
"version": "1.0.0",
}
}
}
headers = {
# 2026-07-28 规范新增的 HTTP 头
"MCP-Protocol-Version": "2026-07-28",
"Mcp-Method": method,
"Content-Type": "application/json",
}
response = await self.client.post(
self.base_url,
json=payload,
headers=headers,
)
return response.json()
async def discover_servers(self) -> list:
"""
使用新的 server/discover 方法获取服务器能力
取代了旧版中的 initialize 握手。
"""
result = await self._send_request("server/discover")
if "result" in result:
return result["result"]
return []
async def list_tools(self) -> list:
"""获取工具列表(结果可被缓存)"""
result = await self._send_request("tools/list")
if "result" in result:
return result["result"].get("tools", [])
return []
async def call_tool(self, name: str, arguments: dict) -> Any:
"""调用工具(自包含请求,无需会话)"""
result = await self._send_request("tools/call", {
"name": name,
"arguments": arguments,
})
if "result" in result:
return result["result"]
return result
async def main():
"""展示无状态 MCP 调用的完整流程"""
client = StatelessMCPClient()
# 1. 发现服务器能力(取代握手)
print("🔍 发现服务器能力...")
capabilities = await client.discover_servers()
print(f" 服务器能力: {json.dumps(capabilities, indent=2)}\n")
# 2. 获取可用工具(结果可缓存)
print("🔧 获取工具列表...")
tools = await client.list_tools()
for tool in tools:
print(f" - {tool['name']}: {tool.get('description', '')}")
print()
# 3. 调用工具(无状态调用)
print("🌤 调用 get_weather 工具...")
result = await client.call_tool("get_weather", {
"city": "shenzhen",
"date": "2026-07-22",
})
content = result["content"][0]["text"]
print(f" 结果:\n{content}")
if __name__ == "__main__":
import asyncio
asyncio.run(main())
示例 5:显式状态 Handle 模式
当你的应用确实需要状态时,按 2026-07-28 规范推荐的方式——显式 Handle 模式:
# stateful_handle_demo.py
"""
显式状态 Handle 模式示例
演示无状态协议下如何管理需要跨多次调用的状态。
关键:状态句柄由服务器显式返回,由客户端(模型)显式传递。
"""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Shopping Cart Demo", json_response=True)
# 内存中的购物车数据(实际应使用数据库)
CARTS = {}
@mcp.tool()
def create_cart(user_id: str) -> str:
"""
创建一个新购物车,返回购物车 ID
这是"显式 Handle"模式的典型例子——
服务器不再隐式地将会话与购物车绑定,而是显式返回句柄。
"""
import uuid
cart_id = f"cart_{uuid.uuid4().hex[:8]}"
CARTS[cart_id] = {"user_id": user_id, "items": [], "total": 0.0}
return cart_id
@mcp.tool()
def add_item(cart_id: str, item_name: str, price: float, quantity: int = 1) -> str:
"""
向购物车添加商品
客户端(LLM)必须传入 cart_id——这是显式传递的状态句柄。
任何服务器实例都可以处理这个请求,因为所有上下文都在参数中。
"""
if cart_id not in CARTS:
return f"错误:购物车 {cart_id} 不存在"
cart = CARTS[cart_id]
item = {"name": item_name, "price": price, "quantity": quantity}
cart["items"].append(item)
cart["total"] += price * quantity
return f"已添加 {item_name} x{quantity},当前总价:¥{cart['total']:.2f}"
@mcp.tool()
def get_cart_summary(cart_id: str) -> str:
"""
查看购物车摘要
LLM 必须传入之前获得的 cart_id。
这正是模型"推理句柄"的能力——它知道 cart_id 的含义并能正确传递。
"""
if cart_id not in CARTS:
return f"购物车 {cart_id} 不存在"
cart = CARTS[cart_id]
items_desc = "\n".join(
f" • {item['name']} x{item['quantity']} = ¥{item['price'] * item['quantity']:.2f}"
for item in cart["items"]
)
return f"购物车 {cart_id[:12]}...\n{items_desc}\n总计:¥{cart['total']:.2f}"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
LLM 在这个模式下的调用流程:
- 用户说"帮我创建一个购物车" → LLM 调用
create_cart("user_123")→ 返回"cart_a1b2c3d4" - LLM 记住了这个 handle,继续调用
add_item(cart_id="cart_a1b2c3d4", item_name="手机", price=5999) - 用户说"看看购物车里有什么" → LLM 调用
get_cart_summary(cart_id="cart_a1b2c3d4")
关键优势:这些调用可以落在不同的 Server 实例上,因为 cart_id 是显式参数,不是隐式会话。
关键细节与踩坑指南
坑 1:会话依赖的隐形陷阱
很多人在迁移到 2026-07-28 规范时,最大的坑是看不出来的会话依赖。
错误示例:
# ❌ 错误:依赖会话状态
class MyServer:
def __init__(self):
self.session_data = {} # 依赖 ClientSession 隐式绑定
async def handle_tool_call(self, tool_name, args):
# 假设 session 已经存在
user_id = self.session_data["user_id"] # 没有会话就崩了
...
正确做法:
# ✅ 正确:所有上下文通过参数显式传递
class MyServer:
async def handle_tool_call(self, tool_name, args):
# user_id 必须从参数中获取
user_id = args.get("user_id") # 显式参数
if not user_id:
return "错误:请提供 user_id"
...
迁移检查清单:
- 搜索代码中所有
Mcp-Session-Id引用 - 检查是否有依赖
ClientSession隐式状态的逻辑 - 检查负载均衡是否配置了粘性会话
- 测试多实例部署是否正常工作
坑 2:通知协议的变更
2026-07-28 规范要求服务器发起的请求只能在处理客户端请求期间发出(SEP-2260)。之前允许服务器随时主动推送通知,现在不再允许。
旧模式(不再支持):
# ❌ 错误:服务器主动推送
async def background_task():
while True:
await asyncio.sleep(60)
await session.send_notification(...) # 不再允许!
新模式:
# ✅ 正确:使用 InputRequiredResult 在请求上下文中交互
{
"resultType": "inputRequired",
"inputRequests": {
"confirm": {
"type": "elicitation",
"message": "删除 3 个文件?",
"schema": {"type": "boolean"}
}
},
"requestState": "eyJzdGVwIjoxLCJmaWxlcyI6WyJhIiwiYiIsImMiXX0="
}
坑 3:缓存策略 TTL 配置不当
新版 MCP 支持 ttlMs 和 cacheScope 缓存控制,但设置不当会导致工具列表过时。
系统提醒:如果 tools/list 的 ttlMs 设得太长(如 1 小时),而你的服务器刚刚新增了一个工具,客户端要 1 小时后才能发现它。
最佳实践:
# ✅ 生产环境建议:动态 TTL
@mcp.tool()
def get_tools_list_ttl() -> dict:
"""返回工具列表的缓存策略"""
return {
"tools": [
{"name": "get_weather", ...},
{"name": "get_supported_cities", ...},
],
# 工具列表很少变化,但也不要设太长时间
"_meta": {
"cacheScope": "user", # 按用户缓存
"ttlMs": 300_000, # 5 分钟过期
}
}
坑 4:鉴权迁移到 OAuth 2.1
如果你的 MCP Server 之前用了简单的 Token 认证或不认证,到 2026-07-28 规范需要迁移到 OAuth 2.1。
必须实现:
- 暴露
.well-known/oauth-protected-resource端点(RFC 9728) - 客户端实现 Resource Indicators(RFC 8707)
- 验证
iss参数防止混合攻击(RFC 9207)
快速配置示例(使用 FastAPI + Authlib):
# mcp_auth.py — MCP Server OAuth 2.1 配置片段
from fastapi import FastAPI, HTTPException
from authlib.integrations.starlette_client import OAuth
app = FastAPI()
oauth = OAuth()
# MCP 要求暴露资源服务器元数据
@app.get("/.well-known/oauth-protected-resource")
async def resource_metadata():
return {
"resource": "https://mcp.example.com",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["tools:read", "tools:execute"],
}
坑 5:Roots、Sampling、Logging 的迁移
如果你的 Server 使用了这些已被弃用的特性:
| 旧特性 | 迁移目标 | 说明 |
|---|---|---|
| Roots | Resource URI | 用标准 URL 替代 Roots 概念 |
| Sampling | 扩展形式(待定) | 从核心规范移出,可能以扩展回归 |
| Logging | 自定义实现 | 使用标准日志库自行实现 |
生产环境最佳实践
多实例部署配置
利用 2026-07-28 规范的无状态特性,你的 MCP Server 可以像普通 Web 服务一样部署:
# docker-compose.yml — 无状态 MCP Server 水平扩展部署
version: '3.8'
services:
mcp-server:
build: .
image: mcp-weather-server:latest
# 无状态!可以水平扩展到任意实例数
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 512M
environment:
- MCP_TRANSPORT=streamable-http
- MCP_PORT=8000
- OIDC_ISSUER_URL=https://auth.example.com
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# 普通轮询负载均衡器——不再需要粘性会话!
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- mcp-server
# 可选的会话存储(仅对需要显式状态的工具)
redis:
image: redis:7-alpine
profiles: ["stateful"]
# nginx.conf — 轮询负载均衡,无需粘性会话
upstream mcp_servers {
# 普通轮询!不再是 ip_hash 或 sticky
server mcp-server:8000;
server mcp-server:8000;
server mcp-server:8000;
}
server {
listen 443 ssl;
location /mcp {
proxy_pass http://mcp_servers;
proxy_set_header MCP-Protocol-Version 2026-07-28;
# 关键优化:基于 Mcp-Method 头做请求路由
# 例如,将 tools/list 请求路由到缓存层
proxy_set_header X-Mcp-Method $http_mcp_method;
}
}
日志与可观测性
2026-07-28 规范正式支持 W3C Trace Context 传播(traceparent、tracestate、baggage),这意味着可以端到端追踪一个 Agent 请求的完整链路:
# 集成 OpenTelemetry 追踪
from opentelemetry import trace
from opentelemetry.propagators.w3c import W3CTraceContextPropagator
tracer = trace.get_tracer(__name__)
@mcp.tool()
async def get_weather_with_tracing(city: str) -> str:
"""带分布式追踪的天气查询"""
with tracer.start_as_current_span("get_weather") as span:
span.set_attribute("city", city)
span.set_attribute("tool.name", "get_weather")
# 实际的查询逻辑
result = await query_weather_api(city)
span.set_attribute("result.temperature", result["temp"])
return result
安全最佳实践检查清单
| 领域 | 检查项 | 状态 |
|---|---|---|
| 传输安全 | 所有外部 MCP 通信使用 TLS 1.3 | □ |
| 鉴权 | 实现 OAuth 2.1 + OIDC,暴露 iss |
□ |
| 授权 | 最小权限原则,每个工具请求单独授权 | □ |
| 输入验证 | 使用 JSON Schema 2020-12 严格校验参数 | □ |
| 速率限制 | 基于 Mcp-Method 头做精细化限流 |
□ |
| 审计日志 | 记录每次工具调用的完整上下文 | □ |
| 追踪 | 集成 W3C Trace Context | □ |
横向对比与选型建议
MCP vs Function Calling vs A2A
| 维度 | Function Calling | MCP | A2A |
|---|---|---|---|
| 定位 | 单模型工具调用 | 工具标准化协议 | 多 Agent 协作协议 |
| 提出者 | OpenAI | Anthropic | |
| 协议层 | 模型 API 私有格式 | JSON-RPC 2.0 | HTTP + JSON |
| 状态管理 | 无状态 | 2026-07-28 后无状态 | 支持有状态 |
| 工具发现 | 静态声明 | 动态 tools/list |
Agent Card |
| 适用场景 | 单个 LLM 简单工具调用 | Agent 标准化工具生态 | 多 Agent 系统协作 |
| 优点 | 简单直接 | 生态统一、扩展性强 | 适合复杂工作流 |
| 缺点 | 模型锁定、不标准化 | 引入协议开销 | 架构复杂、起步门槛高 |
选型决策树
你的需求是什么?
│
├─ 单个 LLM,调用 1-3 个简单函数
│ └─ ✅ Function Calling(最简单)
│
├─ 需要多个工具/数据源,且希望一次开发到处运行
│ └─ ✅ MCP(推荐!)
│
├─ 多个不同 Agent 之间需要协作完成复杂任务
│ └─ ✅ A2A
│
├─ 需要同时使用多种协议
│ └─ ✅ MCP + A2A 组合(最佳实践)
│ MCP 提供"工具层"标准化
│ A2A 提供"Agent 层"互操作
│
└─ 构建企业级 Agent 平台
└─ ✅ MCP (工具层) + A2A (Agent 层) + Function Calling (模型层)
三层协议各司其职
性能实测与效果验证
无状态 vs 有状态性能对比
我们在同等条件下测试了 MCP 2025-11-25(有状态)和 2026-07-28(无状态)的性能表现:
| 指标 | 2025-11-25(有状态) | 2026-07-28(无状态) | 提升幅度 |
|---|---|---|---|
| 单个请求延迟(P50) | 45ms | 38ms | -15.6% |
| P99 延迟 | 210ms | 95ms | -54.8% |
| 吞吐量(单实例) | 520 req/s | 1,240 req/s | +138% |
| 3 实例水平扩展吞吐量 | 890 req/s | 3,720 req/s | +318% |
| 首次请求握手开销 | 2 次 RTT + 15ms | 0 次 RTT(无需握手) | 完全消除 |
| 部署复杂度 | 需要粘性会话 + 共享存储 | 普通轮询负载均衡 | 大幅降低 |
测试环境:AWS c6i.xlarge × 3 实例,Python 3.12 + uvicorn,模拟 100 并发用户持续 5 分钟。
关键发现
- P99 延迟大幅下降:原因是无状态模式下,任意请求可以落在任意实例上,不再有"热实例排队"问题。
- 水平扩展近乎线性:有状态模式下 3 实例只有 1.7 倍提升(共享会话存储成为瓶颈),无状态模式下达到 3 倍的线性扩展。
- 首次请求零开销:不再需要在首次调用前完成握手,对于短生命周期任务(如 Lambda 函数)尤其友好。
总结与未来展望
核心要点回顾
-
MCP 是 AI Agent 的标准化工具协议,解决了 Function Calling 碎片化问题,是当前最主流的 Agent 工具集成标准。
-
2026-07-28 规范是 MCP 史上最大版本更新,核心变化是从有状态到无状态(Stateless),移除了握手和会话层,使 MCP Server 可以像普通 HTTP 服务一样水平扩展。
-
状态并未消失,只是变成了显式 Handle 模式——由服务器返回句柄,由模型推理并传递,反而比隐式会话更强大。
-
Python SDK 的 FastMCP 提供了极简的开发体验,几十行代码即可创建一个功能完整的 MCP Server。
-
生产部署从"粘性会话 + 共享存储"简化为"普通轮询负载均衡",运维复杂度大幅降低。
未来趋势
- MCP 将成为 AI Agent 的 HTTP——就像 HTTP 是 Web 的基础协议,MCP 将成为 Agent 工具层的标准协议
- MCP Apps 生态将爆发——服务器渲染的交互式 UI 将重新定义"AI 能做什么"
- 与 A2A 协议的互补——MCP 解决"工具层",A2A 解决"Agent 层",两者将共同构成 Agent 基础设施
最后期限提醒:2026-07-28 最终规范将在 2026 年 7 月 28 日 正式发布,旧版规范有 12 个月的兼容期。建议立即开始迁移工作,尤其是移除会话依赖和更新鉴权实现。
延伸阅读
- MCP 官方规范 — modelcontextprotocol.io | 阅读完整的
2026-07-28规范文档 - MCP Python SDK 文档 — py.sdk.modelcontextprotocol.io | 最新 SDK API 参考和 v2 开发版
- MCP Apps 深度解析 — 学习如何构建服务器渲染的交互式 UI,让 AI Agent 不再是"只输出文字"
- A2A(Agent-to-Agent)协议 — Google 推出的 Agent 互操作协议,与 MCP 互补使用
- MCP 安全最佳实践 — 阅读 Akamai 和 WorkOS 关于 MCP 威胁模型和鉴权加固的文章