07_FastMCP 2.x 中文文档之FastMCP服务端核心组件:资源与模板详解

一、资源与模板概述

资源代表MCP客户端可以读取的数据或文件,而资源模板通过允许客户端基于URI中传递的参数请求动态生成的资源来扩展这一概念。

FastMCP简化了静态和动态资源的定义,主要使用@mcp.resource装饰器。

二、什么是资源?

资源为LLM或客户端应用程序提供对数据的只读访问。当客户端请求资源URI时:

  • FastMCP找到相应的资源定义。
  • 如果是动态的(由函数定义),则执行该函数。
  • 内容(文本、JSON、二进制数据)返回给客户端。
    这使得LLM能够访问文件、数据库内容、配置或与会话相关的动态生成信息。

三、资源

3.1 @resource装饰器

定义资源最常见的方法是通过装饰Python函数。装饰器需要资源的唯一URI。

python 复制代码
import json
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 返回字符串的基本动态资源
@mcp.resource("resource://greeting")
def get_greeting() -> str:
    """提供简单的问候消息。"""
    return "Hello from FastMCP Resources!"

# 返回JSON数据的资源(字典自动序列化)
@mcp.resource("data://config")
def get_config() -> dict:
    """以JSON格式提供应用程序配置。"""
    return {
        "theme": "dark",
        "version": "1.2.0",
        "features": ["tools", "resources"],
    }

关键概念:

  • URI:@resource的第一个参数是唯一的URI(例如"resource://greeting"),客户端使用它来请求此数据。
  • 懒加载:装饰的函数(get_greeting、get_config)仅在客户端通过resources/read特定请求该资源URI时执行。
  • 推断的元数据:默认情况下:
  • 资源名称:取自函数名称(get_greeting)。
  • 资源描述:取自函数的文档字符串。

3.2 装饰器参数

您可以使用 @mcp.resource 装饰器中的参数自定义资源的属性:

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 指定元数据的示例
@mcp.resource(
    uri="data://app-status",      # 显式URI(必需)
    name="ApplicationStatus",     # 自定义名称
    description="提供应用程序的当前状态。", # 自定义描述
    mime_type="application/json", # 显式MIME类型
    tags={"monitoring", "status"}, # 分类标签
    meta={"version": "2.1", "team": "infrastructure"}  # 自定义元数据
)
def get_application_status() -> dict:
    """内部函数描述(如果上面提供了描述,则忽略)。"""
    return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # 示例用法

@resource装饰器参数:

Annotations属性:

四、返回值

FastMCP自动将函数的返回值转换为适当的MCP资源内容:

  • str:作为TextResourceContents发送(默认mime_type="text/plain")。
  • dict、list、pydantic.BaseModel:自动序列化为JSON字符串并作为TextResourceContents发送(默认mime_type="application/json")。
  • bytes:Base64编码并作为BlobResourceContents发送。您应指定适当的mime_type(例如"image/png"、"application/octet-stream")。
  • None:导致返回空资源内容列表。

五、禁用资源

新版本2.8.0功能

您可以通过启用或禁用资源来控制资源和模板的可见性和可用性。禁用的资源不会出现在可用资源或模板列表中,尝试读取禁用的资源将导致"未知资源"错误。

默认情况下,所有资源都是启用的。您可以在创建时使用装饰器中的enabled参数禁用资源:

python 复制代码
@mcp.resource("data://secret", enabled=False)
def get_secret_data():
    """此资源当前已禁用。"""
    return "Secret data"

您也可以在创建后以编程方式切换资源的状态:

python 复制代码
@mcp.resource("data://config")
def get_config(): return {"version": 1}

# 禁用和重新启用资源
get_config.disable()
get_config.enable()

六、访问MCP上下文

新版本2.2.5功能

资源和资源模板可以通过Context对象访问额外的MCP信息和功能。要访问它,请向资源函数添加一个类型注解为Context的参数:

python 复制代码
from fastmcp import FastMCP, Context

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://system-status")
async def get_system_status(ctx: Context) -> dict:
    """提供系统状态信息。"""
    return {
        "status": "operational",
        "request_id": ctx.request_id
    }

