Learn to expose data as Resources — allowing the model to access information without performing actions.
So far, we’ve worked with tools — functions that perform actions and return results. Now let's get to know the resources: data the model can read but not modify. While tools are "verbs" (doing something), resources are "nouns" (accessing something). This distinction is fundamental to the MCP architecture.
In FastMCP, a resource is created with the decorator @mcp.resource(), which receives a URI as a unique identifier:
@mcp.resource("config://app")
def get_config() -> str:
"""Returns the app settings."""
return "version: 1.0, mode: production"
The fundamental difference: tools perform actions (send email, calculate, query an API), while resources expose data (settings, documentation, status). The model reads resources to get context, but can't use them to modify anything.
Think of resources as files in a virtual file system. Each resource has an address (URI) and content. The model can "open and read" these files, but it can't "write" to them. Use resources for anything the model needs to know but doesn't need to do: settings, documentation, constants, and reference data.
Static resources return fixed data that doesn’t change between calls — application settings, documentation, constants, business rules. They’re the simplest to implement and the most common in MCP servers. The model can access them at any time to get reference information.
@mcp.resource("config://app")
def get_app_config() -> str:
"""App settings."""
return """
App: My MCP Server
Version: 1.0.0
Environment: production
Max connections: 100
Default timeout: 30s
"""
@mcp.resource("docs://help")
def get_help() -> str:
"""Help documentation."""
return "Use the available tools to calculate and check the weather."
Static resources are great for providing context to the model. For example, a resource with the company's business rules lets the model answer questions about policies without needing a tool. A resource with the system documentation helps the model guide the user. Think of resources as the "instruction manual" the model can consult.
Unlike static resources, dynamic resources return data that changes with each access. The current date and time, system status, counters, monitoring data — all of this is dynamic. The resource function is called each time the model accesses that URI, ensuring the data is always up to date.
from datetime import datetime
@mcp.resource("status://current-time")
def get_hora_atual() -> str:
"""Returns the server's current date and time."""
now = datetime.now()
return f"Date and time: {agora.strftime('%d/%m/%Y %H:%M:%S')}"
@mcp.resource("status://system")
def get_status_sistema() -> str:
"""Returns the system's current status."""
import platform
return (
f"OS: {platform.system()}\n"
f"Python: {platform.python_version()}\n"
f"Time: {datetime.now().strftime('%H:%M:%S')}"
)
The model decides it needs a piece of data (e.g., the current time) and requests the resource by URI.
FastMCP receives the request and runs the function associated with that URI at that moment.
The function returns the updated data (e.g., "14:35:22"), and the model uses it in its response to the user.
Dynamic resources are particularly useful in scenarios involving monitoring and observability. An MCP server can expose resources with system metrics, service status, recent logs, and health dashboards—allowing the model to answer questions like "How is the system's health?" or "What's the current memory usage?" without needing specific tools.
Each resource is identified by a URI (Uniform Resource Identifier) — a unique address that follows the pattern tipo://caminho. The "type" (scheme) indicates the category of data, and the "path" identifies the specific resource. Well-organized URIs make it easier for the model to discover and use resources.
The URI format is scheme://path, where the scheme indicates the type of data:
The URI identifies only each resource. Two different URIs always point to different resources. Organize your URIs logically and consistently.
data://x1 — meaninglessresource://get_stuff — verb in resourceabc://123 — scheme without semanticsconfig://ConfiguraçõesDoApp — accents and capitalizationconfig://app — clear and concisestatus://health — clear semanticsdocs://api/endpoints — hierarchicaldb://users/schema — specific
The MCP protocol lets the client list all resources available on the server. FastMCP does this automatically—all resources registered with @mcp.resource() appear in the listing. In addition, MCP supports resource templates — parameterized resources where part of the URI is variable.
Resource templates let you create parameterized resources. FastMCP automatically detects parameters in the URI:
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
"""Returns a user's profile."""
return f"User profile {user_id}"
O {user_id} in the URI becomes a parameter. The model can access users://alice/profile or users://bob/profile — each returning different data. FastMCP registers this as a resource template in the listing.
The resource listing works like a "catalog" from the server. The MCP client can request the full list of resources before using any of them. This lets the model know exactly what data is available and how to access it. Keep your URIs and descriptions (docstrings) organized to make discovery easier.
Be careful about what you expose as a resource. All registered resources are visible to the MCP client. Never expose sensitive data such as passwords, API tokens, private keys, or personal data without adequate protection. Treat resources as public endpoints — if the model shouldn't be able to read some data, don't create a resource for it.
Let's test the resources we created in Claude Desktop. The model should be able to list the available resources and read the content of each one. Unlike tools (which the model actively calls), resources are data the model can access when it needs context to answer a question.
Test your resources with questions that encourage the model to retrieve them:
config://app.
status://hora-atual and return the dynamic value.
status://sistema.
docs://ajuda.
A good practice is to create a resource docs://ajuda in every MCP server that explains which tools and resources are available. When the user asks "What can you do?", the model reads this resource and presents the server's capabilities in an organized way. It's like creating a integrated help menu.
Ask about settings. Check that the model returns the fixed data from the config://app resource.
Ask for the time twice, with an interval in between. Check that the values change between queries.
Ask questions that use both tools and resources. For example: "Check the weather and tell me the current time."
Next Module: 3.7 — Adding Prompts