MCP 2026-07-28 协议革新:Stateless 架构下的 AI Agent 工具调用实战

Agent 0 次阅读
MCP 2026-07-28 协议革新:Stateless 架构下的 AI Agent 工具调用实战

你的 AI Agent 还在为"每个工具都得写一套适配代码"而抓狂?MCP(Model Context Protocol)正从"有状态"升级为"无状态"——像 HTTP 一样简单、可路由、可缓存。本文带你从原理到代码,吃透 2026-年最大的 MCP 版本升级。


开篇:从一个真实业务场景说起

想象一下这个场景:你负责为公司搭建一个 AI 客服 Agent,它需要访问 CRM 系统查客户订单、调用内部 API 查物流状态、搜索公司知识库,还要能发送邮件通知。

如果用传统方式,你得做这些事:

  1. 为每个系统写一套 Function Calling 适配层
  2. 每个 LLM 平台的 Function Calling 格式不同,切换模型得重写
  3. 每个工具调用都有状态绑定,无法水平扩展
  4. 安全和鉴权全靠自己拼凑

更糟糕的是,当你接入 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 的工具调用主要有两种方式:

  1. Function Calling:OpenAI、Claude 等模型各自定义了私有格式的"函数调用"机制。问题是每家 API 不同,切换模型等于重写整套工具适配层。
  2. 自定义集成:开发者在 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 支持三种传输方式:

  1. stdio(标准输入输出):本地进程通信,Client 启动 Server 子进程,通过 stdin/stdout 交换 JSON-RPC 消息。适合开发调试、本地工具。
  2. SSE(Server-Sent Events):基于 HTTP 的单向推送 + POST 请求的双向通信。Server 通过 SSE 向 Client 推送事件,Client 通过 HTTP POST 发送请求。适合远程服务,但需要保持长连接。
  3. 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"
      }
    }
  }
}

变化要点

  1. initialize/initialized 握手被移除:协议版本、客户端信息、客户端能力不再通过握手交换,而是通过每个请求的 _meta 字段携带。
  2. Mcp-Session-Id 头被移除:不再有协议级别的会话。任何 Server 实例都可以处理任何请求。
  3. 新增 server/discover 方法:Client 可以在需要时获取 Server 的能力信息,而不是必须在连接时一次性完成。
  4. 新增 Mcp-MethodMcp-Name HTTP 头:让负载均衡器、网关、限流器可以不解析请求体就直接路由请求。
  5. 新增 ttlMscacheScope 缓存控制:类似 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_idbrowser_id,并在不同的步骤中灵活使用。

其他重大变化

除了去状态化,2026-07-28 规范还带来了:

  1. 扩展成为一等公民:扩展使用反向 DNS 作为 ID(如 io.modelcontextprotocol/apps),通过 extensions 映射在客户端/服务器能力中协商,独立于主规范版本迭代。
  2. MCP Apps:服务器可以渲染交互式 HTML 界面,在沙箱化的 iframe 中运行,通过 JSON-RPC 与 Host 通信。
  3. Tasks 扩展:将原来的实验性 Tasks 重设计为扩展,支持长时间运行的异步工作流。
  4. OAuth 2.1 / OpenID Connect 鉴权加固:服务器必须实现 OAuth 2.0 保护资源元数据(RFC 9728),客户端必须验证 iss 参数。
  5. 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 在这个模式下的调用流程

  1. 用户说"帮我创建一个购物车" → LLM 调用 create_cart("user_123") → 返回 "cart_a1b2c3d4"
  2. LLM 记住了这个 handle,继续调用 add_item(cart_id="cart_a1b2c3d4", item_name="手机", price=5999)
  3. 用户说"看看购物车里有什么" → 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 支持 ttlMscacheScope 缓存控制,但设置不当会导致工具列表过时。

系统提醒:如果 tools/listttlMs 设得太长(如 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。

必须实现

  1. 暴露 .well-known/oauth-protected-resource 端点(RFC 9728)
  2. 客户端实现 Resource Indicators(RFC 8707)
  3. 验证 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 传播(traceparenttracestatebaggage),这意味着可以端到端追踪一个 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 Google
协议层 模型 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 分钟。

关键发现

  1. P99 延迟大幅下降:原因是无状态模式下,任意请求可以落在任意实例上,不再有"热实例排队"问题。
  2. 水平扩展近乎线性:有状态模式下 3 实例只有 1.7 倍提升(共享会话存储成为瓶颈),无状态模式下达到 3 倍的线性扩展。
  3. 首次请求零开销:不再需要在首次调用前完成握手,对于短生命周期任务(如 Lambda 函数)尤其友好。

总结与未来展望

核心要点回顾

  1. MCP 是 AI Agent 的标准化工具协议,解决了 Function Calling 碎片化问题,是当前最主流的 Agent 工具集成标准。

  2. 2026-07-28 规范是 MCP 史上最大版本更新,核心变化是从有状态到无状态(Stateless),移除了握手和会话层,使 MCP Server 可以像普通 HTTP 服务一样水平扩展。

  3. 状态并未消失,只是变成了显式 Handle 模式——由服务器返回句柄,由模型推理并传递,反而比隐式会话更强大。

  4. Python SDK 的 FastMCP 提供了极简的开发体验,几十行代码即可创建一个功能完整的 MCP Server。

  5. 生产部署从"粘性会话 + 共享存储"简化为"普通轮询负载均衡",运维复杂度大幅降低。

未来趋势

  • MCP 将成为 AI Agent 的 HTTP——就像 HTTP 是 Web 的基础协议,MCP 将成为 Agent 工具层的标准协议
  • MCP Apps 生态将爆发——服务器渲染的交互式 UI 将重新定义"AI 能做什么"
  • 与 A2A 协议的互补——MCP 解决"工具层",A2A 解决"Agent 层",两者将共同构成 Agent 基础设施

最后期限提醒2026-07-28 最终规范将在 2026 年 7 月 28 日 正式发布,旧版规范有 12 个月的兼容期。建议立即开始迁移工作,尤其是移除会话依赖和更新鉴权实现。


延伸阅读

  1. MCP 官方规范modelcontextprotocol.io | 阅读完整的 2026-07-28 规范文档
  2. MCP Python SDK 文档py.sdk.modelcontextprotocol.io | 最新 SDK API 参考和 v2 开发版
  3. MCP Apps 深度解析 — 学习如何构建服务器渲染的交互式 UI,让 AI Agent 不再是"只输出文字"
  4. A2A(Agent-to-Agent)协议 — Google 推出的 Agent 互操作协议,与 MCP 互补使用
  5. MCP 安全最佳实践 — 阅读 Akamai 和 WorkOS 关于 MCP 威胁模型和鉴权加固的文章