@mcp.resource("resource://{name}/details")
async def get_details(name: str, ctx: Context) -> dict:
    """获取特定名称的详细信息。"""
    return {
        "name": name,
        "accessed_at": ctx.request_id
    }

有关Context对象及其所有功能的完整文档,请参阅上下文文档。

七、异步资源

对于执行I/O操作(例如从数据库或网络读取)的资源函数,使用async def以避免阻塞服务器。

python 复制代码
import aiofiles
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

@mcp.resource("file:///app/data/important_log.txt", mime_type="text/plain")
async def read_important_log() -> str:
    """异步从特定日志文件读取内容。"""
    try:
        async with aiofiles.open("/app/data/important_log.txt", mode="r") as f:
            content = await f.read()
        return content
    except FileNotFoundError:
        return "Log file not found."

八、资源类

虽然 @mcp.resource 适用于动态内容,但您可以直接使用 mcp.add_resource() 和具体的 Resource 子类注册预定义的资源(如静态文件或简单文本)。

python 复制代码
from pathlib import Path
from fastmcp import FastMCP
from fastmcp.resources import FileResource, TextResource, DirectoryResource

mcp = FastMCP(name="DataServer")

# 1. 直接公开静态文件
readme_path = Path("./README.md").resolve()
if readme_path.exists():
    # 使用file:// URI方案
    readme_resource = FileResource(
        uri=f"file://{readme_path.as_posix()}",
        path=readme_path, # 实际文件的路径
        name="README File",
        description="项目的README。",
        mime_type="text/markdown",
        tags={"documentation"}
    )
    mcp.add_resource(readme_resource)

# 2. 公开简单的预定义文本
notice_resource = TextResource(
    uri="resource://notice",
    name="Important Notice",
    text="系统维护计划于周日进行。",
    tags={"notification"}
)
mcp.add_resource(notice_resource)

# 3. 使用与URI不同的自定义键
special_resource = TextResource(
    uri="resource://common-notice",
    name="Special Notice",
    text="这是一个具有自定义存储键的特殊通知。",
)
mcp.add_resource(special_resource, key="resource://custom-key")

# 4. 公开目录列表
data_dir_path = Path("./app_data").resolve()
if data_dir_path.is_dir():
    data_listing_resource = DirectoryResource(
        uri="resource://data-files",
        path=data_dir_path, # 目录的路径
        name="Data Directory Listing",
        description="列出数据目录中可用的文件。",
        recursive=False # 设置为True以列出子目录
    )
    mcp.add_resource(data_listing_resource) # 返回文件的JSON列表

常见资源类:

  • TextResource:用于简单字符串内容。
  • BinaryResource:用于原始bytes内容。
  • FileResource:从本地文件路径读取内容。处理文本/二进制模式和懒读取。
  • HttpResource:从HTTP(S) URL获取内容(需要httpx)。
  • DirectoryResource:列出本地目录中的文件(返回JSON)。
  • (FunctionResource:@mcp.resource使用的内部类)。

当内容是静态的或直接来自文件/URL时使用这些,绕过对专用Python函数的需求。

九、自定义资源键

新版本2.2.0功能

当直接使用mcp.add_resource()添加资源时,您可以选择性地提供自定义存储键:

python 复制代码
# 创建具有标准URI作为键的资源
resource = TextResource(uri="resource://data")
mcp.add_resource(resource)  # 将使用"resource://data"存储和访问

# 创建具有自定义键的资源
special_resource = TextResource(uri="resource://special-data")
mcp.add_resource(special_resource, key="internal://data-v2")  # 将使用"internal://data-v2"存储和访问

请注意,此参数仅在直接使用add_resource()时可用,而不通过@resource装饰器可用,因为在使用装饰器时URI是显式提供的。

十、通知

新版本2.9.1功能

当资源或模板被添加、启用或禁用时,FastMCP会自动向连接的客户端发送notifications/resources/list_changed通知。这允许客户端保持与当前资源集的最新状态,而无需手动轮询更改。

python 复制代码
@mcp.resource("data://example")
def example_resource() -> str:
    return "Hello!"

# 这些操作触发通知:
mcp.add_resource(example_resource)  # 发送resources/list_changed通知
example_resource.disable()          # 发送resources/list_changed通知  
example_resource.enable()           # 发送resources/list_changed通知

