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

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

什么是资源?

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

  1. FastMCP 查找对应的资源定义。
  2. 如果是动态的(由函数定义),则执行该函数。
  3. 内容(文本、JSON、二进制数据)将返回给客户端。

这使得LLM能够访问与对话相关的文件、数据库内容、配置或动态生成的信息。

资源

@resource 装饰器

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

import json
from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Basic dynamic resource returning a string
@mcp.resource("resource://greeting")
def get_greeting() -> str:
    """Provides a simple greeting message."""
    return "Hello from FastMCP Resources!"

# Resource returning JSON data (dict is auto-serialized)
@mcp.resource("data://config")
def get_config() -> dict:
    """Provides application configuration as JSON."""
    return {
        "theme": "dark",
        "version": "1.2.0",
        "features": ["tools", "resources"],
    }

关键概念:

  • URI: @resource的第一个参数是客户端用来请求此数据的唯一URI(例如"resource://greeting")。
  • 延迟加载: 装饰函数(get_greeting, get_config)仅在客户端通过resources/read明确请求该资源URI时才会执行。
  • Inferred Metadata: By default:
    • 资源名称:取自函数名称(get_greeting)。
    • 资源描述:取自函数的文档字符串。

返回值

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: 将返回一个空的资源内容列表。

资源元数据

您可以通过装饰器中的参数自定义资源的属性:

from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Example specifying metadata
@mcp.resource(
    uri="data://app-status",      # Explicit URI (required)
    name="ApplicationStatus",     # Custom name
    description="Provides the current status of the application.", # Custom description
    mime_type="application/json", # Explicit MIME type
    tags={"monitoring", "status"} # Categorization tags
)
def get_application_status() -> dict:
    """Internal function description (ignored if description is provided above)."""
    return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage
  • uri: 资源的唯一标识符(必填)。
  • name: 一个人类可读的名称(默认为函数名称)。
  • description: 资源的说明(默认为文档字符串)。
  • mime_type: 指定内容类型(FastMCP通常会推断默认值如text/plainapplication/json,但对于非文本类型显式指定更佳)。
  • tags: 一组用于分类的字符串,客户端可能用于过滤。

访问MCP上下文

New in version: 2.2.5

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

from fastmcp import FastMCP, Context

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://system-status")
async def get_system_status(ctx: Context) -> dict:
    """Provides system status information."""
    return {
        "status": "operational",
        "request_id": ctx.request_id
    }

@mcp.resource("resource://{name}/details")
async def get_details(name: str, ctx: Context) -> dict:
    """Get details for a specific name."""
    return {
        "name": name,
        "accessed_at": ctx.request_id
    }

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

异步资源

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

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:
    """Reads content from a specific log file asynchronously."""
    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子类来注册预定义的资源(如静态文件或简单文本)。

from pathlib import Path
from fastmcp import FastMCP
from fastmcp.resources import FileResource, TextResource, DirectoryResource

mcp = FastMCP(name="DataServer")

# 1. Exposing a static file directly
readme_path = Path("./README.md").resolve()
if readme_path.exists():
    # Use a file:// URI scheme
    readme_resource = FileResource(
        uri=f"file://{readme_path.as_posix()}",
        path=readme_path, # Path to the actual file
        name="README File",
        description="The project's README.",
        mime_type="text/markdown",
        tags={"documentation"}
    )
    mcp.add_resource(readme_resource)

# 2. Exposing simple, predefined text
notice_resource = TextResource(
    uri="resource://notice",
    name="Important Notice",
    text="System maintenance scheduled for Sunday.",
    tags={"notification"}
)
mcp.add_resource(notice_resource)

# 3. Using a custom key different from the URI
special_resource = TextResource(
    uri="resource://common-notice",
    name="Special Notice",
    text="This is a special notice with a custom storage key.",
)
mcp.add_resource(special_resource, key="resource://custom-key")

# 4. Exposing a directory listing
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, # Path to the directory
        name="Data Directory Listing",
        description="Lists files available in the data directory.",
        recursive=False # Set to True to list subdirectories
    )
    mcp.add_resource(data_listing_resource) # Returns JSON list of files

常用资源类:

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

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

自定义资源键

New in version: 2.2.0

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

# Creating a resource with standard URI as the key
resource = TextResource(uri="resource://data")
mcp.add_resource(resource)  # Will be stored and accessed using "resource://data"

# Creating a resource with a custom key
special_resource = TextResource(uri="resource://special-data")
mcp.add_resource(special_resource, key="internal://data-v2")  # Will be stored and accessed using "internal://data-v2"

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

资源模板

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

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

资源模板会为每组独特参数生成一个新资源,这意味着可以根据需求动态创建资源。例如,如果注册了资源模板"user://profile/{name}",MCP客户端就可以请求"user://profile/ford""user://profile/marvin"来获取这两个用户配置文件作为资源,而无需单独注册每个资源。

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

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

from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Template URI includes {city} placeholder
@mcp.resource("weather://{city}/current")
def get_weather(city: str) -> dict:
    """Provides weather information for a specific city."""
    # In a real implementation, this would call a weather API
    # Here we're using simplified logic for example purposes
    return {
        "city": city.capitalize(),
        "temperature": 22,
        "condition": "Sunny",
        "unit": "celsius"
    }

# Template with multiple parameters
@mcp.resource("repos://{owner}/{repo}/info")
def get_repo_info(owner: str, repo: str) -> dict:
    """Retrieves information about a GitHub repository."""
    # In a real implementation, this would call the 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仓库的信息

