Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI.

FastMCP simplifies defining both static and dynamic resources, primarily using the @mcp.resource decorator.

What Are Resources?

Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:

  1. FastMCP finds the corresponding resource definition.
  2. If it’s dynamic (defined by a function), the function is executed.
  3. The content (text, JSON, binary data) is returned to the client.

This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.

Defining Resources

The @resource Decorator

The most common way to define a resource is by decorating a Python function. The decorator requires the resource’s unique 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"],
    }

Key Concepts:

  • URI: The first argument to @resource is the unique URI (e.g., "resource://greeting") clients use to request this data.
  • Lazy Loading: The decorated function (get_greeting, get_config) is only executed when a client specifically requests that resource URI via resources/read.
  • Inferred Metadata: By default:
    • Resource Name: Taken from the function name (get_greeting).
    • Resource Description: Taken from the function’s docstring.

Return Values

FastMCP automatically converts your function’s return value into the appropriate MCP resource content:

  • str: Sent as TextResourceContents (with mime_type="text/plain" by default).
  • dict, list, pydantic.BaseModel: Automatically serialized to a JSON string and sent as TextResourceContents (with mime_type="application/json" by default).
  • bytes: Base64 encoded and sent as BlobResourceContents. You should specify an appropriate mime_type (e.g., "image/png", "application/octet-stream").
  • None: Results in an empty resource content list being returned.

Resource Metadata

You can customize the resource’s properties using arguments in the decorator:

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: The unique identifier for the resource (required).
  • name: A human-readable name (defaults to function name).
  • description: Explanation of the resource (defaults to docstring).
  • mime_type: Specifies the content type (FastMCP often infers a default like text/plain or application/json, but explicit is better for non-text types).
  • tags: A set of strings for categorization, potentially used by clients for filtering.

Asynchronous Resources

Use async def for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.

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."

Resource Classes

While @mcp.resource is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using mcp.add_resource() and concrete Resource subclasses.

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

Common Resource Classes:

  • TextResource: For simple string content.
  • BinaryResource: For raw bytes content.
  • FileResource: Reads content from a local file path. Handles text/binary modes and lazy reading.
  • HttpResource: Fetches content from an HTTP(S) URL (requires httpx).
  • DirectoryResource: Lists files in a local directory (returns JSON).
  • (FunctionResource: Internal class used by @mcp.resource).

Use these when the content is static or sourced directly from a file/URL, bypassing the need for a dedicated Python function.

Custom Resource Keys

When adding resources directly with mcp.add_resource(), you can optionally provide a custom storage key:

# 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"

Note that this parameter is only available when using add_resource() directly and not through the @resource decorator, as URIs are provided explicitly when using the decorator.

Defining Resource Templates

Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the same @mcp.resource decorator, but include {parameter_name} placeholders in the URI string and add corresponding arguments to your function signature.

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
    }

With these templates defined, clients can request:

  • weather://london/current → Returns weather for London
  • repos://fastmcp/docs/info → Returns info about the fastmcp/docs repository

Parameters and Default Values

When creating resource templates, FastMCP enforces two rules for the relationship between URI template parameters and function parameters:

  1. Required Function Parameters: All function parameters without default values (required parameters) must appear in the URI template.
  2. URI Parameters: All URI template parameters must exist as function parameters.

However, function parameters with default values don’t need to be included in the URI template. When a client requests a resource, FastMCP will:

  • Extract parameter values from the URI for parameters included in the template
  • Use default values for any function parameters not in the URI template

This allows for flexible API designs. For example, a simple search template with optional parameters:

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
    }

With this template, clients can request search://python and the function will be called with query="python", max_results=10, include_archived=False. MCP Developers can still call the underlying search_resources function directly with more specific parameters.

An even more powerful pattern is registering a single function with multiple URI templates, allowing different ways to access the same data:

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"}

Now an LLM or client can retrieve user information in two different ways:

  • users://email/alice@example.com → Looks up user by email (with name=None)
  • users://name/Bob → Looks up user by name (with email=None)

In this stacked decorator pattern:

  • The name parameter is only provided when using the users://name/{name} template
  • The email parameter is only provided when using the users://email/{email} template
  • Each parameter defaults to None when not included in the URI
  • The function logic handles whichever parameter is provided

How Templates Work:

  1. Definition: When FastMCP sees {...} placeholders in the @resource URI and matching function parameters, it registers a ResourceTemplate.
  2. Discovery: Clients list templates via resources/listResourceTemplates.
  3. Request & Matching: A client requests a specific URI, e.g., weather://london/current. FastMCP matches this to the weather://{city}/current template.
  4. Parameter Extraction: It extracts the parameter value: city="london".
  5. Type Conversion & Function Call: It converts extracted values to the types hinted in the function and calls get_weather(city="london").
  6. Default Values: For any function parameters with default values not included in the URI template, FastMCP uses the default values.
  7. Response: The function’s return value is formatted (e.g., dict to JSON) and sent back as the resource content.

Templates provide a powerful way to expose parameterized data access points following REST-like principles.

Custom Template Keys

Similar to resources, you can provide custom keys when directly adding templates:

from fastmcp.resources import ResourceTemplate

# Create a template with a function
template = ResourceTemplate.from_function(
    my_function,
    uri_template="data://{id}/details",
    name="Data Details"
)

# Register with a custom key
mcp._resource_manager.add_template(template, key="custom://{id}/view")

This allows accessing the same template implementation through different URI patterns.

Server Behavior

Duplicate Resources

You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the on_duplicate_resources setting during FastMCP initialization.

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}

The duplicate behavior options are:

  • "warn" (default): Logs a warning, and the new resource/template replaces the old one.
  • "error": Raises a ValueError, preventing the duplicate registration.
  • "replace": Silently replaces the existing resource/template with the new one.
  • "ignore": Keeps the original resource/template and ignores the new registration attempt.