仅当这些操作发生在活动的MCP请求上下文内(例如,从工具或其他MCP操作内调用时)才会发送通知。在服务器初始化期间执行的操作不会触发通知。

客户端可以使用消息处理程序来处理这些通知,以自动刷新其资源列表或更新其界面。

十一、注解

新版本2.11.0功能

FastMCP允许您通过注解向资源添加专门的元数据。这些注解向客户端应用程序传达资源的行为方式,而不会消耗LLM提示中的令牌上下文。

注解在客户端应用程序中有几个用途:

  • 指示资源是只读的还是可能有副作用
  • 描述资源的安全配置文件(幂等 vs. 非幂等)
  • 帮助客户端优化缓存和访问模式

您可以使用@mcp.resource装饰器中的annotations参数向资源添加注解:

python 复制代码
@mcp.resource(
    "data://config",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
def get_config() -> dict:
    """获取应用程序配置。"""
    return {"version": "1.0", "debug": False}

FastMCP支持这些标准注解:

请记住,注解有助于提供更好的用户体验,但应被视为建议性提示。它们帮助客户端应用程序呈现适当的UI元素和优化访问模式,但不会自行强制执行行为。始终专注于使您的注解准确反映资源的实际功能。

十二、资源模板

资源模板允许客户端请求其内容依赖于URI中嵌入的参数的资源。使用相同的@mcp.resource装饰器定义模板,但在URI字符串中包含{parameter_name}占位符,并向函数签名添加相应的参数。

资源模板与常规资源共享大多数配置选项(名称、描述、mime_type、标签、注解),但增加了定义URI参数的能力,这些参数映射到函数参数。

资源模板为每组唯一参数生成一个新资源,这意味着资源可以按需动态创建。例如,如果注册了资源模板"user://profile/{name}",MCP客户端可以请求"user://profile/ford"或"user://profile/marvin"来检索这两个用户配置文件中的任何一个作为资源,而无需单独注册每个资源。

提示:不支持将带有*args的函数作为资源模板。但是,与工具和提示不同,资源模板确实支持**kwargs,因为URI模板定义了将收集并作为关键字参数传递的特定参数名称。

这是一个完整的示例,展示如何定义两个资源模板:

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 模板URI包括{city}占位符
@mcp.resource("weather://{city}/current")
def get_weather(city: str) -> dict:
    """提供特定城市的天气信息。"""
    # 在实际实现中,这将调用天气API
    # 这里我们为了示例目的使用简化逻辑
    return {
        "city": city.capitalize(),
        "temperature": 22,
        "condition": "Sunny",
        "unit": "celsius"
    }

# 具有多个参数和注解的模板
@mcp.resource(
    "repos://{owner}/{repo}/info",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
def get_repo_info(owner: str, repo: str) -> dict:
    """检索GitHub存储库的信息。"""
    # 在实际实现中,这将调用GitHub API
    return {
        "owner": owner,
        "name": repo,
        "full_name": f"{owner}/{repo}",
        "stars": 120,
        "forks": 48
    }

定义了这两个模板后,客户端可以请求各种资源:

  • weather://london/current → 返回伦敦的天气
  • weather://paris/current → 返回巴黎的天气
  • repos://jlowin/fastmcp/info → 返回jlowin/fastmcp存储库的信息
  • repos://prefecthq/prefect/info → 返回prefecthq/prefect存储库的信息

十三、RFC 6570 URI模板

FastMCP实现了RFC 6570 URI模板用于资源模板,提供了一种标准化的方式来定义参数化URI。这包括对简单扩展、通配符路径参数和表单样式查询参数的支持。

13.1 通配符参数

新版本2.2.4功能

资源模板支持可以匹配多个路径段的通配符参数。虽然标准参数({param})只匹配单个路径段并且不跨越"/"边界,但通配符参数({param*})可以捕获多个段,包括斜杠。通配符捕获所有后续路径段,直到URI模板的定义部分(无论是字面量还是另一个参数)。这允许您在单个URI模板中拥有多个通配符参数。

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 标准参数只匹配一个段
@mcp.resource("files://{filename}")
def get_file(filename: str) -> str:
    """按名称检索文件。"""
    # 只匹配files://<single-segment>
    return f"File content for: {filename}"

# 通配符参数可以匹配多个段
@mcp.resource("path://{filepath*}")
def get_path_content(filepath: str) -> str:
    """检索特定路径的内容。"""
    # 可以匹配path://docs/server/resources.mdx
    return f"Content at path: {filepath}"

# 混合标准参数和通配符参数
@mcp.resource("repo://{owner}/{path*}/template.py")
def get_template_file(owner: str, path: str) -> dict:
    """从特定存储库和路径检索文件,但
    仅当资源以`template.py`结尾时"""
    # 可以匹配repo://jlowin/fastmcp/src/resources/template.py
    return {
        "owner": owner,
        "path": path + "/template.py",
        "content": f"File at {path}/template.py in {owner}'s repository"
    }

通配符参数在以下情况下很有用:

  • 处理文件路径或分层数据
  • 创建需要捕获可变长度路径段的API
  • 构建类似于REST API的URL模式

请注意,与常规参数一样,每个通配符参数仍然必须是函数签名中的命名参数,并且所有必需的函数参数必须出现在URI模板中。

13.2 查询参数

新版本2.13.0功能

FastMCP支持使用{?param1,param2}语法的RFC 6570表单样式查询参数。查询参数提供了一种干净的方式来向资源传递可选配置,而不会使路径混乱。

查询参数必须是可选的函数参数(具有默认值),而路径参数映射到必需的函数参数。这强制执行了清晰的分离:必需的数据放在路径中,可选配置放在查询参数中。

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 基本查询参数
@mcp.resource("data://{id}{?format}")
def get_data(id: str, format: str = "json") -> str:
    """以指定格式检索数据。"""
    if format == "xml":
        return f"<data id='{id}' />"
    return f'{{"id": "{id}"}}'

# 具有类型强制的多个查询参数
@mcp.resource("api://{endpoint}{?version,limit,offset}")
def call_api(endpoint: str, version: int = 1, limit: int = 10, offset: int = 0) -> dict:
    """使用分页调用API端点。"""
    return {
        "endpoint": endpoint,
        "version": version,
        "limit": limit,
        "offset": offset,
        "results": fetch_results(endpoint, version, limit, offset)
    }

# 具有通配符的查询参数
@mcp.resource("files://{path*}{?encoding,lines}")
def read_file(path: str, encoding: str = "utf-8", lines: int = 100) -> str:
    """使用可选编码和行限制读取文件。"""
    return read_file_content(path, encoding, lines)

示例请求:

  • data://123 → 使用默认格式"json"
  • data://123?format=xml → 使用格式"xml"
  • api://users?version=2&limit=50 → version=2, limit=50, offset=0
  • files://src/main.py?encoding=ascii&lines=50 → 自定义编码和行限制

FastMCP根据函数的类型提示(int、float、bool、str)自动将查询参数字符串值强制转换为正确的类型。

查询参数 vs. 隐藏默认值:

查询参数向客户端公开可选配置。要完全向客户端隐藏可选参数(始终使用默认值),只需从URI模板中省略它们:

python 复制代码
# 客户端可以通过查询字符串覆盖max_results
@mcp.resource("search://{query}{?max_results}")
def search_configurable(query: str, max_results: int = 10) -> dict:
    return {"query": query, "limit": max_results}

# 客户端无法覆盖max_results(不在URI模板中)
@mcp.resource("search://{query}")
def search_fixed(query: str, max_results: int = 10) -> dict:
    return {"query": query, "limit": max_results}

13.3 模板参数规则

新版本2.2.0功能

FastMCP在创建资源模板时强制执行这些验证规则:

  • 必需的函数参数(无默认值)必须出现在URI路径模板中
  • 查询参数(使用{?param}语法指定)必须是具有默认值的可选函数参数
  • 所有URI模板参数(路径和查询)必须作为函数参数存在

可选的函数参数(具有默认值的参数)可以是:

  • 包含为查询参数({?param})- 客户端可以通过查询字符串覆盖
  • 从URI模板中省略 - 始终使用默认值,不向客户端公开
  • 用于替代路径模板 - 启用访问同一资源的多种方式

一个函数的多个模板:

通过手动应用装饰器创建多个资源模板,通过不同的URI模式公开同一函数:

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# 定义一个用户查找函数,可以通过不同的标识符访问
def lookup_user(name: str | None = None, email: str | None = None) -> dict:
    """通过名称或电子邮件查找用户。"""
    if email:
        return find_user_by_email(email)  # 伪代码
    elif name:
        return find_user_by_name(name)  # 伪代码
    else:
        return {"error": "No lookup parameters provided"}

# 手动将多个装饰器应用于同一函数
mcp.resource("users://email/{email}")(lookup_user)
mcp.resource("users://name/{name}")(lookup_user)

现在LLM或客户端可以通过两种不同的方式检索用户信息:

  • users://email/alice@example.com → 通过电子邮件查找用户(name=None)
  • users://name/Bob → 通过名称查找用户(email=None)

这种方法允许单个函数注册多个URI模式,同时保持实现干净和直接。

模板提供了一种强大的方式来公开参数化的数据访问点,遵循类似REST的原则。

十四、错误处理

新版本2.4.1功能

如果您的资源函数遇到错误,您可以引发标准Python异常(ValueError、TypeError、FileNotFoundError、自定义异常等)或FastMCP ResourceError。

默认情况下,所有异常(包括其详细信息)都会被记录并转换为MCP错误响应发送回客户端LLM。这有助于LLM理解失败并适当反应。

如果您出于安全原因想要屏蔽内部错误详细信息,您可以:

  • 在创建FastMCP实例时使用mask_error_details=True参数:
python 复制代码
mcp = FastMCP(name="SecureServer", mask_error_details=True)
  • 或使用ResourceError显式控制发送给客户端的错误信息:
python 复制代码
from fastmcp import FastMCP
from fastmcp.exceptions import ResourceError

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://safe-error")
def fail_with_details() -> str:
    """此资源提供详细的错误信息。"""
    # ResourceError内容总是发送回客户端,
    # 无论mask_error_details设置如何
    raise ResourceError("Unable to retrieve data: file not found")

@mcp.resource("resource://masked-error")
def fail_with_masked_details() -> str:
    """当mask_error_details=True时,此资源屏蔽内部错误详细信息。"""
    # 如果mask_error_details=True,此消息将被屏蔽
    raise ValueError("Sensitive internal file path: /etc/secrets.conf")

@mcp.resource("data://{id}")
def get_data_by_id(id: str) -> dict:
    """模板资源也支持相同的错误处理模式。"""
    if id == "secure":
        raise ValueError("Cannot access secure data")
    elif id == "missing":
        raise ResourceError("Data ID 'missing' not found in database")
    return {"id": id, "value": "data"}

当mask_error_details=True时,只有来自ResourceError的错误消息会包含详细信息,其他异常将转换为通用消息。

十五、重复资源

新版本2.1.0功能

您可以配置FastMCP服务器如何处理尝试注册多个具有相同URI的资源或模板。在FastMCP初始化期间使用on_duplicate_resources设置。

python 复制代码
from fastmcp import FastMCP

mcp = FastMCP(
    name="ResourceServer",
    on_duplicate_resources="error" # 重复时引发错误
)

@mcp.resource("data://config")
def get_config_v1(): return {"version": 1}

# 此注册尝试将引发ValueError,因为
# "data://config"已注册且行为为"error"。
# @mcp.resource("data://config")
# def get_config_v2(): return {"version": 2}

重复行为选项是:

  • "warn"(默认):记录警告,新资源/模板替换旧资源/模板。
  • "error":引发ValueError,防止重复注册。
  • "replace":静默地用新资源/模板替换现有资源/模板。
  • "ignore":保留原始资源/模板并忽略新的注册尝试。
相关推荐
腾飞开源2 天前
09_FastMCP 2.x 中文文档之FastMCP高级功能服务器组成详解
性能优化·代理服务器·模块化设计·fastmcp 2.x中文文档·服务器组合·前缀规则·标签过滤
腾飞开源5 天前
03_FastMCP 2.x 中文文档之FastMCP快速入门
快速入门·fastmcp 2.x中文文档·fastmcp 2.x·工具装饰器·传输方式·客户端调用·mcp服务器