mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-16 18:49:14 +02:00
486 lines
No EOL
20 KiB
Text
486 lines
No EOL
20 KiB
Text
---
|
|
title: Resources & Templates
|
|
sidebarTitle: Resources
|
|
description: Expose data sources and dynamic content generators to your MCP client.
|
|
icon: database
|
|
---
|
|
|
|
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.
|
|
|
|
## 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.
|
|
|
|
## 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.
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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.
|
|
|
|
### Accessing MCP Context
|
|
|
|
<VersionBadge version="2.2.5" />
|
|
|
|
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`:
|
|
|
|
```python {6, 14}
|
|
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](/servers/context).
|
|
|
|
|
|
### 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.
|
|
|
|
```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:
|
|
"""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.
|
|
|
|
```python
|
|
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
|
|
|
|
<VersionBadge version="2.2.0" />
|
|
|
|
When adding resources directly with `mcp.add_resource()`, you can optionally provide a custom storage key:
|
|
|
|
```python
|
|
# 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.
|
|
|
|
## 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.
|
|
|
|
Resource templates share most configuration options with regular resources (name, description, mime_type, tags), 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.
|
|
|
|
<Tip>
|
|
Functions with `*args` are not supported as resource templates. However, unlike tools and prompts, resource templates do support `**kwargs` because the URI template defines specific parameter names that will be collected and passed as keyword arguments.
|
|
</Tip>
|
|
|
|
Here is a complete example that shows how to define two resource templates:
|
|
|
|
```python
|
|
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 two templates defined, clients can request a variety of resources:
|
|
- `weather://london/current` → Returns weather for London
|
|
- `weather://paris/current` → Returns weather for Paris
|
|
- `repos://jlowin/fastmcp/info` → Returns info about the jlowin/fastmcp repository
|
|
- `repos://prefecthq/prefect/info` → Returns info about the prefecthq/prefect repository
|
|
|
|
### Wildcard Parameters
|
|
|
|
<VersionBadge version="2.2.4" />
|
|
|
|
<Tip>
|
|
Please note: FastMCP's support for wildcard parameters is an **extension** of the Model Context Protocol standard, which otherwise follows RFC 6570. Since all template processing happens in the FastMCP server, this should not cause any compatibility issues with other MCP implementations.
|
|
</Tip>
|
|
|
|
|
|
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.
|
|
|
|
```python {15, 23}
|
|
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"
|
|
}
|
|
```
|
|
|
|
Wildcard parameters are useful when:
|
|
|
|
- Working with file paths or hierarchical data
|
|
- Creating APIs that need to capture variable-length path segments
|
|
- Building URL-like patterns similar to REST APIs
|
|
|
|
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.
|
|
|
|
### Default Values
|
|
|
|
<VersionBadge version="2.2.0" />
|
|
|
|
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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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
|
|
|
|
Templates provide a powerful way to expose parameterized data access points following REST-like principles.
|
|
|
|
## Error Handling
|
|
|
|
<VersionBadge version="2.4.1" />
|
|
|
|
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:
|
|
|
|
1. Use the `mask_error_details=True` parameter when creating your `FastMCP` instance:
|
|
```python
|
|
mcp = FastMCP(name="SecureServer", mask_error_details=True)
|
|
```
|
|
|
|
2. Or use `ResourceError` to explicitly control what error information is sent to clients:
|
|
```python
|
|
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.
|
|
|
|
## Server Behavior
|
|
|
|
### Duplicate Resources
|
|
|
|
<VersionBadge version="2.1.0" />
|
|
|
|
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.
|
|
|
|
```python
|
|
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. |