通配符参数

New in version: 2.2.4

请注意:FastMCP对通配符参数的支持是对扩展Model Context Protocol标准的实现,该标准其他方面遵循RFC 6570规范。由于所有模板处理都在FastMCP服务器端完成,这不会与其他MCP实现产生兼容性问题。

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

from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")


# Standard parameter only matches one segment
@mcp.resource("files://{filename}")
def get_file(filename: str) -> str:
    """Retrieves a file by name."""
    # Will only match files://<single-segment>
    return f"File content for: {filename}"


# Wildcard parameter can match multiple segments
@mcp.resource("path://{filepath*}")
def get_path_content(filepath: str) -> str:
    """Retrieves content at a specific path."""
    # Can match path://docs/server/resources.mdx
    return f"Content at path: {filepath}"


# Mixing standard and wildcard parameters
@mcp.resource("repo://{owner}/{path*}/template.py")
def get_template_file(owner: str, path: str) -> dict:
    """Retrieves a file from a specific repository and path, but 
    only if the resource ends with `template.py`"""
    # Can match 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模板中。

默认值

New in version: 2.2.0

在创建资源模板时,FastMCP对URI模板参数和函数参数之间的关系强制执行两条规则:

  1. 必填函数参数:所有没有默认值的函数参数(必填参数)都必须出现在URI模板中。
  2. URI参数:所有URI模板参数必须作为函数参数存在。

然而,带有默认值的函数参数不需要包含在URI模板中。当客户端请求资源时,FastMCP将:

  • 从URI中提取模板中包含的参数值
  • 对URI模板中未包含的任何函数参数使用默认值

这允许灵活的API设计。例如,一个带有可选参数的简单搜索模板:

from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

@mcp.resource("search://{query}")
def search_resources(query: str, max_results: int = 10, include_archived: bool = False) -> dict:
    """Search for resources matching the query string."""
    # Only 'query' is required in the URI, the other parameters use their defaults
    results = perform_search(query, limit=max_results, archived=include_archived)
    return {
        "query": query,
        "max_results": max_results,
        "include_archived": include_archived,
        "results": results
    }

通过此模板,客户端可以请求search://python,该函数将被调用并传入参数query="python", max_results=10, include_archived=False。MCP开发者仍可直接调用底层的search_resources函数并指定更具体的参数。

更强大的模式是使用多个URI模板注册单个函数,允许通过不同方式访问相同数据:

from fastmcp import FastMCP

mcp = FastMCP(name="DataServer")

# Define a user lookup function that can be accessed by different identifiers
@mcp.resource("users://email/{email}")
@mcp.resource("users://name/{name}")
def lookup_user(name: str | None = None, email: str | None = None) -> dict:
    """Look up a user by either name or email."""
    if email:
        return find_user_by_email(email) # pseudocode
    elif name:
        return find_user_by_name(name) # pseudocode
    else:
        return {"error": "No lookup parameters provided"}

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

  • users://email/alice@example.com → 通过邮箱查找用户(name参数为None)
  • users://name/Bob → 按名称查找用户(email=None)

在这个堆叠装饰器模式中:

  • 仅当使用users://name/{name}模板时才会提供name参数
  • 仅在使用 users://email/{email} 模板时才会提供 email 参数
  • 当URI中未包含时,每个参数默认为None
  • 函数逻辑处理提供的任何参数

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

错误处理

New in version: 2.3.4

如果你的资源函数遇到错误,可以抛出标准Python异常(ValueErrorTypeErrorFileNotFoundError、自定义异常等)或FastMCP的ResourceError

出于安全考虑,大多数异常在被发送到客户端之前都会被封装在通用的ResourceError中,内部错误细节会被隐藏。但是,如果您直接抛出ResourceError,其内容包含在响应中。这允许您选择性地向客户端提供信息性错误消息。

from fastmcp import FastMCP
from fastmcp.exceptions import ResourceError

mcp = FastMCP(name="DataServer")

@mcp.resource("resource://safe-error")
def fail_with_details() -> str:
    """This resource provides detailed error information."""
    # ResourceError contents are sent back to clients
    raise ResourceError("Unable to retrieve data: file not found")

@mcp.resource("resource://masked-error")
def fail_with_masked_details() -> str:
    """This resource masks internal error details."""
    # Other exceptions are converted to ResourceError with generic message
    raise ValueError("Sensitive internal file path: /etc/secrets.conf")

@mcp.resource("data://{id}")
def get_data_by_id(id: str) -> dict:
    """Template resources also support the same error handling pattern."""
    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"}

此错误处理模式适用于常规资源和资源模板。

服务器行为

重复资源

New in version: 2.1.0

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

from fastmcp import FastMCP

mcp = FastMCP(
    name="ResourceServer",
    on_duplicate_resources="error" # Raise error on duplicates
)

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

# This registration attempt will raise a ValueError because
# "data://config" is already registered and the behavior is "error".
# @mcp.resource("data://config")
# def get_config_v2(): return {"version": 2}

重复行为选项包括:

  • "warn" (默认): 记录警告日志,新资源/模板会替换旧资源/模板。
  • "error": 抛出ValueError错误,阻止重复注册。
  • "replace": 静默地用新资源/模板替换现有资源/模板。
  • "ignore": 保留原始资源/模板并忽略新的注册尝试。