本文为 FastMCP 2.0 服务端教学文档的下篇,重点讲解 上下文(Context) 、代理与组合 、交互机制 (征询、日志、进度、采样)、中间件与存储后端 等高阶能力,帮助开发者构建可扩展、交互式、生产就绪的 MCP 服务。
一、MCP 上下文(Context)
上下文对象是 FastMCP 中连接工具、资源、提示函数与 MCP 协议运行时功能的桥梁。通过 依赖注入 或 运行时依赖函数,开发者可在任意函数中访问丰富的能力。
1.1 依赖注入方式(推荐)
在函数签名中增加类型为 Context 的参数,FastMCP 自动注入:
python
from fastmcp import FastMCP, Context
@mcp.tool
async def demo_tool(ctx: Context) -> str:
await ctx.info("工具正在执行")
return "完成"
参数名可任意(ctx, context, _c 均可),只需类型提示为 Context。
1.2 运行时依赖函数(适用于深层调用栈)
python
from fastmcp import get_context
def inner_function():
ctx = get_context() # 仅在请求上下文中有效
ctx.info("从嵌套函数中记录日志")
⚠️ 注意 :
get_context()仅限服务器端在活跃请求中调用,否则抛出RuntimeError。
1.3 上下文核心能力
| 功能 | 方法 | 说明 |
|---|---|---|
| 日志记录 | ctx.debug/info/warning/error(msg) |
向客户端发送日志(LLM 可见) |
| 进度报告 | ctx.report_progress(progress, total=None) |
支持百分比、绝对值、多阶段等模式 |
| 资源访问 | await ctx.read_resource(uri) |
读取本服务器或其他服务器注册的资源 |
| LLM 采样 | await ctx.sample(prompt, ...) |
请求 LLM 生成文本(如摘要、转换) |
| 用户征询 | await ctx.elicit(message, response_type=...) |
交互式请求用户输入结构化数据 |
| 状态管理 | ctx.set_state(key, value) / ctx.get_state(key) |
在中间件与工具间传递数据 |
| 请求元数据 | ctx.request_id, ctx.client_id, ctx.session_id |
识别当前会话 |
| 服务器实例 | ctx.fastmcp |
访问当前 FastMCP 实例 |
| 变更通知 | ctx.notify_tools_changed() 等 |
手动触发能力列表变更通知 |
二、代理(Proxy)与服务器组合(Composition)
2.1 代理服务器(Proxy)
代理允许将远程或本地 MCP 服务器透明地整合到当前服务中。
创建远程代理
python
from fastmcp import FastMCP
# 代理远程 MCP 服务器
weather_proxy = FastMCP.as_proxy(
name="WeatherProxy",
transport="http",
url="https://weather-api.example.com/mcp"
)
# 挂载到主服务器
main = FastMCP("MainServer")
main.mount(weather_proxy, prefix="weather")
创建配置驱动代理(MCPConfig)
python
config = {
"servers": [
{
"name": "GitHub",
"transport": "http",
"url": "https://github-mcp.example.com/mcp"
}
]
}
proxy = FastMCP.as_proxy_from_config(config)
代理支持图标与元信息
python
from mcp.types import Icon
mcp = FastMCP(
name="WeatherService",
website_url="https://weather.example.com",
icons=[
Icon(
src="https://weather.example.com/icon-48.png",
mimeType="image/png",
sizes=["48x48"]
),
Icon(
src="https://weather.example.com/icon-96.png",
mimeType="image/png",
sizes=["96x96"]
),
]
)
2.2 服务器组合(Composition)
FastMCP 支持两种组合模式:静态导入 (import_server) 与 动态挂载 (mount)。
| 特性 | import_server() |
mount() |
|---|---|---|
| 类型 | 静态拷贝 | 动态代理 |
| 更新同步 | ❌ 导入后不更新 | ✅ 实时同步 |
| 性能 | ⚡ 快(直接调用) | ⏱️ 较慢(委托调用) |
| 生命周期 | 不执行子服务器生命周期 | 执行(代理模式) |
| 适用场景 | 打包发布、性能敏感 | 开发调试、模块热插拔 |
示例:静态导入
python
# 子服务器
weather = FastMCP("Weather")
@weather.tool
def get_temp(city: str) -> float: ...
# 主服务器
main = FastMCP("Main")
await main.import_server(weather, prefix="weather")
# 工具名变为 weather_get_temp
示例:动态挂载
python
main.mount(weather, prefix="weather") # 实时链接
前缀规则
- 工具/提示 :
{prefix}_{name} - 资源 URI (路径格式,默认):
data://{prefix}/info - 资源 URI (协议格式):
{prefix}+//info(需配置)
标签递归过滤(≥2.9.0)
python
# 子服务器
weather = FastMCP("Weather", include_tags={"prod"})
@weather.tool(tags={"prod", "free"})
def free_api(): ...
@weather.tool(tags={"prod", "premium"})
def premium_api(): ...
# 主服务器
main = FastMCP("Main", exclude_tags={"premium"})
main.mount(weather) # 仅暴露 free_api
三、高级交互机制
3.1 用户征询(Elicitation)
允许工具在执行中暂停并请求用户输入,支持标量、结构化、选项式输入。
基本用法
python
from dataclasses import dataclass
@dataclass
class Task:
title: str
priority: Literal["低", "中", "高"]
@mcp.tool
async def create_task(ctx: Context) -> str:
result = await ctx.elicit("请填写任务信息", response_type=Task)
if result.action == "accept":
return f"创建任务:{result.data.title}"
return "已取消"
支持的响应类型
| 类型 | 说明 |
|---|---|
str, int, bool |
自动包装为 {value: T},返回时解包 |
None |
仅确认/拒绝,无数据 |
["A", "B"] |
快捷选项(等价于 Literal["A","B"]) |
dataclass / Pydantic |
结构化对象(仅支持标量字段) |
多轮征询
python
@mcp.tool
async def plan_meeting(ctx: Context) -> str:
title = await ctx.elicit("标题?", str)
if title.action != "accept": return "取消"
duration = await ctx.elicit("时长(分钟)?", int)
return f"会议:{title.data}({duration.data}分钟)"
⚠️ 客户端要求 :必须实现
elicitation处理程序,否则抛出错误。
3.2 日志记录(Logging)
向客户端发送结构化日志,LLM 可据此调整行为。
python
@mcp.tool
async def risky_operation(ctx: Context) -> str:
await ctx.info("开始高风险操作")
try:
# ...
await ctx.info("操作成功")
except Exception as e:
await ctx.error(f"操作失败: {e}")
raise
日志级别:debug < info < warning < error
3.3 进度报告(Progress)
提升长时间操作的用户体验,防止超时。
进度模式
| 模式 | 用法 | 示例 |
|---|---|---|
| 绝对进度 | progress=i, total=n |
处理 5/10 个文件 |
| 百分比 | progress=75.5, total=100 |
下载 75.5% |
| 不确定 | progress=count(无 total) |
扫描未知数量文件 |
| 多阶段 | 分段报告 0--25%、25--60% 等 | 数据迁移 |
示例:多阶段迁移
python
@mcp.tool
async def data_migration(src: str, dst: str, ctx: Context) -> str:
# 阶段1:验证 (0-25%)
for i in range(5):
await ctx.report_progress(i * 5, 100)
# 阶段2:导出 (25-60%)
for i in range(7):
await ctx.report_progress(25 + i * 5, 100)
# ... 其他阶段
await ctx.report_progress(100, 100)
return "迁移完成"
⚠️ 客户端要求 :必须在初始请求中提供
progressToken,否则进度调用静默忽略。
3.4 LLM 采样(Sampling)
在工具内部调用 LLM 生成文本,实现 AI 增强逻辑。
基本用法
python
@mcp.tool
async def summarize_text(text: str, ctx: Context) -> str:
summary = await ctx.sample(f"用一句话总结:{text[:500]}")
return summary.text
高级用法
python
# 系统提示 + 模型偏好
summary = await ctx.sample(
messages=[
{"role": "system", "content": "你是一个专业摘要助手"},
{"role": "user", "content": text}
],
model_preferences={"family": "claude"}
)
回退处理器(处理不支持采样的客户端)
python
from fastmcp.llm import OpenAISamplingHandler
mcp = FastMCP(
"MyServer",
sampling_handler=OpenAISamplingHandler(api_key="..."),
sampling_handler_behavior="fallback" # 客户端不支持时自动回退
)
"fallback":仅客户端不支持时使用回退"always":始终使用回退,绕过客户端
四、中间件(Middleware)与存储后端(Storage Backends)
4.1 中间件系统
FastMCP 支持通过中间件扩展服务器行为,如缓存、认证、日志等。
响应缓存中间件
python
from fastmcp.server.middleware.caching import ResponseCachingMiddleware
from key_value.aio.stores.disk import DiskStore
mcp.add_middleware(ResponseCachingMiddleware(
cache_storage=DiskStore(directory="/tmp/fastmcp-cache")
))
缓存基于请求内容哈希,自动跳过副作用工具(如非 idempotent 工具)。
4.2 可插拔存储后端(≥2.13.0)
FastMCP 使用 py-key-value-aio 库提供统一存储接口。
| 后端 | 适用场景 | 特点 |
|---|---|---|
| MemoryStore | 开发、测试 | 快速,重启丢失 |
| DiskStore | 单机生产 | 持久化,需文件系统 |
| RedisStore | 分布式生产 | 高可用,支持 TTL |
| DynamoDB/MongoDB | 云原生 | 与现有基础设施集成 |
OAuth 令牌安全存储(生产必需)
python
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
auth = GitHubProvider(
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(host="redis.example.com"),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)
⚠️ 安全警告 :生产环境必须使用
FernetEncryptionWrapper加密 OAuth 令牌,否则以明文存储!
客户端令牌持久化
python
from fastmcp.client.auth import OAuthClientProvider
from key_value.aio.stores.disk import DiskStore
client = OAuthClientProvider(
mcp_url="...",
token_storage=DiskStore(directory="~/.fastmcp/tokens")
)
五、最佳实践总结
| 场景 | 推荐方案 |
|---|---|
| 跨服务器调用 | 使用 ctx.read_resource() 或组合(mount/import) |
| 交互式工具 | 结合 ctx.elicit() + ctx.report_progress() |
| AI 增强 | 使用 ctx.sample() + 回退处理器 |
| 性能敏感 | 用 import_server() 代替 mount() |
| 生产部署 | Redis + 加密存储 + 严格输入验证 |
| 模块化开发 | 按功能拆分服务器(WeatherServer, DBServer),通过组合集成 |
| 安全错误处理 | mask_error_details=True + ToolError/ResourceError |