docs/v2/servers/resources.mdx
import { VersionBadge } from "/snippets/version-badge.mdx"
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.
Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:
This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.
@resource DecoratorThe 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:
@resource is the unique URI (e.g., "resource://greeting") clients use to request this data.get_greeting, get_config) is only executed when a client specifically requests that resource URI via resources/read.get_greeting).You can customize the resource's properties using arguments in the @mcp.resource 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
meta={"version": "2.1", "team": "infrastructure"} # Custom metadata
)
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
Optional list of icon representations for this resource or template. See Icons for detailed examples </ParamField>
<ParamField body="annotations" type="Annotations | dict | None"> An optional `Annotations` object or dictionary to add additional metadata about the resource. <Expandable title="Annotations attributes"> <ParamField body="readOnlyHint" type="bool | None"> If true, the resource is read-only and does not modify its environment. </ParamField> <ParamField body="idempotentHint" type="bool | None"> If true, reading the resource repeatedly will have no additional effect on its environment. </ParamField> </Expandable> </ParamField> <ParamField body="meta" type="dict[str, Any] | None"> <VersionBadge version="2.11.0" />Optional meta information about the resource. This data is passed through to the MCP client as the _meta field of the client-side resource object and can be used for custom metadata, versioning, or other application-specific purposes.
</ParamField>
</Card>
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.You can control the visibility and availability of resources and templates by enabling or disabling them. Disabled resources will not appear in the list of available resources or templates, and attempting to read a disabled resource will result in an "Unknown resource" error.
By default, all resources are enabled. You can disable a resource upon creation using the enabled parameter in the decorator:
@mcp.resource("data://secret", enabled=False)
def get_secret_data():
"""This resource is currently disabled."""
return "Secret data"
You can also toggle a resource's state programmatically after it has been created:
@mcp.resource("data://config")
def get_config(): return {"version": 1}
# Disable and re-enable the resource
get_config.disable()
get_config.enable()
Resources and resource templates can access additional MCP information and features through the Context object. To access it, add a parameter to your resource function with a type annotation of 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
}
For full documentation on the Context object and all its capabilities, see the Context documentation.
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."
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. 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.
FastMCP automatically sends notifications/resources/list_changed notifications to connected clients when resources or templates are added, enabled, or disabled. This allows clients to stay up-to-date with the current resource set without manually polling for changes.
@mcp.resource("data://example")
def example_resource() -> str:
return "Hello!"
# These operations trigger notifications:
mcp.add_resource(example_resource) # Sends resources/list_changed notification
example_resource.disable() # Sends resources/list_changed notification
example_resource.enable() # Sends resources/list_changed notification
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
Clients can handle these notifications using a message handler to automatically refresh their resource lists or update their interfaces.
FastMCP allows you to add specialized metadata to your resources through annotations. These annotations communicate how resources behave to client applications without consuming token context in LLM prompts.
Annotations serve several purposes in client applications:
You can add annotations to a resource using the annotations parameter in the @mcp.resource decorator:
@mcp.resource(
"data://config",
annotations={
"readOnlyHint": True,
"idempotentHint": True
}
)
def get_config() -> dict:
"""Get application configuration."""
return {"version": "1.0", "debug": False}
FastMCP supports these standard annotations:
| Annotation | Type | Default | Purpose |
|---|---|---|---|
readOnlyHint | boolean | true | Indicates if the resource only provides data without side effects |
idempotentHint | boolean | true | Indicates if repeated reads have the same effect as a single read |
Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and optimize access patterns, but won't enforce behavior on their own. Always focus on making your annotations accurately represent what your resource actually does.
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.
Resource templates share most configuration options with regular resources (name, description, mime_type, tags, annotations), but add the ability to define URI parameters that map to function parameters.
Resource templates generate a new resource for each unique set of parameters, which means that resources can be dynamically created on-demand. For example, if the resource template "user://profile/{name}" is registered, MCP clients could request "user://profile/ford" or "user://profile/marvin" to retrieve either of those two user profiles as resources, without having to register each resource individually.
Here is a complete example that shows how to define two resource templates:
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 and annotations
@mcp.resource(
"repos://{owner}/{repo}/info",
annotations={
"readOnlyHint": True,
"idempotentHint": True
}
)
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 two templates defined, clients can request a variety of resources:
weather://london/current → Returns weather for Londonweather://paris/current → Returns weather for Parisrepos://PrefectHQ/fastmcp/info → Returns info about the PrefectHQ/fastmcp repositoryrepos://prefecthq/prefect/info → Returns info about the prefecthq/prefect repositoryFastMCP implements RFC 6570 URI Templates for resource templates, providing a standardized way to define parameterized URIs. This includes support for simple expansion, wildcard path parameters, and form-style query parameters.
Resource templates support wildcard parameters that can match multiple path segments. While standard parameters ({param}) only match a single path segment and don't cross "/" boundaries, wildcard parameters ({param*}) can capture multiple segments including slashes. Wildcards capture all subsequent path segments up until the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template.
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://PrefectHQ/fastmcp/src/resources/template.py
return {
"owner": owner,
"path": path + "/template.py",
"content": f"File at {path}/template.py in {owner}'s repository"
}
Wildcard parameters are useful when:
Note that like regular parameters, each wildcard parameter must still be a named parameter in your function signature, and all required function parameters must appear in the URI template.
FastMCP supports RFC 6570 form-style query parameters using the {?param1,param2} syntax. Query parameters provide a clean way to pass optional configuration to resources without cluttering the path.
Query parameters must be optional function parameters (have default values), while path parameters map to required function parameters. This enforces a clear separation: required data goes in the path, optional configuration in query params.
from fastmcp import FastMCP
mcp = FastMCP(name="DataServer")
# Basic query parameters
@mcp.resource("data://{id}{?format}")
def get_data(id: str, format: str = "json") -> str:
"""Retrieve data in specified format."""
if format == "xml":
return f"<data id='{id}' />"
return f'{{"id": "{id}"}}'
# Multiple query parameters with type coercion
@mcp.resource("api://{endpoint}{?version,limit,offset}")
def call_api(endpoint: str, version: int = 1, limit: int = 10, offset: int = 0) -> dict:
"""Call API endpoint with pagination."""
return {
"endpoint": endpoint,
"version": version,
"limit": limit,
"offset": offset,
"results": fetch_results(endpoint, version, limit, offset)
}
# Query parameters with wildcards
@mcp.resource("files://{path*}{?encoding,lines}")
def read_file(path: str, encoding: str = "utf-8", lines: int = 100) -> str:
"""Read file with optional encoding and line limit."""
return read_file_content(path, encoding, lines)
Example requests:
data://123 → Uses default format "json"data://123?format=xml → Uses format "xml"api://users?version=2&limit=50 → version=2, limit=50, offset=0files://src/main.py?encoding=ascii&lines=50 → Custom encoding and line limitFastMCP automatically coerces query parameter string values to the correct types based on your function's type hints (int, float, bool, str).
Query parameters vs. hidden defaults:
Query parameters expose optional configuration to clients. To hide optional parameters from clients entirely (always use defaults), simply omit them from the URI template:
# Clients CAN override max_results via query string
@mcp.resource("search://{query}{?max_results}")
def search_configurable(query: str, max_results: int = 10) -> dict:
return {"query": query, "limit": max_results}
# Clients CANNOT override max_results (not in URI template)
@mcp.resource("search://{query}")
def search_fixed(query: str, max_results: int = 10) -> dict:
return {"query": query, "limit": max_results}
FastMCP enforces these validation rules when creating resource templates:
{?param} syntax) must be optional function parameters with default valuesOptional function parameters (those with default values) can be:
{?param}) - clients can override via query stringMultiple templates for one function:
Create multiple resource templates that expose the same function through different URI patterns by manually applying decorators:
from fastmcp import FastMCP
mcp = FastMCP(name="DataServer")
# Define a user lookup function that can be accessed by different identifiers
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"}
# Manually apply multiple decorators to the same function
mcp.resource("users://email/{email}")(lookup_user)
mcp.resource("users://name/{name}")(lookup_user)
Now an LLM or client can retrieve user information in two different ways:
users://email/[email protected] → Looks up user by email (with name=None)users://name/Bob → Looks up user by name (with email=None)This approach allows a single function to be registered with multiple URI patterns while keeping the implementation clean and straightforward.
Templates provide a powerful way to expose parameterized data access points following REST-like principles.
If your resource function encounters an error, you can raise a standard Python exception (ValueError, TypeError, FileNotFoundError, custom exceptions, etc.) or a FastMCP ResourceError.
By default, all exceptions (including their details) are logged and converted into an MCP error response to be sent back to the client LLM. This helps the LLM understand failures and react appropriately.
If you want to mask internal error details for security reasons, you can:
mask_error_details=True parameter when creating your FastMCP instance:mcp = FastMCP(name="SecureServer", mask_error_details=True)
ResourceError to explicitly control what error information is sent to clients: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 always sent back to clients,
# regardless of mask_error_details setting
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 when mask_error_details=True."""
# This message would be masked if 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:
"""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"}
When mask_error_details=True, only error messages from ResourceError will include details, other exceptions will be converted to a generic message.
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.