目录
- 前言
- [一、FastMCP 概述](#一、FastMCP 概述)
-
- 简介
- [stdio和HTTP/SSE 模式](#stdio和HTTP/SSE 模式)
-
- [stdio 模式:](#stdio 模式:)
- [HTTP/SSE 模式](#HTTP/SSE 模式)
- [二、FastMCP 核心架构](#二、FastMCP 核心架构)
-
- [2.1 服务器端(Server)](#2.1 服务器端(Server))
-
- [2.1.1 核心入口:FastMCP 类](#2.1.1 核心入口:FastMCP 类)
- [2.1.2 三大能力组件](#2.1.2 三大能力组件)
-
- [Tools(工具)------ 可执行动作](#Tools(工具)—— 可执行动作)
- [Resources(资源)------ 只读数据](#Resources(资源)—— 只读数据)
- [Prompts(提示模板)------ 可复用提示词](#Prompts(提示模板)—— 可复用提示词)
- [2.1.3 传输模式无缝切换](#2.1.3 传输模式无缝切换)
- [2.2 客户端(Client)](#2.2 客户端(Client))
-
- [2.2.1 创建与连接](#2.2.1 创建与连接)
- [2.2.2 生命周期管理](#2.2.2 生命周期管理)
- [2.2.3 核心操作](#2.2.3 核心操作)
-
- [1. 列出服务器能力](#1. 列出服务器能力)
- [2. 调用工具(Tool)](#2. 调用工具(Tool))
- [3. 读取资源(Resource)](#3. 读取资源(Resource))
- [4. 获取提示模板(Prompt)](#4. 获取提示模板(Prompt))
- [三、实战:基于 FastMCP + OpenAI SDK 实现极简 Agent](#三、实战:基于 FastMCP + OpenAI SDK 实现极简 Agent)
-
- [3.1 核心执行流程](#3.1 核心执行流程)
- [3.2 核心代码](#3.2 核心代码)
前言
承接上一篇《MCP 的前世今生:从 "为每个工具写 Schema" 到统一协议》,本文从理论落地实践,以目前 Python 生态最主流的 FastMCP 框架为核心,讲解其架构组成与开发流程。
一、FastMCP 概述
简介
FastMCP 是一套Python 的 MCP 开发框架,它对底层 MCP(Model Context Protocol)协议做了高度封装,让开发者无需关心 JSON-RPC 消息格式、握手协商、传输层细节,只需专注业务逻辑即可快速构建标准的 MCP 服务。
它既可以用来编写 MCP 服务端(对外提供工具、资源、提示),也自带完整的客户端 SDK,用于编程方式连接任意 MCP 服务器。
为什么选择 FastMCP:
在众多 MCP 实现中,FastMCP 是目前生产环境最常用的方案。
- 极致简单,开发效率高
采用与 FastAPI 一致的设计哲学:装饰器 + 类型注解 + 文档字符串自动生成JSON-RPC协议内容。 - 功能完备,开箱即用
它是一个生产级框架,内置了 stdio、HTTP/SSE 等多种传输协议,支持服务聚合、代理,并提供便捷的客户端用于测试。你无需"重复造轮子"。 - 官方认可,生态主流
全球 70% 的 MCP 服务器基于它构建,日均百万次下载量。选择它意味着拥有强大的社区支持和最佳实践。
stdio和HTTP/SSE 模式
| 传输方式 | 通信原理 | 典型适用场景 | 特性 |
|---|---|---|---|
| stdio | 以子进程启动服务器,通过标准输入 / 输出通信 | 本地桌面客户端(Claude Desktop、Cursor)、私有本地工具 | 低延迟、有会话状态、无需网络 |
| http | 通过 HTTP/SSE 协议与远程服务器网络通信 | 分布式部署、共享工具服务、服务端 Agent 调用 | 可远程访问、易扩缩容、独立部署 |
stdio 模式:
原理:
进程的 stdio 概念:每个进程启动时,操作系统会自动分配三个标准数据流:
stdin(标准输入,用于接收外部写入的数据)
stdout(标准输出,用于输出普通结果)
stderr(标准错误,用于输出错误日志)。
父子进程通信时,父进程可以往子进程的 stdin 写数据,子进程的输出会从 stdout 和 stderr 流出。
流程:
客户端创建子进程运行 MCP 服务器 → 将 JSON-RPC 消息写入子进程的 stdin → 从子进程的 stdout 读取响应 → 子进程退出时关闭连接。适合本地命令行工具、脚本集成场景,简单轻量但无法跨机器访问。
HTTP/SSE 模式
原理:
MCP 服务器作为独立 Web 服务运行,同时提供两个核心端点------/mcp(HTTP POST 端点)处理客户端发起的请求式通信,/sse(Server-Sent Events 端点)建立长连接实现服务端主动推送。
流程:
- 客户端 GET /sse 建立长连接,服务器返回一个消息端点地址(如 /message)。
- 客户端向该端点 POST JSON-RPC 请求。
- 服务器处理请求后,通过之前建立的 SSE 长连接把响应推回客户端。
二、FastMCP 核心架构
FastMCP 的整体架构分为服务器端和客户端两大核心部分,二者通过标准 MCP 协议通信,完全解耦。
2.1 服务器端(Server)
服务器端是工具能力的提供者,负责对外暴露能力、处理调用请求。其核心由一个入口类 + 三类能力组件构成。
MCP 服务端的核心就是一个事件循环,所有请求都由这个事件循环接收和调度。
2.1.1 核心入口:FastMCP 类
FastMCP 是服务器的核心实例,负责管理所有注册的能力、处理连接生命周期、序列化与反序列化协议消息。
python
from fastmcp import FastMCP
# 初始化服务器,name 为服务标识,instructions 为服务说明
mcp = FastMCP(
name="WeatherServer",
instructions="提供城市天气查询与城市信息服务"
)
2.1.2 三大能力组件
服务器通过三个装饰器注册不同类型的能力,这也是日常开发最核心的三个 API:
Tools(工具)------ 可执行动作
对应 MCP 协议的工具调用能力
python
@mcp.tool()
def get_weather(city: str, include_forecast: bool = False) -> str:
"""
查询指定城市的实时天气信息
Args:
city: 城市名称,如北京、上海
include_forecast: 是否返回未来3天预报
"""
# 业务逻辑:调用第三方天气接口
return f"{city} 今日晴朗,22~28°C"
框架自动从函数签名、类型注解、docstring 生成符合 MCP 规范的 JSON Schema
支持同步函数与 async def 异步函数,如果是同步函数,事件循环会直接将同步工具扔到内置线程池执行,不阻塞事件循环。
python
客户端请求(JSON-RPC)
↓
【事件循环】 ← 核心调度器
↓
判断工具类型
↓
┌────┴────┐
↓ ↓
异步工具 同步工具
(直接执行) (线程池)
↓ ↓
└────┬────┘
↓
返回响应给客户端
Resources(资源)------ 只读数据
用于向大模型提供结构化、无副作用的只读数据(配置、元数据、文件内容等),模型可以读取资源注入上下文。
python
# 使用 URI 模板标识资源,支持路径参数
@mcp.resource("city://{city}/info")
def get_city_info(city: str) -> dict:
"""获取城市基础信息"""
return {"city": city, "province": "直辖市", "population": "2189万"}
同样也支持异步方式
Prompts(提示模板)------ 可复用提示词
预定义的标准化提示词模板,支持接收参数动态生成提示内容,用于封装固定的交互模式(如代码审查、数据清洗、报告生成等)。
python
@mcp.prompt()
def data_analyst_prompt(data: str) -> str:
"""数据分析场景提示模板"""
return f"""请作为数据分析师,对以下数据进行总结:
{data}
要求:提炼核心结论 + 1条趋势判断"""
2.1.3 传输模式无缝切换
服务端的业务代码与传输方式完全解耦,仅需修改启动参数,即可在两种通信模式间切换,业务逻辑零改动:
python
if __name__ == "__main__":
# 模式1:stdio 模式,本地进程间通信,适合桌面客户端对接
# mcp.run(transport="stdio")
# 模式2:HTTP 模式,网络访问,适合远程部署与多客户端共享
mcp.run(transport="http", host="0.0.0.0", port=8000)
HTTP 模式启动后,默认服务端点为 http://127.0.0.1:8000/mcp,基于流式 HTTP 协议实现双向通信,同时向下兼容传统 SSE 模式。
2.2 客户端(Client)
客户端是 MCP 能力的使用者,负责连接服务器、发现能力、适配模型。它是连接「MCP 工具」与「大模型 / Agent 框架」的桥梁。
核心特性:
- 自动传输协商:根据传入的服务器源自动推断并选择合适的传输机制。
- 完整的协议支持:封装了 MCP 协议的全部能力------工具调用、资源读取、提示模板获取。
- 类型安全:返回结构化的 Python 对象,支持 datetime、UUID 等复杂类型的自动反序列化。
- 可重入上下文管理器:支持多个并发的 async with client 块,通过引用计数和后台会话管理实现高效的会话复用。
2.2.1 创建与连接
Client 的构造方式极其灵活,支持多种服务器源,并自动推断传输方式:
python
from fastmcp import Client, FastMCP
# 1. 连接同一进程内的内存服务器(理想用于测试)
server = FastMCP("TestServer")
client = Client(server) # 自动使用 In-Memory Transport[reference:12]
# 2. 连接远程 HTTP 服务器(生产部署)
client = Client("https://example.com/mcp") # 自动使用 HTTP Transport[reference:13]
# 3. 连接本地 Python 脚本(stdio 模式)
client = Client("my_mcp_server.py") # 自动使用 STDIO Transport[reference:14]
⚠️ STDIO 模式的关键注意点:STDIO 服务器运行在隔离环境中,不会继承你 shell 的环境变量。API Key、路径等配置必须显式传递:
python
# ❌ 错误:服务器看不到 shell 中 export 的变量
# export API_KEY="secret"
# client = Client("my_server.py")
# ✅ 正确:显式传递环境变量
client = Client("my_server.py", env={"API_KEY": "secret", "DEBUG": "true"})[reference:18]
2.2.2 生命周期管理
所有客户端操作最好在 async with 上下文管理器中执行,以确保连接的正确建立和释放
python
import asyncio
from fastmcp import Client
async def main():
client = Client("https://example.com/mcp")
async with client: # 进入时建立连接,退出时自动清理
# 在这里执行所有操作
await client.ping() # 验证连接是否正常
tools = await client.list_tools()
# ...
asyncio.run(main())
2.2.3 核心操作
1. 列出服务器能力
连接建立后,可以枚举服务器暴露的所有能力。
python
async with client:
# 列出所有可用工具
tools = await client.list_tools()
for tool in tools:
print(f"工具: {tool.name} - {tool.description}")
# 列出所有资源
resources = await client.list_resources()
# 列出所有提示模板
prompts = await client.list_prompts()
2. 调用工具(Tool)
call_tool() 是客户端最核心的方法,按名称执行服务器端工具并返回结构化结果
python
async with client:
# 基本调用:参数以字典形式传入
result = await client.call_tool("add", {"a": 5, "b": 3})
# 访问结构化数据(自动反序列化)
print(result.data) # 8[reference:25]
# 访问原始内容块
print(result.content[0].text) # "8"[reference:26]
超时控制与进度监控
python
async with client:
# 设置超时(超过2秒自动中止)
result = await client.call_tool(
"long_running_task",
{"param": "value"},
timeout=2.0
)
# 监听进度通知
result = await client.call_tool(
"long_running_task",
{"param": "value"},
progress_handler=my_progress_handler
)
结构化结果(v2.10.0+) :FastMCP 客户端能够将服务器返回的 JSON 数据自动还原为完整的 Python 对象,包括 datetime、UUID 等复杂类型:
因为服务器端工具返回json结果时,在返回的JSON-RPC数据中,会将数据类型一起发送给客户端,进而能够解析并反序列化为python对象。
python
from datetime import datetime
from uuid import UUID
async with client:
result = await client.call_tool("get_weather", {"city": "London"})
# FastMCP 自动重建完整 Python 对象
weather = result.data
print(f"温度: {weather.temperature}°C,时间: {weather.timestamp}")
# 复杂类型被正确反序列化
assert isinstance(weather.timestamp, datetime)
assert isinstance(weather.station_id, UUID)[reference:29]
错误处理:
python
from fastmcp.exceptions import ToolError
async with client:
# 方式一:捕获异常(默认行为)
try:
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
except ToolError as e:
print(f"工具执行失败: {e}")
# 方式二:手动检查错误标志
result = await client.call_tool(
"potentially_failing_tool",
{"param": "value"},
raise_on_error=False
)
if result.is_error:
print(f"失败: {result.content[0].text}")
else:
print(f"成功: {result.data}")
3. 读取资源(Resource)
python
async with client:
# 读取单个资源
resource = await client.read_resource("city://北京/info")
print(resource.content)
# 列出所有可用资源
resources = await client.list_resources()
4. 获取提示模板(Prompt)
python
async with client:
# 获取提示模板(可传入参数)
prompt = await client.get_prompt("data_analyst_prompt", {"data": "销售数据..."})
print(prompt.messages[0].content)
三、实战:基于 FastMCP + OpenAI SDK 实现极简 Agent
不依赖任何 Agent 框架,直接用 OpenAI 原生 SDK + FastMCP 客户端实现完整工具调用闭环,更直观地体现 MCP 协议的解耦价值。
3.1 核心执行流程
- 能力拉取:从 MCP 服务器拉取工具清单,直接映射为模型可识别的
function calling格式 - 模型决策:将用户问题 + 工具描述传入模型,模型自主判断是否调用工具
- 工具执行:解析模型返回的工具调用指令,通过
FastMCP客户端执行对应工具 - 结果生成:将工具执行结果回填到对话上下文,再次调用模型生成最终回答
3.2 核心代码
python
import asyncio
import json
from openai import AsyncOpenAI
from fastmcp import Client
class MCPAgent:
def __init__(self, mcp_url: str, api_key: str, model: str = "gpt-4o-mini"):
self.llm = AsyncOpenAI(api_key=api_key)
self.model = model
self.mcp_url = mcp_url
self.mcp_client = None
self.tools = []
async def init(self):
"""连接 MCP 服务器,拉取工具并转换为 OpenAI 格式"""
self.mcp_client = Client(self.mcp_url)
await self.mcp_client.__aenter__()
# MCP 的 inputSchema 本身就是标准 JSON Schema,可直接复用
mcp_tools = await self.mcp_client.list_tools()
self.tools = [
{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema
}
}
for tool in mcp_tools
]
async def run(self, query: str) -> str:
messages = [
{"role": "system", "content": "你是智能助手,可调用工具回答问题,回答简洁准确。"},
{"role": "user", "content": query}
]
# 最多 3 轮工具调用,防止死循环
for _ in range(3):
resp = await self.llm.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools,
tool_choice="auto"
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
# 模型回复加入上下文
messages.append(msg.model_dump())
# 批量执行工具调用
for tc in msg.tool_calls:
name = tc.function.name
args = json.loads(tc.function.arguments)
# 通过 MCP 协议调用工具
result = await self.mcp_client.call_tool(name, args)
result_text = "\n".join([item.text for item in result.content])
# 结果回填上下文
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"name": name,
"content": result_text
})
return "已达到最大调用次数,请求未完成"
async def close(self):
if self.mcp_client:
await self.mcp_client.__aexit__(None, None, None)
async def main():
agent = MCPAgent(
mcp_url="http://localhost:8000/mcp",
api_key="替换为你的 OpenAI API Key"
)
await agent.init()
try:
print("=== 测试1:单工具调用 ===")
print(await agent.run("北京今天天气怎么样,加上预报"))
print("\n=== 测试2:多工具并行调用 ===")
print(await agent.run("上海和广州的天气分别是什么?"))
finally:
await agent.close()
if __name__ == "__main__":
asyncio.run(main())