mcpField
F
fastmcp

fastmcp

github.com/jackspace开发1641 次安装12 周前更新
代码仓库

关于 fastmcp

fastmcp 是面向开发者的 Python 框架技能,用于构建和运维 MCP 服务。内容涵盖服务组件、中间件、存储、上下文功能及部署模式。

主要功能

  1. 01定义工具、资源和提示词
  2. 02添加用户输入与进度更新
  3. 03配置持久化存储后端
  4. 04使用内置或自定义中间件
  5. 05通过导入或挂载组合服务

适用场景

  1. 01构建 Python MCP 服务
  2. 02为长时间运行的工具报告进度
  3. 03持久化 OAuth 令牌或缓存响应
  4. 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 种)
  1. TimingMiddleware - 性能监控
  2. ResponseCachingMiddleware - 基于 TTL 的缓存,支持可插拔存储
  3. LoggingMiddleware - 易读日志和 JSON 结构化日志
  4. RateLimitingMiddleware - 令牌桶和滑动窗口算法
  5. ErrorHandlingMiddleware - 一致的错误管理
  6. ToolInjectionMiddleware - 动态注入工具
  7. PromptToolMiddleware - 为功能受限的客户端提供基于工具的提示词访问
  8. 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"]
))
钩子层级

中间件钩子从最通用到最具体依次为:

  1. on_message - 所有消息(请求和通知)
  2. on_request / on_notification - 按消息类型区分
  3. on_call_tool、on_read_resource、on_get_prompt - 特定操作
  4. 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)的提供方。

四种身份验证模式
  1. 令牌验证(TokenVerifier/JWTVerifier)- 验证外部令牌
  2. 外部身份提供方(RemoteAuthProvider)- 支持 DCR 的 OAuth 2.0/OIDC
  3. OAuth 代理(OAuthProxy)- 对接传统 OAuth 提供方
  4. 完整 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 的强制要求:

  1. 模块级服务对象,名称为 mcp、server 或 app
  2. 仅使用 PyPI 依赖项,写入 requirements.txt
  3. 公开的 GitHub 仓库(或可供 FastMCP Cloud 访问)
  4. 使用环境变量进行配置
云就绪服务模式
# 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()
部署步骤
  1. 准备仓库:
git init
git add .
git commit -m "Initial MCP server"
gh repo create my-mcp-server --public
git push -u origin main
  1. 在 FastMCP Cloud 上部署:

    • 访问 https://fastmcp.cloud
    • 使用 GitHub 登录
    • 点击“Create Project”
    • 选择你的仓库
    • 配置:
      • 服务名称:你的项目名称
      • 入口点:server.py
      • 环境变量:添加所需变量
  2. 访问你的服务:

    • URL:https://your-project.fastmcp.app/mcp
    • 推送到 main 时自动部署
    • PR 预览部署

客户端配置

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

参考资料

官方文档:

相关技能:

  • openai-api - OpenAI 集成
  • claude-api - Claude API
  • cloudflare-worker-base - 将 MCP 部署为 Worker

软件包版本:

  • fastmcp >= 2.13.0
  • Python >= 3.10
  • httpx(推荐用于异步 API 调用)
  • pydantic(用于验证)
  • py-key-value-aio(用于存储后端)
  • cryptography(用于加密存储)

总结

FastMCP 支持快速开发可用于生产环境的 MCP 服务,并提供存储、身份验证、中间件和组合等高级功能。要点如下:

  1. 始终在模块级别导出服务,以兼容 FastMCP Cloud
  2. 在生产环境中使用持久化存储后端(Disk/Redis)存储 OAuth 令牌和缓存
  3. 配置服务生命周期,以妥善管理资源(数据库连接、API 客户端)
  4. 有策略地添加中间件——顺序很重要!(错误 → 计时 → 日志记录 → 速率限制 → 缓存)
  5. 明智地选择组合方式——静态捆绑使用 import_server(),动态组合使用 mount()
  6. 妥善保护 OAuth——启用同意页面、加密令牌存储、使用 JWT 签名密钥
  7. 正确使用 async/await——不要阻塞事件循环
  8. 使用结构化响应和 ErrorHandlingMiddleware 优雅地处理错误
  9. 避免循环导入,尤其是使用工厂函数时
  10. 使用 fastmcp dev 在部署前进行本地测试
  11. 使用环境变量进行所有配置(绝不硬编码密钥)
  12. 完善文档——大模型会读取你的文档字符串
  13. 遵循生产环境模式,编写自包含且易于维护的代码
  14. 利用 OpenAPI 快速集成 API
  15. 通过健康检查和中间件进行监控,确保生产环境的可靠性

生产就绪性:

  • 存储:对 OAuth 令牌和响应缓存进行加密持久化
  • 身份验证:4 种身份验证模式(令牌验证、远程 OAuth、OAuth 代理、完整 OAuth)
  • 中间件:8 种内置类型,用于日志记录、速率限制、缓存和错误处理
  • 组合:采用导入/挂载策略的模块化服务架构
  • 安全性:同意页面、PKCE、RFC 7662 令牌内省、加密存储
  • 性能:响应缓存、连接池、计时中间件

与手动实现相比,这项技能可避免 25 种以上的常见错误,并节省 90%~95% 的令牌。

评论

支持 Markdown