关于 fastmcp
fastmcp 是面向开发者的 Python 框架技能,用于构建和运维 MCP 服务。内容涵盖服务组件、中间件、存储、上下文功能及部署模式。
主要功能
- 01定义工具、资源和提示词
- 02添加用户输入与进度更新
- 03配置持久化存储后端
- 04使用内置或自定义中间件
- 05通过导入或挂载组合服务
适用场景
- 01构建 Python MCP 服务
- 02为长时间运行的工具报告进度
- 03持久化 OAuth 令牌或缓存响应
- 04将服务组织为模块化组件
MCP 相关
FastMCP 帮助开发者构建向 MCP 客户端公开工具、资源和提示词的 MCP 服务。该技能提供实现与部署指导。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill fastmcp安装后在 Claude Code 中输入/fastmcp确认已加载。
附带文件README.mdSKILL.json
详细介绍
FastMCP - 使用 Python 构建 MCP 服务
FastMCP 是一个用于构建 Model Context Protocol(MCP)服务的 Python 框架,可向 Claude 等大模型提供工具、资源和提示词。此技能提供经过生产环境验证的模式、错误预防方法和部署策略,助你构建稳健的 MCP 服务。
快速入门
安装
pip install fastmcp
# or
uv pip install fastmcp
最简服务
from fastmcp import FastMCP
# MUST be at module level for FastMCP Cloud
mcp = FastMCP("My Server")
@mcp.tool()
async def hello(name: str) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
运行:
# Local development
python server.py
# With FastMCP CLI
fastmcp dev server.py
# HTTP mode
python server.py --transport http --port 8000
核心概念
1. 工具
工具是大模型可调用以执行操作的函数:
@mcp.tool()
def calculate(operation: str, a: float, b: float) -> float:
"""Perform mathematical operations.
Args:
operation: add, subtract, multiply, or divide
a: First number
b: Second number
Returns:
Result of the operation
"""
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else None
}
return operations.get(operation, lambda x, y: None)(a, b)
最佳实践:
- 使用清晰、描述性强的函数名称
- 编写全面的文档字符串(大模型会读取这些内容!)
- 提供完善的类型提示(Pydantic 会自动验证)
- 返回结构化数据(字典/列表)
- 妥善处理错误
同步与异步:
# Sync tool (for non-blocking operations)
@mcp.tool()
def sync_tool(param: str) -> dict:
return {"result": param.upper()}
# Async tool (for I/O operations, API calls)
@mcp.tool()
async def async_tool(url: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
2. 资源
资源向大模型提供静态或动态数据:
# Static resource
@mcp.resource("data://config")
def get_config() -> dict:
"""Provide application configuration."""
return {
"version": "1.0.0",
"features": ["auth", "api", "cache"]
}
# Dynamic resource
@mcp.resource("info://status")
async def server_status() -> dict:
"""Get current server status."""
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"api_configured": bool(os.getenv("API_KEY"))
}
资源 URI 方案:
data://- 通用数据file://- 文件资源resource://- 通用资源info://- 信息/元数据api://- API 端点- 允许使用自定义方案
3. 资源模板
URI 中带有参数的动态资源:
# Single parameter
@mcp.resource("user://{user_id}/profile")
async def get_user_profile(user_id: str) -> dict:
"""Get user profile by ID."""
user = await fetch_user_from_db(user_id)
return {
"id": user_id,
"name": user.name,
"email": user.email
}
# Multiple parameters
@mcp.resource("org://{org_id}/team/{team_id}/members")
async def get_team_members(org_id: str, team_id: str) -> list:
"""Get team members with org context."""
return await db.query(
"SELECT * FROM members WHERE org_id = ? AND team_id = ?",
[org_id, team_id]
)
重要提示: URI 模板中的参数名称必须与函数签名完全一致。
4. 提示词
为大模型预配置的提示词:
@mcp.prompt("analyze")
def analyze_prompt(topic: str) -> str:
"""Generate analysis prompt."""
return f"""
Analyze {topic} considering:
1. Current state
2. Challenges
3. Opportunities
4. Recommendations
Use available tools to gather data.
"""
@mcp.prompt("help")
def help_prompt() -> str:
"""Generate help text for server."""
return """
Welcome to My Server!
Available tools:
- search: Search for items
- process: Process data
Available resources:
- info://status: Server status
"""
上下文功能
FastMCP 通过上下文注入提供高级功能:
1. 信息收集(用户输入)
在工具执行期间请求用户输入:
from fastmcp import Context
@mcp.tool()
async def confirm_action(action: str, context: Context) -> dict:
"""Perform action with user confirmation."""
# Request confirmation from user
confirmed = await context.request_elicitation(
prompt=f"Confirm {action}? (yes/no)",
response_type=str
)
if confirmed.lower() == "yes":
result = await perform_action(action)
return {"status": "completed", "action": action}
else:
return {"status": "cancelled", "action": action}
2. 进度跟踪
报告长时间运行操作的进度:
@mcp.tool()
async def batch_import(file_path: str, context: Context) -> dict:
"""Import data with progress updates."""
data = await read_file(file_path)
total = len(data)
imported = []
for i, item in enumerate(data):
# Report progress
await context.report_progress(
progress=i + 1,
total=total,
message=f"Importing item {i + 1}/{total}"
)
result = await import_item(item)
imported.append(result)
return {"imported": len(imported), "total": total}
3. 采样(大模型集成)
在工具内部请求大模型补全:
@mcp.tool()
async def enhance_text(text: str, context: Context) -> str:
"""Enhance text using LLM."""
response = await context.request_sampling(
messages=[{
"role": "system",
"content": "You are a professional copywriter."
}, {
"role": "user",
"content": f"Enhance this text: {text}"
}],
temperature=0.7,
max_tokens=500
)
return response["content"]
存储后端
FastMCP 支持基于 py-key-value-aio 库构建的可插拔存储后端。存储后端可为 OAuth 令牌、响应缓存和客户端令牌存储提供持久化状态。
可用后端
内存存储(默认):
- 临时存储(重启后丢失)
- 速度快,无需配置
- 适用于开发
磁盘存储:
- 在本地文件系统中持久存储
- 默认使用
FernetEncryptionWrapper加密 - 根据平台选择默认设置(Mac/Windows 使用磁盘,Linux 使用内存)
Redis 存储:
- 用于生产环境的分布式存储
- 支持多实例部署
- 适合跨服务器响应缓存
其他支持的存储:
- DynamoDB(AWS)
- MongoDB
- Elasticsearch
- Memcached
- RocksDB
- Valkey
基本用法
from fastmcp import FastMCP
from key_value.stores import MemoryStore, DiskStore, RedisStore
from key_value.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
import os
# Memory storage (default)
mcp = FastMCP("My Server")
# Disk storage (persistent)
from key_value.stores import DiskStore
mcp = FastMCP(
"My Server",
storage=DiskStore(path="/app/data/storage")
)
# Redis storage (production)
from key_value.stores import RedisStore
mcp = FastMCP(
"My Server",
storage=RedisStore(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", "6379")),
password=os.getenv("REDIS_PASSWORD")
)
)
加密存储
存储后端支持自动加密:
from cryptography.fernet import Fernet
from key_value.encryption import FernetEncryptionWrapper
from key_value.stores import DiskStore
# Generate encryption key (store in environment!)
# key = Fernet.generate_key()
# Use encrypted storage
encrypted_storage = FernetEncryptionWrapper(
key_value=DiskStore(path="/app/data/storage"),
fernet=Fernet(os.getenv("STORAGE_ENCRYPTION_KEY"))
)
mcp = FastMCP("My Server", storage=encrypted_storage)
OAuth 令牌存储
存储后端会自动持久化 OAuth 令牌:
from fastmcp.auth import OAuthProxy
from key_value.stores import RedisStore
from key_value.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Production OAuth with encrypted Redis storage
auth = OAuthProxy(
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.getenv("REDIS_HOST"),
password=os.getenv("REDIS_PASSWORD")
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
),
upstream_authorization_endpoint="https://provider.com/oauth/authorize",
upstream_token_endpoint="https://provider.com/oauth/token",
upstream_client_id=os.getenv("OAUTH_CLIENT_ID"),
upstream_client_secret=os.getenv("OAUTH_CLIENT_SECRET")
)
mcp = FastMCP("OAuth Server", auth=auth)
根据平台选择默认设置
FastMCP 会根据平台自动选择存储方式:
- Mac/Windows:磁盘存储(持久化)
- Linux:内存存储(临时)
- 覆盖默认设置:显式设置
storage参数
# Explicitly use disk storage on Linux
from key_value.stores import DiskStore
mcp = FastMCP(
"My Server",
storage=DiskStore(path="/var/lib/mcp/storage")
)
服务生命周期
服务生命周期提供初始化和清理钩子,每个服务实例只运行一次(不是每个客户端会话运行一次)。这对管理数据库连接、API 客户端和其他资源至关重要。
⚠️ v2.13.0 中的重大变更:生命周期行为已从按会话运行改为按服务实例运行。
基本模式
from fastmcp import FastMCP
from contextlib import asynccontextmanager
from typing import AsyncIterator
from dataclasses import dataclass
@dataclass
class AppContext:
"""Shared application state."""
db: Database
api_client: httpx.AsyncClient
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
"""
Initialize resources on startup, cleanup on shutdown.
Runs ONCE per server instance, NOT per client session.
"""
# Startup: Initialize resources
db = await Database.connect(os.getenv("DATABASE_URL"))
api_client = httpx.AsyncClient(
base_url=os.getenv("API_BASE_URL"),
headers={"Authorization": f"Bearer {os.getenv('API_KEY')}"},
timeout=30.0
)
print("Server initialized")
try:
# Yield context to tools
yield AppContext(db=db, api_client=api_client)
finally:
# Shutdown: Cleanup resources
await db.disconnect()
await api_client.aclose()
print("Server shutdown complete")
# Create server with lifespan
mcp = FastMCP("My Server", lifespan=app_lifespan)
# Access context in tools
from fastmcp import Context
@mcp.tool()
async def query_database(sql: str, context: Context) -> list:
"""Query database using shared connection."""
# Access lifespan context
app_context: AppContext = context.fastmcp_context.lifespan_context
return await app_context.db.query(sql)
@mcp.tool()
async def api_request(endpoint: str, context: Context) -> dict:
"""Make API request using shared client."""
app_context: AppContext = context.fastmcp_context.lifespan_context
response = await app_context.api_client.get(endpoint)
return response.json()
ASGI 集成
将 FastMCP 与 ASGI 应用(FastAPI、Starlette)搭配使用时,必须显式传入生命周期:
from fastapi import FastAPI
from fastmcp import FastMCP
# FastMCP lifespan
@asynccontextmanager
async def mcp_lifespan(server: FastMCP):
print("MCP server starting")
yield
print("MCP server stopping")
mcp = FastMCP("My Server", lifespan=mcp_lifespan)
# FastAPI app MUST include MCP lifespan
app = FastAPI(lifespan=mcp.lifespan)
# Add routes
@app.get("/")
def root():
return {"message": "Hello World"}
❌ 错误:未将生命周期传递给父应用
app = FastAPI() # MCP lifespan won't run!
✅ 正确:将 MCP 生命周期传递给父应用
app = FastAPI(lifespan=mcp.lifespan)
状态管理
在服务生命周期内存储和检索状态:
from fastmcp import Context
@mcp.tool()
async def set_config(key: str, value: str, context: Context) -> dict:
"""Store configuration value."""
context.fastmcp_context.set_state(key, value)
return {"status": "saved", "key": key}
@mcp.tool()
async def get_config(key: str, context: Context) -> dict:
"""Retrieve configuration value."""
value = context.fastmcp_context.get_state(key, default=None)
if value is None:
return {"error": f"Key '{key}' not found"}
return {"key": key, "value": value}
中间件系统
FastMCP 提供原生于 MCP 的中间件系统,用于日志记录、速率限制、缓存和错误处理等横切功能。
内置中间件(8 种)
- TimingMiddleware - 性能监控
- ResponseCachingMiddleware - 基于 TTL 的缓存,支持可插拔存储
- LoggingMiddleware - 易读日志和 JSON 结构化日志
- RateLimitingMiddleware - 令牌桶和滑动窗口算法
- ErrorHandlingMiddleware - 一致的错误管理
- ToolInjectionMiddleware - 动态注入工具
- PromptToolMiddleware - 为功能受限的客户端提供基于工具的提示词访问
- ResourceToolMiddleware - 为功能受限的客户端提供基于工具的资源访问
基本用法
from fastmcp import FastMCP
from fastmcp.middleware import (
TimingMiddleware,
LoggingMiddleware,
RateLimitingMiddleware,
ResponseCachingMiddleware,
ErrorHandlingMiddleware
)
mcp = FastMCP("My Server")
# Add middleware (order matters!)
mcp.add_middleware(ErrorHandlingMiddleware())
mcp.add_middleware(TimingMiddleware())
mcp.add_middleware(LoggingMiddleware(level="INFO"))
mcp.add_middleware(RateLimitingMiddleware(
max_requests=100,
window_seconds=60,
algorithm="token_bucket"
))
mcp.add_middleware(ResponseCachingMiddleware(
ttl_seconds=300,
storage=RedisStore(host="localhost")
))
中间件执行顺序
中间件按添加顺序执行:
Request Flow:
→ ErrorHandlingMiddleware (catches errors)
→ TimingMiddleware (starts timer)
→ LoggingMiddleware (logs request)
→ RateLimitingMiddleware (checks rate limit)
→ ResponseCachingMiddleware (checks cache)
→ Tool/Resource Handler
← ResponseCachingMiddleware (stores in cache)
← RateLimitingMiddleware
← LoggingMiddleware (logs response)
← TimingMiddleware (stops timer, logs duration)
← ErrorHandlingMiddleware (returns error if any)
自定义中间件
使用钩子创建自定义中间件:
from fastmcp.middleware import BaseMiddleware
from fastmcp import Context
class AccessControlMiddleware(BaseMiddleware):
"""Check authorization before tool execution."""
def __init__(self, allowed_users: list[str]):
self.allowed_users = allowed_users
async def on_call_tool(self, tool_name: str, arguments: dict, context: Context):
"""Hook runs before tool execution."""
# Get user from context (from auth)
user = context.fastmcp_context.get_state("user_id")
if user not in self.allowed_users:
raise PermissionError(f"User '{user}' not authorized")
# Continue to tool
return await self.next(tool_name, arguments, context)
# Add to server
mcp.add_middleware(AccessControlMiddleware(
allowed_users=["alice", "bob", "charlie"]
))
钩子层级
中间件钩子从最通用到最具体依次为:
on_message- 所有消息(请求和通知)on_request/on_notification- 按消息类型区分on_call_tool、on_read_resource、on_get_prompt- 特定操作on_list_tools、on_list_resources、on_list_prompts、on_list_resource_templates- 列表操作
class ComprehensiveMiddleware(BaseMiddleware):
async def on_message(self, message: dict, context: Context):
"""Runs for ALL messages."""
print(f"Message: {message['method']}")
return await self.next(message, context)
async def on_call_tool(self, tool_name: str, arguments: dict, context: Context):
"""Runs only for tool calls."""
print(f"Tool: {tool_name}")
return await self.next(tool_name, arguments, context)
async def on_read_resource(self, uri: str, context: Context):
"""Runs only for resource reads."""
print(f"Resource: {uri}")
return await self.next(uri, context)
响应缓存中间件
通过缓存开销较大的操作来提升性能:
from fastmcp.middleware import ResponseCachingMiddleware
from key_value.stores import RedisStore
# Cache responses for 5 minutes
cache_middleware = ResponseCachingMiddleware(
ttl_seconds=300,
storage=RedisStore(host="localhost"), # Shared across instances
cache_tools=True, # Cache tool calls
cache_resources=True, # Cache resource reads
cache_prompts=False # Don't cache prompts
)
mcp.add_middleware(cache_middleware)
# Tools/resources are automatically cached
@mcp.tool()
async def expensive_computation(data: str) -> dict:
"""This will be cached for 5 minutes."""
import time
time.sleep(5) # Expensive operation
return {"result": process(data)}
服务组合
使用服务组合,将工具、资源和提示词组织成模块化组件。
两种策略
1. import_server() - 静态快照:
- 在导入时一次性复制组件
- 子服务的更改不会传播
- 速度快(无需运行时委派)
- 适用于:打包已定稿的组件
2. mount() - 动态链接:
- 与子服务建立实时运行时链接
- 子服务的更改会立即生效
- 运行时委派(速度较慢)
- 适用于:模块化运行时组合
导入服务(静态)
from fastmcp import FastMCP
# Subserver with tools
api_server = FastMCP("API Server")
@api_server.tool()
def api_tool():
return "API result"
@api_server.resource("api://status")
def api_status():
return {"status": "ok"}
# Main server imports components
main_server = FastMCP("Main Server")
# Import all components from subserver
main_server.import_server(api_server)
# Now main_server has api_tool and api://status
# Changes to api_server won't affect main_server
挂载服务(动态)
from fastmcp import FastMCP
# Create servers
api_server = FastMCP("API Server")
db_server = FastMCP("DB Server")
@api_server.tool()
def fetch_data():
return "API data"
@db_server.tool()
def query_db():
return "DB result"
# Main server mounts subservers
main_server = FastMCP("Main Server")
# Mount with prefix
main_server.mount(api_server, prefix="api")
main_server.mount(db_server, prefix="db")
# Tools are namespaced:
# - api.fetch_data
# - db.query_db
# Resources are prefixed:
# - resource://api/path/to/resource
# - resource://db/path/to/resource
挂载模式
直接挂载(默认):
# In-memory access, subserver runs in same process
main_server.mount(subserver, prefix="sub")
代理挂载:
# Treats subserver as separate entity with own lifecycle
main_server.mount(
subserver,
prefix="sub",
mode="proxy"
)
标签筛选
导入/挂载时筛选组件:
# Tag subserver components
@api_server.tool(tags=["public"])
def public_api():
return "Public"
@api_server.tool(tags=["admin"])
def admin_api():
return "Admin only"
# Import only public tools
main_server.import_server(
api_server,
include_tags=["public"]
)
# Or exclude admin tools
main_server.import_server(
api_server,
exclude_tags=["admin"]
)
# Tag filtering is recursive with mount()
main_server.mount(
api_server,
prefix="api",
include_tags=["public"]
)
资源前缀格式
路径格式(自 v2.4.0 起为默认格式):
resource://prefix/path/to/resource
协议格式(旧版):
prefix+resource://path/to/resource
配置格式:
main_server.mount(
subserver,
prefix="api",
resource_prefix_format="path" # or "protocol"
)
OAuth 代理与身份验证
FastMCP 为基于 HTTP 的传输提供全面的身份验证支持,包括 OAuth 代理,可用于不支持动态客户端注册(DCR)的提供方。
四种身份验证模式
- 令牌验证(
TokenVerifier/JWTVerifier)- 验证外部令牌 - 外部身份提供方(
RemoteAuthProvider)- 支持 DCR 的 OAuth 2.0/OIDC - OAuth 代理(
OAuthProxy)- 对接传统 OAuth 提供方 - 完整 OAuth(
OAuthProvider)- 完整的授权服务器
模式 1:令牌验证
验证由外部系统颁发的令牌:
from fastmcp import FastMCP
from fastmcp.auth import JWTVerifier
# JWT verification
auth = JWTVerifier(
issuer="https://auth.example.com",
audience="my-mcp-server",
public_key=os.getenv("JWT_PUBLIC_KEY")
)
mcp = FastMCP("Secure Server", auth=auth)
@mcp.tool()
async def secure_operation(context: Context) -> dict:
"""Only accessible with valid JWT."""
# Token validated automatically
user = context.fastmcp_context.get_state("user_id")
return {"user": user, "status": "authorized"}
模式 2:外部身份提供方
使用支持动态客户端注册的 OAuth 2.0/OIDC 提供方:
from fastmcp.auth import RemoteAuthProvider
auth = RemoteAuthProvider(
issuer="https://auth.example.com",
# Provider must support DCR
)
mcp = FastMCP("OAuth Server", auth=auth)
模式 3:OAuth 代理(推荐用于生产环境)
对接不支持 DCR 的 OAuth 提供方(GitHub、Google、Azure、AWS、Discord、Facebook 等):
from fastmcp.auth import OAuthProxy
from key_value.stores import RedisStore
from key_value.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
import os
auth = OAuthProxy(
# JWT signing for issued tokens
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
# Encrypted storage for upstream tokens
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.getenv("REDIS_HOST"),
password=os.getenv("REDIS_PASSWORD")
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
),
# Upstream OAuth provider
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id=os.getenv("GITHUB_CLIENT_ID"),
upstream_client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
# Scopes
upstream_scope="read:user user:email",
# Security: Enable consent screen (prevents confused deputy attacks)
enable_consent_screen=True
)
mcp = FastMCP("GitHub Auth Server", auth=auth)
OAuth 代理功能
令牌工厂模式:
- 代理颁发自己的 JWT(不转发上游令牌)
- 上游令牌以加密形式存储
- 代理令牌可包含自定义声明
授权确认页面:
- 防止绕过授权攻击
- 向用户展示将授予哪些权限
- 符合安全规范的必要措施
PKCE 支持:
- 从客户端到上游的端到端验证
- 防止授权码被拦截
RFC 7662 令牌内省:
- 使用上游提供方验证令牌
- 检查令牌是否已撤销
模式 4:完整 OAuth 提供方
运行完整的授权服务器:
from fastmcp.auth import OAuthProvider
auth = OAuthProvider(
issuer="https://my-auth-server.com",
client_storage=RedisStore(host="localhost"),
# Full OAuth 2.0 server implementation
)
mcp = FastMCP("Auth Server", auth=auth)
基于环境的配置
根据环境自动检测身份验证:
export FASTMCP_SERVER_AUTH='{"type": "oauth_proxy", "upstream_authorization_endpoint": "...", ...}'
# Automatically configures from FASTMCP_SERVER_AUTH
mcp = FastMCP("Auto Auth Server")
支持的 OAuth 提供商
- GitHub:
https://github.com/login/oauth/authorize - Google:
https://accounts.google.com/o/oauth2/v2/auth - Azure:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize - AWS Cognito:
https://{domain}.auth.{region}.amazoncognito.com/oauth2/authorize - Discord:
https://discord.com/api/oauth2/authorize - Facebook:
https://www.facebook.com/v12.0/dialog/oauth - WorkOS:企业身份认证
- AuthKit:身份验证工具包
- Descope:身份验证平台
- Scalekit:企业 SSO
图标支持
为服务、工具、资源和提示词添加可视化表示,以提升 MCP 客户端的用户体验。
服务级图标
from fastmcp import FastMCP, Icon
mcp = FastMCP(
name="Weather Service",
website_url="https://weather.example.com",
icons=[
Icon(
url="https://example.com/icon-small.png",
size="small"
),
Icon(
url="https://example.com/icon-large.png",
size="large"
)
]
)
组件级图标
from fastmcp import Icon
@mcp.tool(icons=[
Icon(url="https://example.com/tool-icon.png")
])
async def analyze_data(data: str) -> dict:
"""Analyze data with visual icon."""
return {"result": "analyzed"}
@mcp.resource(
"user://{user_id}/profile",
icons=[Icon(url="https://example.com/user-icon.png")]
)
async def get_user(user_id: str) -> dict:
"""User profile with icon."""
return {"id": user_id, "name": "Alice"}
@mcp.prompt(
"analyze",
icons=[Icon(url="https://example.com/prompt-icon.png")]
)
def analysis_prompt(topic: str) -> str:
"""Analysis prompt with icon."""
return f"Analyze {topic}"
数据 URI 支持
直接嵌入图像(适用于独立部署):
from fastmcp import Icon, Image
# Convert local file to data URI
icon = Icon.from_file("/path/to/icon.png", size="medium")
# Or use Image utility
image_data_uri = Image.to_data_uri("/path/to/icon.png")
icon = Icon(url=image_data_uri, size="medium")
# Use in server
mcp = FastMCP(
"My Server",
icons=[icon]
)
多种尺寸
为不同场景提供不同尺寸:
mcp = FastMCP(
"Responsive Server",
icons=[
Icon(url="icon-16.png", size="small"), # 16x16
Icon(url="icon-32.png", size="medium"), # 32x32
Icon(url="icon-64.png", size="large"), # 64x64
]
)
API 集成
FastMCP 提供多种 API 集成模式:
模式 1:手动集成 API
import httpx
import os
# Create reusable client
client = httpx.AsyncClient(
base_url=os.getenv("API_BASE_URL"),
headers={"Authorization": f"Bearer {os.getenv('API_KEY')}"},
timeout=30.0
)
@mcp.tool()
async def fetch_data(endpoint: str) -> dict:
"""Fetch data from API."""
try:
response = await client.get(endpoint)
response.raise_for_status()
return {"success": True, "data": response.json()}
except httpx.HTTPStatusError as e:
return {"error": f"HTTP {e.response.status_code}"}
except Exception as e:
return {"error": str(e)}
模式 2:自动生成 OpenAPI/Swagger
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
import httpx
# Load OpenAPI spec
spec = httpx.get("https://api.example.com/openapi.json").json()
# Create authenticated client
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": f"Bearer {API_TOKEN}"},
timeout=30.0
)
# Auto-generate MCP server from OpenAPI
mcp = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
name="API Server",
route_maps=[
# GET with parameters → Resource Templates
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE
),
# GET without parameters → Resources
RouteMap(
methods=["GET"],
mcp_type=MCPType.RESOURCE
),
# POST/PUT/DELETE → Tools
RouteMap(
methods=["POST", "PUT", "DELETE"],
mcp_type=MCPType.TOOL
),
]
)
# Optionally add custom tools
@mcp.tool()
async def custom_operation(data: dict) -> dict:
"""Custom tool on top of generated ones."""
return process_data(data)
模式 3:转换 FastAPI
from fastapi import FastAPI
from fastmcp import FastMCP
# Existing FastAPI app
app = FastAPI()
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"id": item_id, "name": "Item"}
# Convert to MCP server
mcp = FastMCP.from_fastapi(
app=app,
httpx_client_kwargs={
"headers": {"Authorization": "Bearer token"}
}
)
云部署(FastMCP Cloud)
关键要求
**❗️ 重要:**以下要求是 FastMCP Cloud 的强制要求:
- 模块级服务对象,名称为
mcp、server或app - 仅使用 PyPI 依赖项,写入 requirements.txt
- 公开的 GitHub 仓库(或可供 FastMCP Cloud 访问)
- 使用环境变量进行配置
云就绪服务模式
# server.py
from fastmcp import FastMCP
import os
# ✅ CORRECT: Module-level server object
mcp = FastMCP(
name="production-server"
)
# ✅ Use environment variables
API_KEY = os.getenv("API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
@mcp.tool()
async def production_tool(data: str) -> dict:
"""Production-ready tool."""
if not API_KEY:
return {"error": "API_KEY not configured"}
# Your implementation
return {"status": "success", "data": data}
# ✅ Optional: for local testing
if __name__ == "__main__":
mcp.run()
常见云部署错误
❌ 错误:用函数包装服务
def create_server():
mcp = FastMCP("my-server")
return mcp
if __name__ == "__main__":
server = create_server() # Too late for cloud!
server.run()
✅ 正确:使用工厂并导出模块
def create_server() -> FastMCP:
mcp = FastMCP("my-server")
# Complex setup logic
return mcp
# Export at module level
mcp = create_server()
if __name__ == "__main__":
mcp.run()
部署步骤
- 准备仓库:
git init
git add .
git commit -m "Initial MCP server"
gh repo create my-mcp-server --public
git push -u origin main
-
在 FastMCP Cloud 上部署:
- 访问 https://fastmcp.cloud
- 使用 GitHub 登录
- 点击“Create Project”
- 选择你的仓库
- 配置:
- 服务名称:你的项目名称
- 入口点:
server.py - 环境变量:添加所需变量
-
访问你的服务:
- URL:
https://your-project.fastmcp.app/mcp - 推送到 main 时自动部署
- PR 预览部署
- URL:
客户端配置
Claude Desktop
添加到 claude_desktop_config.json:
{
"mcpServers": {
"my-server": {
"url": "https://your-project.fastmcp.app/mcp",
"transport": "http"
}
}
}
本地开发
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/absolute/path/to/server.py"],
"env": {
"API_KEY": "your-key",
"DATABASE_URL": "your-db-url"
}
}
}
}
Claude Code CLI
{
"mcpServers": {
"my-server": {
"command": "uv",
"args": ["run", "python", "/absolute/path/to/server.py"]
}
}
}
25 个常见错误(及解决方案)
错误 1:缺少服务对象
错误:
RuntimeError: No server object found at module level
**原因:**服务对象未在模块级导出(FastMCP Cloud 的要求)
解决方案:
# ❌ WRONG
def create_server():
return FastMCP("server")
# ✅ CORRECT
mcp = FastMCP("server") # At module level
**来源:**FastMCP Cloud 文档、部署失败案例
错误 2:Async/Await 混淆
错误:
RuntimeError: no running event loop
TypeError: object coroutine can't be used in 'await' expression
**原因:**同步与异步的使用方式混淆
解决方案:
# ❌ WRONG: Sync function calling async
@mcp.tool()
def bad_tool():
result = await async_function() # Error!
# ✅ CORRECT: Async tool
@mcp.tool()
async def good_tool():
result = await async_function()
return result
# ✅ CORRECT: Sync tool with sync code
@mcp.tool()
def sync_tool():
return "Hello"
**来源:**GitHub issues #156、#203
错误 3:未注入上下文
错误:
TypeError: missing 1 required positional argument: 'context'
**原因:**上下文参数缺少 Context 类型注解
解决方案:
from fastmcp import Context
# ❌ WRONG: No type hint
@mcp.tool()
async def bad_tool(context): # Missing type!
await context.report_progress(...)
# ✅ CORRECT: Proper type hint
@mcp.tool()
async def good_tool(context: Context):
await context.report_progress(0, 100, "Starting")
**来源:**FastMCP v2 迁移指南
错误 4:资源 URI 语法
错误:
ValueError: Invalid resource URI: missing scheme
**原因:**资源 URI 缺少 scheme 前缀
解决方案:
# ❌ WRONG: Missing scheme
@mcp.resource("config")
def get_config(): pass
# ✅ CORRECT: Include scheme
@mcp.resource("data://config")
def get_config(): pass
# ✅ Valid schemes
@mcp.resource("file://config.json")
@mcp.resource("api://status")
@mcp.resource("info://health")
**来源:**MCP 协议规范
错误 5:资源模板参数不匹配
错误:
TypeError: get_user() missing 1 required positional argument: 'user_id'
**原因:**函数参数名称与 URI 模板不匹配
解决方案:
# ❌ WRONG: Parameter name mismatch
@mcp.resource("user://{user_id}/profile")
def get_user(id: str): # Wrong name!
pass
# ✅ CORRECT: Matching names
@mcp.resource("user://{user_id}/profile")
def get_user(user_id: str): # Matches {user_id}
return {"id": user_id}
**来源:**FastMCP 模式文档
错误 6:Pydantic 验证错误
错误:
ValidationError: value is not a valid integer
**原因:**类型提示与提供的数据不匹配
解决方案:
from pydantic import BaseModel, Field
# ✅ Use Pydantic models for complex validation
class SearchParams(BaseModel):
query: str = Field(min_length=1, max_length=100)
limit: int = Field(default=10, ge=1, le=100)
@mcp.tool()
async def search(params: SearchParams) -> dict:
# Validation automatic
return await perform_search(params.query, params.limit)
**来源:**Pydantic 文档、FastMCP 示例
错误 7:传输方式/协议不匹配
错误:
ConnectionError: Server using different transport
**原因:**客户端和服务使用不兼容的传输方式
解决方案:
# Server using stdio (default)
mcp.run() # or mcp.run(transport="stdio")
# Client configuration must match
{
"command": "python",
"args": ["server.py"]
}
# OR for HTTP:
mcp.run(transport="http", port=8000)
# Client:
{
"url": "http://localhost:8000/mcp",
"transport": "http"
}
**来源:**MCP 传输规范
错误 8:导入错误(可编辑包)
错误:
ModuleNotFoundError: No module named 'my_package'
**原因:**包未以可编辑模式正确安装
解决方案:
# ✅ Install in editable mode
pip install -e .
# ✅ Or use absolute imports
from src.tools import my_tool
# ✅ Or add to PYTHONPATH
export PYTHONPATH="${PYTHONPATH}:/path/to/project"
**来源:**Python 打包文档
错误 9:弃用警告
错误:
DeprecationWarning: 'mcp.settings' is deprecated, use global Settings instead
**原因:**使用旧版 FastMCP v1 API
解决方案:
# ❌ OLD: FastMCP v1
from fastmcp import FastMCP
mcp = FastMCP()
api_key = mcp.settings.get("API_KEY")
# ✅ NEW: FastMCP v2
import os
api_key = os.getenv("API_KEY")
**来源:**FastMCP v2 迁移指南
错误 10:端口已被占用
错误:
OSError: [Errno 48] Address already in use
**原因:**端口 8000 已被占用
解决方案:
# ✅ Use different port
python server.py --transport http --port 8001
# ✅ Or kill process on port
lsof -ti:8000 | xargs kill -9
**来源:**常见网络问题
错误 11:架构生成失败
错误:
TypeError: Object of type 'ndarray' is not JSON serializable
**原因:**不支持的类型提示(NumPy 数组、自定义类)
解决方案:
# ❌ WRONG: NumPy array
import numpy as np
@mcp.tool()
def bad_tool() -> np.ndarray: # Not JSON serializable
return np.array([1, 2, 3])
# ✅ CORRECT: Use JSON-compatible types
@mcp.tool()
def good_tool() -> list[float]:
return [1.0, 2.0, 3.0]
# ✅ Or convert to dict
@mcp.tool()
def array_tool() -> dict:
data = np.array([1, 2, 3])
return {"values": data.tolist()}
**来源:**JSON 序列化要求
错误 12:JSON 序列化
错误:
TypeError: Object of type 'datetime' is not JSON serializable
**原因:**返回无法进行 JSON 序列化的对象
解决方案:
from datetime import datetime
# ❌ WRONG: Return datetime object
@mcp.tool()
def bad_tool() -> dict:
return {"timestamp": datetime.now()} # Not serializable
# ✅ CORRECT: Convert to string
@mcp.tool()
def good_tool() -> dict:
return {"timestamp": datetime.now().isoformat()}
# ✅ Use helper function
def make_serializable(obj):
"""Convert object to JSON-serializable format."""
if isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, bytes):
return obj.decode('utf-8')
# Add more conversions as needed
return obj
**来源:**JSON 规范
错误 13:循环导入错误
错误:
ImportError: cannot import name 'X' from partially initialized module
**原因:**模块相互导入,造成循环依赖(云部署中很常见)
解决方案:
# ❌ WRONG: Factory function in __init__.py
# shared/__init__.py
_client = None
def get_api_client():
from .api_client import APIClient # Circular!
return APIClient()
# shared/monitoring.py
from . import get_api_client # Creates circle
# ✅ CORRECT: Direct imports
# shared/__init__.py
from .api_client import APIClient
from .cache import CacheManager
# shared/monitoring.py
from .api_client import APIClient
client = APIClient() # Create directly
# ✅ ALTERNATIVE: Lazy import
# shared/monitoring.py
def get_client():
from .api_client import APIClient
return APIClient()
**来源:**生产环境云部署错误、Python 导入系统
错误 14:Python 版本兼容性
错误:
DeprecationWarning: datetime.utcnow() is deprecated
**原因:**使用已弃用的 Python 3.12+ 方法
解决方案:
# ❌ DEPRECATED (Python 3.12+)
from datetime import datetime
timestamp = datetime.utcnow()
# ✅ CORRECT: Future-proof
from datetime import datetime, timezone
timestamp = datetime.now(timezone.utc)
**来源:**Python 3.12 发行说明
错误 15:导入时执行
错误:
RuntimeError: Event loop is closed
**原因:**在模块导入时创建异步资源
解决方案:
# ❌ WRONG: Module-level async execution
import asyncpg
connection = asyncpg.connect('postgresql://...') # Runs at import!
# ✅ CORRECT: Lazy initialization
import asyncpg
class Database:
connection = None
@classmethod
async def connect(cls):
if cls.connection is None:
cls.connection = await asyncpg.connect('postgresql://...')
return cls.connection
# Usage: connection happens when needed, not at import
@mcp.tool()
async def get_users():
conn = await Database.connect()
return await conn.fetch("SELECT * FROM users")
**来源:**异步事件循环管理、云部署要求
错误 16:未配置存储后端
错误:
RuntimeError: OAuth tokens lost on restart
ValueError: Cache not persisting across server instances
**原因:**在生产环境中使用默认的内存存储,且未配置持久化
解决方案:
# ❌ WRONG: Memory storage in production
mcp = FastMCP("Production Server") # Tokens lost on restart!
# ✅ CORRECT: Use disk or Redis storage
from key_value.stores import DiskStore, RedisStore
from key_value.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Disk storage (single instance)
mcp = FastMCP(
"Production Server",
storage=FernetEncryptionWrapper(
key_value=DiskStore(path="/var/lib/mcp/storage"),
fernet=Fernet(os.getenv("STORAGE_ENCRYPTION_KEY"))
)
)
# Redis storage (multi-instance)
mcp = FastMCP(
"Production Server",
storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.getenv("REDIS_HOST"),
password=os.getenv("REDIS_PASSWORD")
),
fernet=Fernet(os.getenv("STORAGE_ENCRYPTION_KEY"))
)
)
**来源:**FastMCP v2.13.0 存储后端文档
错误 17:未将 Lifespan 传递给 ASGI 应用
错误:
RuntimeError: Database connection never initialized
Warning: MCP lifespan hooks not running
**原因:**使用 FastMCP 和 FastAPI/Starlette 时未传递 lifespan
解决方案:
from fastapi import FastAPI
from fastmcp import FastMCP
# ❌ WRONG: Lifespan not passed
mcp = FastMCP("My Server", lifespan=my_lifespan)
app = FastAPI() # MCP lifespan won't run!
# ✅ CORRECT: Pass MCP lifespan to parent app
mcp = FastMCP("My Server", lifespan=my_lifespan)
app = FastAPI(lifespan=mcp.lifespan)
**来源:**FastMCP v2.13.0 重大变更、ASGI 集成指南
错误 18:中间件执行顺序错误
错误:
RuntimeError: Rate limit not checked before caching
AttributeError: Context state not available in middleware
**原因:**中间件顺序不正确(顺序很重要!)
解决方案:
# ❌ WRONG: Cache before rate limiting
mcp.add_middleware(ResponseCachingMiddleware())
mcp.add_middleware(RateLimitingMiddleware()) # Too late!
# ✅ CORRECT: Rate limit before cache
mcp.add_middleware(ErrorHandlingMiddleware()) # First: catch errors
mcp.add_middleware(TimingMiddleware()) # Second: time requests
mcp.add_middleware(LoggingMiddleware()) # Third: log
mcp.add_middleware(RateLimitingMiddleware()) # Fourth: check limits
mcp.add_middleware(ResponseCachingMiddleware()) # Last: cache
**来源:**FastMCP 中间件文档、最佳实践
错误 19:循环中间件依赖
错误:
RecursionError: maximum recursion depth exceeded
RuntimeError: Middleware loop detected
**原因:**不正确地调用 self.next(),或存在循环依赖
解决方案:
# ❌ WRONG: Not calling next() or calling incorrectly
class BadMiddleware(BaseMiddleware):
async def on_call_tool(self, tool_name, arguments, context):
# Forgot to call next()!
return {"error": "blocked"}
# ✅ CORRECT: Always call next() to continue chain
class GoodMiddleware(BaseMiddleware):
async def on_call_tool(self, tool_name, arguments, context):
# Do preprocessing
print(f"Before: {tool_name}")
# MUST call next() to continue
result = await self.next(tool_name, arguments, context)
# Do postprocessing
print(f"After: {tool_name}")
return result
**来源:**FastMCP 中间件系统文档
错误 20:混淆 Import 与 Mount
错误:
RuntimeError: Subserver changes not reflected
ValueError: Unexpected tool namespacing
**原因:**需要使用 mount() 时却使用了 import_server()(反之亦然)
解决方案:
# ❌ WRONG: Using import when you want dynamic updates
main_server.import_server(subserver)
# Later: changes to subserver won't appear in main_server
# ✅ CORRECT: Use mount() for dynamic composition
main_server.mount(subserver, prefix="sub")
# Changes to subserver are immediately visible
# ❌ WRONG: Using mount when you want static bundle
main_server.mount(third_party_server, prefix="vendor")
# Runtime overhead for static components
# ✅ CORRECT: Use import_server() for static bundles
main_server.import_server(third_party_server)
# One-time copy, no runtime delegation
**来源:**FastMCP 服务组合模式
错误 21:资源前缀格式不匹配
错误:
ValueError: Resource not found: resource://api/users
ValueError: Unexpected resource URI format
**原因:**使用了错误的资源前缀格式(路径格式而非协议格式)
解决方案:
# Path format (default since v2.4.0)
main_server.mount(api_server, prefix="api")
# Resources: resource://api/users
# ❌ WRONG: Expecting protocol format
# resource://api+users (doesn't exist)
# ✅ CORRECT: Use path format
uri = "resource://api/users"
# OR explicitly set protocol format (legacy)
main_server.mount(
api_server,
prefix="api",
resource_prefix_format="protocol"
)
# Resources: api+resource://users
**来源:**FastMCP v2.4.0+ 资源前缀变更
错误 22:OAuth 代理未配置同意屏幕
错误:
SecurityWarning: Authorization bypass possible
RuntimeError: Confused deputy attack vector
**原因:**OAuth 代理未配置同意屏幕(存在安全漏洞)
解决方案:
# ❌ WRONG: No consent screen (security risk!)
auth = OAuthProxy(
jwt_signing_key=os.getenv("JWT_KEY"),
upstream_authorization_endpoint="...",
upstream_token_endpoint="...",
# Missing: enable_consent_screen
)
# ✅ CORRECT: Enable consent screen
auth = OAuthProxy(
jwt_signing_key=os.getenv("JWT_KEY"),
upstream_authorization_endpoint="...",
upstream_token_endpoint="...",
enable_consent_screen=True # Prevents bypass attacks
)
**来源:**FastMCP v2.13.0 OAuth 安全增强、RFC 7662
错误 23:生产环境缺少 JWT 签名密钥
错误:
ValueError: JWT signing key required for OAuth Proxy
RuntimeError: Cannot issue tokens without signing key
**原因:**生产环境中的 OAuth 代理缺少 jwt_signing_key
解决方案:
# ❌ WRONG: No JWT signing key
auth = OAuthProxy(
upstream_authorization_endpoint="...",
upstream_token_endpoint="...",
# Missing: jwt_signing_key
)
# ✅ CORRECT: Provide signing key from environment
import secrets
# Generate once (in setup):
# signing_key = secrets.token_urlsafe(32)
# Store in: FASTMCP_JWT_SIGNING_KEY environment variable
auth = OAuthProxy(
jwt_signing_key=os.environ["FASTMCP_JWT_SIGNING_KEY"],
client_storage=encrypted_storage,
upstream_authorization_endpoint="...",
upstream_token_endpoint="...",
upstream_client_id=os.getenv("OAUTH_CLIENT_ID"),
upstream_client_secret=os.getenv("OAUTH_CLIENT_SECRET")
)
**来源:**OAuth 代理生产环境要求
错误 24:图标数据 URI 格式错误
错误:
ValueError: Invalid data URI format
TypeError: Icon URL must be string or data URI
**原因:**图标的数据 URI 格式不正确
解决方案:
from fastmcp import Icon, Image
# ❌ WRONG: Invalid data URI
icon = Icon(url="base64,iVBORw0KG...") # Missing data:image/png;
# ✅ CORRECT: Use Image utility
icon = Icon.from_file("/path/to/icon.png", size="medium")
# ✅ CORRECT: Manual data URI
import base64
with open("/path/to/icon.png", "rb") as f:
image_data = base64.b64encode(f.read()).decode()
data_uri = f"data:image/png;base64,{image_data}"
icon = Icon(url=data_uri, size="medium")
**来源:**FastMCP 图标文档、数据 URI 规范
错误 25:Lifespan 行为变更(v2.13.0)
错误:
Warning: Lifespan runs per-server, not per-session
RuntimeError: Resources initialized multiple times
**原因:**在 v2.13.0+ 中仍预期使用 v2.12 的 lifespan 行为(按会话,而非按服务)
解决方案:
# v2.12.0 and earlier: Lifespan ran per client session
# v2.13.0+: Lifespan runs once per server instance
# ✅ CORRECT: v2.13.0+ pattern (per-server)
@asynccontextmanager
async def app_lifespan(server: FastMCP):
"""Runs ONCE when server starts, not per client session."""
db = await Database.connect()
print("Server starting - runs once")
try:
yield {"db": db}
finally:
await db.disconnect()
print("Server stopping - runs once")
mcp = FastMCP("My Server", lifespan=app_lifespan)
# For per-session logic, use middleware instead:
class SessionMiddleware(BaseMiddleware):
async def on_message(self, message, context):
# Runs per client message
session_id = context.fastmcp_context.get_state("session_id")
if not session_id:
session_id = str(uuid.uuid4())
context.fastmcp_context.set_state("session_id", session_id)
return await self.next(message, context)
**来源:**FastMCP v2.13.0 发行说明、重大变更文档
生产环境模式
模式 1:独立的 Utils 模块
将所有工具函数集中在一处的最佳实践:
# src/utils.py - Single file with all utilities
import os
from typing import Dict, Any
from datetime import datetime
class Config:
"""Application configuration."""
SERVER_NAME = os.getenv("SERVER_NAME", "FastMCP Server")
SERVER_VERSION = "1.0.0"
API_BASE_URL = os.getenv("API_BASE_URL")
API_KEY = os.getenv("API_KEY")
CACHE_TTL = int(os.getenv("CACHE_TTL", "300"))
def format_success(data: Any, message: str = "Success") -> Dict[str, Any]:
"""Format successful response."""
return {
"success": True,
"message": message,
"data": data,
"timestamp": datetime.now().isoformat()
}
def format_error(error: str, code: str = "ERROR") -> Dict[str, Any]:
"""Format error response."""
return {
"success": False,
"error": error,
"code": code,
"timestamp": datetime.now().isoformat()
}
# Usage in tools
from .utils import format_success, format_error, Config
@mcp.tool()
async def process_data(data: dict) -> dict:
try:
result = await process(data)
return format_success(result)
except Exception as e:
return format_error(str(e))
模式 2:连接池
高效的资源管理:
import httpx
from typing import Optional
class APIClient:
_instance: Optional[httpx.AsyncClient] = None
@classmethod
async def get_client(cls) -> httpx.AsyncClient:
if cls._instance is None:
cls._instance = httpx.AsyncClient(
base_url=os.getenv("API_BASE_URL"),
headers={"Authorization": f"Bearer {os.getenv('API_KEY')}"},
timeout=httpx.Timeout(30.0),
limits=httpx.Limits(max_keepalive_connections=5)
)
return cls._instance
@classmethod
async def cleanup(cls):
if cls._instance:
await cls._instance.aclose()
cls._instance = None
@mcp.tool()
async def api_request(endpoint: str) -> dict:
"""Make API request with managed client."""
client = await APIClient.get_client()
response = await client.get(endpoint)
return response.json()
模式 3:带重试的错误处理
弹性的 API 调用:
import asyncio
from typing import Callable, TypeVar
T = TypeVar('T')
async def retry_with_backoff(
func: Callable[[], T],
max_retries: int = 3,
initial_delay: float = 1.0,
exponential_base: float = 2.0
) -> T:
"""Retry function with exponential backoff."""
delay = initial_delay
last_exception = None
for attempt in range(max_retries):
try:
return await func()
except Exception as e:
last_exception = e
if attempt < max_retries - 1:
await asyncio.sleep(delay)
delay *= exponential_base
raise last_exception
@mcp.tool()
async def resilient_api_call(endpoint: str) -> dict:
"""API call with automatic retry."""
async def make_call():
async with httpx.AsyncClient() as client:
response = await client.get(endpoint)
response.raise_for_status()
return response.json()
try:
data = await retry_with_backoff(make_call)
return {"success": True, "data": data}
except Exception as e:
return {"error": f"Failed after retries: {e}"}
模式 4:基于时间的缓存
减少 API 负载:
import time
from typing import Any, Optional
class TimeBasedCache:
def __init__(self, ttl: int = 300):
self.ttl = ttl
self.cache = {}
self.timestamps = {}
def get(self, key: str) -> Optional[Any]:
if key in self.cache:
if time.time() - self.timestamps[key] < self.ttl:
return self.cache[key]
else:
del self.cache[key]
del self.timestamps[key]
return None
def set(self, key: str, value: Any):
self.cache[key] = value
self.timestamps[key] = time.time()
cache = TimeBasedCache(ttl=300)
@mcp.tool()
async def cached_fetch(resource_id: str) -> dict:
"""Fetch with caching."""
cache_key = f"resource:{resource_id}"
cached_data = cache.get(cache_key)
if cached_data:
return {"data": cached_data, "from_cache": True}
data = await fetch_from_api(resource_id)
cache.set(cache_key, data)
return {"data": data, "from_cache": False}
测试
单元测试工具
import pytest
from fastmcp import FastMCP
from fastmcp.testing import create_test_client
@pytest.fixture
def test_server():
"""Create test server instance."""
mcp = FastMCP("test-server")
@mcp.tool()
async def test_tool(param: str) -> str:
return f"Result: {param}"
return mcp
@pytest.mark.asyncio
async def test_tool_execution(test_server):
"""Test tool execution."""
async with create_test_client(test_server) as client:
result = await client.call_tool("test_tool", {"param": "test"})
assert result.data == "Result: test"
集成测试
import asyncio
from fastmcp import Client
async def test_server():
"""Test all server functionality."""
async with Client("server.py") as client:
# Test tools
tools = await client.list_tools()
print(f"Tools: {len(tools)}")
for tool in tools:
try:
result = await client.call_tool(tool.name, {})
print(f"✓ {tool.name}: {result}")
except Exception as e:
print(f"✗ {tool.name}: {e}")
# Test resources
resources = await client.list_resources()
for resource in resources:
try:
data = await client.read_resource(resource.uri)
print(f"✓ {resource.uri}")
except Exception as e:
print(f"✗ {resource.uri}: {e}")
if __name__ == "__main__":
asyncio.run(test_server())
CLI 命令
开发:
# Run with inspector (recommended)
fastmcp dev server.py
# Run normally
fastmcp run server.py
# Inspect server without running
fastmcp inspect server.py
安装:
# Install to Claude Desktop
fastmcp install server.py
# Install with custom name
fastmcp install server.py --name "My Server"
调试:
# Enable debug logging
FASTMCP_LOG_LEVEL=DEBUG fastmcp dev server.py
# Run with HTTP transport
fastmcp run server.py --transport http --port 8000
最佳实践
1. 服务结构
from fastmcp import FastMCP
import os
def create_server() -> FastMCP:
"""Factory function for complex setup."""
mcp = FastMCP("Server Name")
# Configure server
setup_tools(mcp)
setup_resources(mcp)
return mcp
def setup_tools(mcp: FastMCP):
"""Register all tools."""
@mcp.tool()
def example_tool():
pass
def setup_resources(mcp: FastMCP):
"""Register all resources."""
@mcp.resource("data://config")
def get_config():
return {"version": "1.0.0"}
# Export at module level
mcp = create_server()
if __name__ == "__main__":
mcp.run()
2. 环境配置
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
API_KEY = os.getenv("API_KEY", "")
BASE_URL = os.getenv("BASE_URL", "https://api.example.com")
DEBUG = os.getenv("DEBUG", "false").lower() == "true"
@classmethod
def validate(cls):
if not cls.API_KEY:
raise ValueError("API_KEY is required")
return True
# Validate on startup
Config.validate()
3. 文档
@mcp.tool()
def complex_tool(
query: str,
filters: dict = None,
limit: int = 10
) -> dict:
"""
Search with advanced filtering.
Args:
query: Search query string
filters: Optional filters dict with keys:
- category: Filter by category
- date_from: Start date (ISO format)
- date_to: End date (ISO format)
limit: Maximum results (1-100)
Returns:
Dict with 'results' list and 'total' count
Examples:
>>> complex_tool("python", {"category": "tutorial"}, 5)
{'results': [...], 'total': 5}
"""
pass
4. 健康检查
@mcp.resource("health://status")
async def health_check() -> dict:
"""Comprehensive health check."""
checks = {}
# Check API connectivity
try:
async with httpx.AsyncClient() as client:
response = await client.get(f"{BASE_URL}/health", timeout=5)
checks["api"] = response.status_code == 200
except:
checks["api"] = False
# Check database
try:
checks["database"] = await check_db_connection()
except:
checks["database"] = False
all_healthy = all(checks.values())
return {
"status": "healthy" if all_healthy else "degraded",
"timestamp": datetime.now().isoformat(),
"checks": checks
}
项目结构
简单服务
my-mcp-server/
├── server.py # Main server file
├── requirements.txt # Dependencies
├── .env # Environment variables (git-ignored)
├── .gitignore # Git ignore file
└── README.md # Documentation
生产环境服务
my-mcp-server/
├── src/
│ ├── server.py # Main entry point
│ ├── utils.py # Shared utilities
│ ├── tools/ # Tool modules
│ │ ├── __init__.py
│ │ ├── api_tools.py
│ │ └── data_tools.py
│ ├── resources/ # Resource definitions
│ │ ├── __init__.py
│ │ └── static.py
│ └── prompts/ # Prompt templates
│ ├── __init__.py
│ └── templates.py
├── tests/
│ ├── test_tools.py
│ └── test_resources.py
├── requirements.txt
├── pyproject.toml
├── .env
├── .gitignore
└── README.md
参考资料
官方文档:
- FastMCP:https://github.com/jlowin/fastmcp
- FastMCP Cloud:https://fastmcp.cloud
- MCP Protocol:https://modelcontextprotocol.io
- Context7 Docs:
/jlowin/fastmcp
相关技能:
openai-api- OpenAI 集成claude-api- Claude APIcloudflare-worker-base- 将 MCP 部署为 Worker
软件包版本:
- fastmcp >= 2.13.0
- Python >= 3.10
- httpx(推荐用于异步 API 调用)
- pydantic(用于验证)
- py-key-value-aio(用于存储后端)
- cryptography(用于加密存储)
总结
FastMCP 支持快速开发可用于生产环境的 MCP 服务,并提供存储、身份验证、中间件和组合等高级功能。要点如下:
- 始终在模块级别导出服务,以兼容 FastMCP Cloud
- 在生产环境中使用持久化存储后端(Disk/Redis)存储 OAuth 令牌和缓存
- 配置服务生命周期,以妥善管理资源(数据库连接、API 客户端)
- 有策略地添加中间件——顺序很重要!(错误 → 计时 → 日志记录 → 速率限制 → 缓存)
- 明智地选择组合方式——静态捆绑使用
import_server(),动态组合使用mount() - 妥善保护 OAuth——启用同意页面、加密令牌存储、使用 JWT 签名密钥
- 正确使用 async/await——不要阻塞事件循环
- 使用结构化响应和 ErrorHandlingMiddleware 优雅地处理错误
- 避免循环导入,尤其是使用工厂函数时
- 使用
fastmcp dev在部署前进行本地测试 - 使用环境变量进行所有配置(绝不硬编码密钥)
- 完善文档——大模型会读取你的文档字符串
- 遵循生产环境模式,编写自包含且易于维护的代码
- 利用 OpenAPI 快速集成 API
- 通过健康检查和中间件进行监控,确保生产环境的可靠性
生产就绪性:
- 存储:对 OAuth 令牌和响应缓存进行加密持久化
- 身份验证:4 种身份验证模式(令牌验证、远程 OAuth、OAuth 代理、完整 OAuth)
- 中间件:8 种内置类型,用于日志记录、速率限制、缓存和错误处理
- 组合:采用导入/挂载策略的模块化服务架构
- 安全性:同意页面、PKCE、RFC 7662 令牌内省、加密存储
- 性能:响应缓存、连接池、计时中间件
与手动实现相比,这项技能可避免 25 种以上的常见错误,并节省 90%~95% 的令牌。