第 11 章:MCP 协议完全指南¶
Model Context Protocol (MCP) 是 AI Agent 工具连接的事实标准,相当于 AI 领域的 USB-C。它由 Anthropic 于 2024 年底开源,已被 OpenAI、Google 等主流厂商支持。
📌 2026-07 重大更新:MCP 核心协议转为完全无状态,移除了
Mcp-Session-Id,并已捐献给 Linux Foundation。Server 部署更简单,天然支持 Serverless 和水平扩展。
11.1 什么是 MCP?¶
核心概念¶
┌─────────────────────────────────────────────┐
│ MCP 三角架构 │
├─────────────────────────────────────────────┤
│ │
│ Host (宿主应用,如 Claude Desktop) │
│ ↓ 调用 │
│ Client (协议客户端) │
│ ↓ 连接 │
│ Server (工具提供方) │
│ │
└─────────────────────────────────────────────┘
三大原语¶
| 原语 | 作用 | 类比 |
|---|---|---|
| Tools | 可执行的操作 | 函数 |
| Resources | 数据读取 | 文件/数据库 |
| Prompts | 模板管理 | 提示词模板 |
11.2 环境准备¶
⚠️ 注意:
mcp包 0.x 与 1.x 的 API 差异较大,本章基于 1.x 编写(FastMCP 风格)。
11.3 编写第一个 MCP Server¶
Python 实现(FastMCP,推荐)¶
用 Python 写 MCP Server 请使用官方推荐的 FastMCP 高层封装,它自动处理协议细节、类型推导和传输层:
# my_mcp_server.py
from mcp.server.fastmcp import FastMCP
# 创建 FastMCP 实例(server 名)
mcp = FastMCP("my-mcp-server")
@mcp.tool()
def get_weather(city: str) -> str:
"""查询城市天气"""
# 实际场景可在此调用天气 API
return f"{city} 当前天气:晴,25°C"
@mcp.tool()
def calculate(expression: str) -> str:
"""安全计算器:仅支持四则运算,禁止任意代码执行"""
import ast
import operator
ops = {
ast.Add: operator.add, ast.Sub: operator.sub,
ast.Mult: operator.mul, ast.Div: operator.truediv,
}
def eval_node(node):
if isinstance(node, ast.Expression):
return eval_node(node.body)
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in ops:
return ops[type(node.op)](eval_node(node.left), eval_node(node.right))
raise ValueError(f"不支持的表达式: {type(node).__name__}")
try:
result = eval_node(ast.parse(expression, mode="eval"))
return str(result)
except Exception as e:
return f"计算错误: {e}"
if __name__ == "__main__":
# 默认通过 stdio 传输运行(供 Claude Desktop / 其他 MCP Client 调用)
mcp.run()
⚠️ 安全警示:永远不要在工具里直接
eval()用户输入——这是任意代码执行漏洞。上面示例用ast模块白名单方式只允许四则运算,才是安全写法。
配置 Claude Desktop¶
11.4 常用工具类型¶
11.4.1 数据库工具¶
from mcp.server.fastmcp import FastMCP
import sqlite3
mcp = FastMCP("db-server")
@mcp.tool()
def query_database(query: str) -> str:
"""执行只读 SQL 查询"""
conn = sqlite3.connect("database.db")
try:
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
return "\n".join(str(r) for r in results)
finally:
conn.close()
11.4.2 文件系统工具¶
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("fs-server")
@mcp.tool()
def read_file(path: str) -> str:
"""读取文件内容"""
with open(path, 'r', encoding='utf-8') as f:
return f.read()
@mcp.tool()
def write_file(path: str, content: str) -> str:
"""写入文件"""
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
return f"已写入 {len(content)} 字符到 {path}"
11.4.3 HTTP API 工具¶
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("http-server")
@mcp.tool()
async def fetch_url(url: str) -> str:
"""获取网页内容(最大超时 10 秒)"""
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(url)
return response.text
同步函数用
def,异步函数用async def,FastMCP 两者都支持,会自动生成正确的 JSON Schema。
11.5 作为 MCP Client 使用¶
import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client
async def use_mcp_server():
async with stdio_client(["python", "my_server.py"]) as (read, write):
async with ClientSession(read, write) as session:
# 列出可用工具
tools = await session.list_tools()
print("可用工具:", [t.name for t in tools.tools])
# 调用工具
result = await session.call_tool(
"get_weather",
{"city": "北京"}
)
print(result.content)
if __name__ == "__main__":
asyncio.run(use_mcp_server())
11.6 与主流框架集成¶
LangChain + MCP¶
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from mcp import ClientSession
from mcp.client.stdio import stdio_client
async def run_agent():
async with stdio_client(["python", "my_server.py"]) as (read, write):
async with ClientSession(read, write) as session:
tools = await load_mcp_tools(session)
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(llm, tools)
result = await agent.ainvoke(
{"messages": [("user", "北京今天天气怎么样?")]}
)
print(result["messages"][-1].content)
import asyncio
asyncio.run(run_agent())
CrewAI + MCP¶
from crewai import Agent, LLM
from crewai_tools import MCPTool
mcp_tool = MCPTool(
server_name="my-server",
tool_name="get_weather"
)
agent = Agent(
role="天气助手",
goal="准确回答用户的天气查询",
backstory="你是一名专业气象分析师",
tools=[mcp_tool],
llm=LLM(model="gpt-4o-mini")
)
11.7 无状态核心:2026-07 重大更新¶
11.7.1 从有状态到无状态¶
2026 年 7 月,MCP 协议核心经历了一次重大架构升级——从有状态会话转为完全无状态。
有状态时代(MCP 1.x)的问题:
┌─────────────────────────────────────────────────────┐
│ 有状态模型 │
│ Client ──Mcp-Session-Id──► Server │
│ ↑ │
│ 会话状态存在 Server 端 │
│ → 无法水平扩展 │
│ → Serverless 冷启动慢 │
│ → 故障恢复麻烦 │
└─────────────────────────────────────────────────────┘
无状态时代(MCP 2026-07+)的变化:
| 变化点 | 有状态 | 无状态 |
|---|---|---|
| 会话标识 | Mcp-Session-Id 请求头 |
无,连接即会话 |
| 状态存储 | Server 端维护 | Client 端维护 / 请求自带 |
| 水平扩展 | ❌ 困难(会话粘滞) | ✅ 天然支持 |
| Serverless | ❌ 冷启动需重建会话 | ✅ 每次调用独立 |
| 故障恢复 | ❌ 会话丢失 | ✅ 重连即可继续 |
11.7.2 对开发者的影响¶
好消息:大多数代码不用改。FastMCP 等高层 SDK 已经帮你屏蔽了底层差异。
需要注意的变化:
- Server 端不再假设会话连续性 —— 如果你之前在全局变量里存了用户状态,现在需要改成"每次调用都带上下文"或用外部存储(Redis / DB)
- Resource 版本号机制 —— 无状态后,Client 通过
resource_timestamp判断资源是否过期,而不是依赖会话内的缓存 - Sampling 回调变化 —— 模型调用的上下文从"会话级"变为"请求级"
11.7.3 SDK 迁移指引¶
如果你在使用 mcp<1.0 的旧 SDK,迁移到无状态版本只需三步:
| 旧写法(0.x) | 新写法(1.x+ 无状态) | 说明 |
|---|---|---|
session_id 参数 |
已移除 | 不再需要手动管理会话 ID |
server.create_session() |
不需要 | 连接自动建立逻辑会话 |
| 全局变量存状态 | 改用 Context 参数 / 外部存储 |
每次调用都是独立的 |
迁移检查清单:
- [ ] 检查是否有模块级可变状态(_cache = {} 等)
- [ ] 确认工具函数不依赖"之前调用过什么"
- [ ] 如果需要持久化,接入 Redis / SQLite 等外部存储
- [ ] 测试 Server 重启后 Client 是否能无缝继续
11.8 Serverless 部署指南¶
无状态化最大的好处就是 MCP Server 可以完美跑在 Serverless 平台上——按调用付费、自动伸缩、零运维。
11.8.1 为什么 Serverless + MCP 是绝配?¶
┌─────────────────────────────────────────────────────┐
│ Serverless MCP 的优势 │
│ │
│ 💰 成本:免费额度 + 按调用付费,个人项目几乎零成本 │
│ 📈 扩展:从 0 到 10000 并发,全自动 │
│ 🔧 运维:不用管服务器、不用更新系统 │
│ 🌍 全球:边缘节点部署,全球用户低延迟 │
└─────────────────────────────────────────────────────┘
11.8.2 部署到 Cloudflare Workers(最快)¶
Cloudflare Workers 是部署 MCP Server 的最佳选择之一:冷启动 < 50ms,全球边缘节点,免费额度充足。
// mcp-server-worker.js
import { McpServer } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new McpServer({
name: "weather-mcp",
version: "1.0.0"
});
// 注册工具
server.tool("get_weather",
{ city: "string" },
async ({ city }) => {
// 调用天气 API
const resp = await fetch(`https://api.weather.example/${city}`);
const data = await resp.json();
return {
content: [{ type: "text", text: `${city}:${data.condition},${data.temp}°C` }]
};
}
);
// SSE 传输(适合 HTTP Serverless)
export default {
async fetch(request, env) {
const transport = new SSEServerTransport("/mcp", request);
await server.connect(transport);
return transport.response;
}
};
11.8.3 部署到 Vercel / Netlify Functions¶
Python 用户可以用 Vercel Serverless Functions:
# api/mcp.py
from mcp.server.fastmcp import FastMCP
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
mcp = FastMCP("my-server")
@mcp.tool()
def hello(name: str) -> str:
return f"Hello, {name}!"
app = FastAPI()
@app.post("/mcp")
async def mcp_endpoint(request: Request):
# SSE 传输适配
from mcp.server.sse import SseServerTransport
sse = SseServerTransport("/mcp/sse")
async def handle_stream():
async with sse.connect_websocket(request) as (read, write):
await mcp._mcp_server.run(read, write, mcp._create_session())
return StreamingResponse(handle_stream(), media_type="text/event-stream")
11.8.4 Serverless 注意事项¶
| 注意点 | 说明 | 解决方案 |
|---|---|---|
| 冷启动 | 首次调用可能慢 100-500ms | 选择启动快的运行时(Workers < Bun < Node < Python) |
| 执行时长限制 | 通常 10s-15min | 长任务用异步队列 + 回调 |
| 无文件系统 | 不能依赖本地文件 | 用对象存储(S3 / R2)或数据库 |
| 并发限制 | 免费额度有并发上限 | 升级付费计划或做限流 |
11.8.5 传输方式选择¶
无状态 MCP 支持多种传输,适用不同场景:
| 传输方式 | 最佳场景 | Serverless 友好度 |
|---|---|---|
| stdio | 本地 CLI、桌面应用 | ❌(需要长连接) |
| SSE (HTTP) | Web 应用、Serverless | ✅ 最推荐 |
| WebSocket | 实时交互、高频调用 | ⚠️ 部分平台支持 |
| Streamable HTTP | 最新标准,兼容 HTTP | ✅ 逐步普及中 |
💡 新手建议:本地开发用 stdio(最简单),生产部署用 SSE(最通用)。
11.9 最佳实践¶
11.9.1 错误处理¶
工具内的异常会直接传播给 MCP Client,建议在工具内部捕获并返回可读的错误信息:
@mcp.tool()
def safe_query(query: str) -> dict:
try:
result = execute_query(query)
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
11.9.2 权限控制¶
@mcp.tool()
def read_allowlisted_file(path: str) -> str:
"""只允许读取特定目录下的文件"""
allowed_dir = "/var/data"
if not os.path.abspath(path).startswith(allowed_dir):
raise PermissionError("Access denied")
return read_file(path)
11.9.3 工具命名与描述¶
- 工具名用 snake_case(如
get_weather),不要用中文或空格 docstring写清楚:工具做什么、何时用、参数含义——LLM 靠它决定是否调用- 每个工具只做一件事,职责单一
11.10 本章小结¶
✅ 理解了 MCP 的三角架构和三大原语
✅ 学会了用 FastMCP 编写生产级 MCP Server
✅ 掌握了 MCP Client 的调用方式
✅ 了解了与 LangChain / CrewAI 的集成方法
✅ 理解了 2026-07 无状态核心 的架构升级与影响
✅ 掌握了 Serverless 部署 MCP Server 的方法
✅ 知道如何从旧版 SDK 迁移到无状态版本