Chapter 3: Sharing Data - FastMCP Resources (Resource
, ResourceManager
)
In Chapter 2: Easier Server Building with FastMCP
, we saw how FastMCP
and the @server.tool()
decorator make it easy to create servers that can perform actions for clients, like our echo
tool.
But what if your server just needs to share some data? Maybe it has a configuration file the client needs, a list of available items, or some text generated on the fly. You could make a tool for each piece of data, but that feels clunky. Isn’t there a way for clients to just browse and read data sources directly?
Yes, there is! Welcome to FastMCP Resources.
The Digital Library: Resources and the Resource Manager
Imagine your FastMCP
server is like a digital library. Inside this library, you have various pieces of information:
- Simple text notes (like a welcome message).
- Static files (like a configuration file or a small image).
- Information that changes (like the current time or weather).
Each piece of information in this library is called a Resource
. Think of each Resource
as a book, a document, or maybe even a live news feed within the library.
To access any item in a library, you need its unique identifier – like a call number or an ISBN. In FastMCP, resources are identified by a URI (Uniform Resource Identifier). This looks similar to a web URL (like http://example.com
) but can use different schemes (like data://
, file://
, weather://
). For example, a welcome message might have the URI data://welcome_message
.
Now, how do you find out what books are in the library, or add a new one? You talk to the librarian. In FastMCP
, the component that keeps track of all the available resources is called the ResourceManager
.
Resource
: A specific piece of data (static, dynamic, file) accessible via a URI. (The book)ResourceManager
: Manages all theResource
objects registered with theFastMCP
server. (The librarian)- URI: The unique address used to find and access a
Resource
. (The call number)
Clients can ask the ResourceManager
(via FastMCP
) to list all available resources (listResources
) and then request the content of a specific resource using its URI (readResource
).
Adding Books to the Library: Using @server.resource()
Just like @server.tool()
made it easy to add actions, FastMCP
provides a simple decorator, @server.resource()
, to add data resources to your server’s library (its ResourceManager
).
Let’s add a simple, static welcome message to our server.
File: library_server.py
(Version 1)
# 1. Import FastMCP
from mcp.server.fastmcp import FastMCP
# 2. Create the server instance
server = FastMCP(name="LibraryServer")
# 3. Define a function that returns our static data
def get_welcome_message() -> str:
"""Returns a simple welcome string."""
return "Welcome to the Library Server!"
# 4. Use the @server.resource() decorator to register the function's result
# The URI "data://greeting" will be used by clients to access this.
@server.resource(uri="data://greeting", description="A friendly greeting.")
def welcome_resource():
# This function will be called *when a client reads* the resource.
# It just returns the static message.
return get_welcome_message() # Or simply: return "Welcome..."
# Standard run block
if __name__ == "__main__":
print(f"Starting {server.name}...")
server.run()
print(f"{server.name} finished.")
(Self-correction: The previous example was slightly complex with two functions. Let’s simplify.)
File: library_server.py
(Version 1 - Simpler)
# 1. Import FastMCP
from mcp.server.fastmcp import FastMCP
# 2. Create the server instance
server = FastMCP(name="LibraryServer")
# 3. Use the @server.resource() decorator directly on the function
# that provides the data.
@server.resource(uri="data://greeting", description="A friendly greeting.")
def welcome_message() -> str:
"""
This function is registered as the resource 'data://greeting'.
It will be called when a client reads this resource URI.
'-> str' indicates it returns text. FastMCP sets MIME type to text/plain.
"""
print("Resource 'data://greeting' was read!") # Server-side log
return "Welcome to the Library Server! Enjoy your stay."
# 4. Standard run block
if __name__ == "__main__":
print(f"Starting {server.name}...")
server.run() # Start listening
print(f"{server.name} finished.")
Explanation:
server = FastMCP(...)
: Creates our server (the library). Inside, it creates aResourceManager
(the librarian).@server.resource(...)
: This is our decorator “button”.uri="data://greeting"
: We assign a unique URI (call number) to this resource. Thedata://
part is just a convention here, you can choose meaningful schemes.description="..."
: A helpful description for clients browsing the library.
def welcome_message() -> str:
: This function provides the content for the resource.-> str
: The type hint tellsFastMCP
this resource provides text data. It will automatically set themime_type
totext/plain
.- The function’s body simply returns the string we want to share.
- Important: This function is only executed when a client actually asks to read the resource
data://greeting
. It’s not run when the server starts.
server.run()
: Starts the server. TheResourceManager
now knows aboutdata://greeting
.
If you run this server (mcp run library_server.py
), a client could:
- Call
listResources
and seedata://greeting
in the list. - Call
readResource
with the URIdata://greeting
. FastMCP
would ask theResourceManager
, find the registered function (welcome_message
), run it, get the string"Welcome..."
, and send it back to the client.
Dynamic Data: Resources Generated on the Fly
Resources don’t have to be static text. The function you decorate can do calculations, read files, or anything else to generate the data when it’s requested. This is great for information that changes.
Let’s add a resource that tells the current time.
File: library_server.py
(Version 2)
import datetime # Need this module to get the current time
from mcp.server.fastmcp import FastMCP
server = FastMCP(name="LibraryServer")
@server.resource(uri="data://greeting", description="A friendly greeting.")
def welcome_message() -> str:
print("Resource 'data://greeting' was read!")
return "Welcome to the Library Server! Enjoy your stay."
# NEW: Add a dynamic resource for the current time
@server.resource(uri="time://current", description="The current server time.")
def current_time() -> str:
"""Returns the current time as a string."""
now = datetime.datetime.now()
time_str = now.strftime("%Y-%m-%d %H:%M:%S")
print(f"Resource 'time://current' was read! Time is {time_str}")
# The function calculates the time *each time* it's called
return f"The current server time is: {time_str}"
# Standard run block
if __name__ == "__main__":
print(f"Starting {server.name}...")
server.run()
print(f"{server.name} finished.")
Now, every time a client reads time://current
, the current_time
function will execute, get the latest time, format it, and return it.
Parameterized Data: Resource Templates
What if you have data related to specific items, like weather information for different cities? You wouldn’t want to create a separate resource function for every city (weather_london
, weather_paris
, etc.).
Resource URIs can contain parameters, indicated by curly braces {}
. When you define a resource with a parameterized URI and a function that accepts arguments matching those parameters, FastMCP
creates a Resource Template.
File: library_server.py
(Version 3)
import datetime
import random # To simulate getting weather data
from mcp.server.fastmcp import FastMCP
server = FastMCP(name="LibraryServer")
@server.resource(uri="data://greeting", description="A friendly greeting.")
def welcome_message() -> str:
return "Welcome to the Library Server! Enjoy your stay."
@server.resource(uri="time://current", description="The current server time.")
def current_time() -> str:
now = datetime.datetime.now()
return f"The current server time is: {now.strftime('%Y-%m-%d %H:%M:%S')}"
# NEW: Add a resource template for weather
# The URI contains a parameter {city_name}
@server.resource(uri="weather://forecast/{city_name}",
description="Provides a dummy weather forecast.")
# The function accepts an argument matching the URI parameter
def get_weather_forecast(city_name: str) -> str:
"""Generates a fake weather forecast for the given city."""
print(f"Resource template 'weather://forecast/' read for city: {city_name}")
# In a real app, you'd fetch actual weather here based on city_name
temperature = random.randint(5, 25)
conditions = random.choice(["Sunny", "Cloudy", "Rainy"])
return f"Forecast for {city_name.capitalize()}: {temperature}°C, {conditions}"
# Standard run block
if __name__ == "__main__":
print(f"Starting {server.name}...")
server.run()
print(f"{server.name} finished.")
Explanation:
@server.resource(uri="weather://forecast/{city_name}", ...)
: We define a URI with a placeholder{city_name}
.def get_weather_forecast(city_name: str) -> str:
: The function signature includes a parametercity_name
that exactly matches the name inside the curly braces in the URI.- How it works:
- When a client asks to read a URI like
weather://forecast/london
,FastMCP
sees it matches the template. - It extracts the value “london” from the URI.
- It calls the
get_weather_forecast
function, passing"london"
as thecity_name
argument. - The function generates the forecast for London and returns the string.
- If the client asks for
weather://forecast/paris
, the same function is called, but withcity_name="paris"
.
- When a client asks to read a URI like
This template approach is very powerful for providing structured data without writing repetitive code. Clients would use listResourceTemplates
to discover templates like this.
How Resources Work Under the Hood
Using @server.resource()
feels simple, but what’s happening inside FastMCP
?
- Registration: When Python processes your code and sees
@server.resource(uri="data://greeting")
above thewelcome_message
function, it calls an internalserver.resource()
method.- This method analyzes the URI and the function.
- If the URI has no
{}
parameters and the function takes no arguments (or only aContext
argument), it creates aFunctionResource
object. This object essentially wraps yourwelcome_message
function, storing its details (URI, description, the function itself). - If the URI does have parameters matching the function’s arguments (like
weather://forecast/{city_name}
andget_weather_forecast(city_name: str)
), it creates aResourceTemplate
object instead. - It then tells the
ResourceManager
(the librarian) to store thisFunctionResource
orResourceTemplate
. (This happens via_resource_manager.add_resource
or_resource_manager.add_template
, referencingserver/fastmcp/resources/resource_manager.py
).
- Client Request (
readResource
):- A client sends an MCP message:
{"method": "readResource", "params": {"uri": "data://greeting"}}
. FastMCP
receives this and calls its internalread_resource
handler (seeserver/fastmcp/server.py
).- The handler asks the
ResourceManager
: “Do you have a resource for the URIdata://greeting
?” (_resource_manager.get_resource
). - The
ResourceManager
checks its list of concrete resources. It finds theFunctionResource
associated withdata://greeting
. FastMCP
(or theResourceManager
) calls the.read()
method on thatFunctionResource
object (seeserver/fastmcp/resources/types.py
).- The
FunctionResource.read()
method executes the original Python function you decorated (welcome_message()
). - Your function returns the string
"Welcome..."
. FastMCP
packages this string into a valid MCPreadResource
response and sends it back to the client.
- A client sends an MCP message:
- Client Request (
readResource
with Template):- Client sends:
{"method": "readResource", "params": {"uri": "weather://forecast/london"}}
. FastMCP
asksResourceManager
forweather://forecast/london
.ResourceManager
checks concrete resources – no match.ResourceManager
checks itsResourceTemplate
list. It finds theweather://forecast/{city_name}
template matches the requested URI.- It extracts the parameter
{"city_name": "london"}
. - It uses the template to dynamically create a temporary
FunctionResource
for this specific request, configured to callget_weather_forecast(city_name="london")
. FastMCP
calls.read()
on this temporary resource.- The
get_weather_forecast("london")
function runs and returns the forecast string. FastMCP
sends the result back.
- Client sends:
Simplified Sequence Diagram (readResource
for data://greeting
):
sequenceDiagram
participant Client
participant FastMCP_Server as FastMCP (library_server.py)
participant ResManager as ResourceManager (_resource_manager)
participant FuncResource as FunctionResource (wraps welcome_message)
participant WelcomeFunc as welcome_message()
Client->>+FastMCP_Server: Send MCP Request: readResource(uri="data://greeting")
FastMCP_Server->>+ResManager: get_resource("data://greeting")
ResManager-->>-FastMCP_Server: Return FunctionResource object
FastMCP_Server->>+FuncResource: resource.read()
FuncResource->>+WelcomeFunc: Call original function welcome_message()
WelcomeFunc-->>-FuncResource: Return "Welcome..."
FuncResource-->>-FastMCP_Server: Return "Welcome..."
FastMCP_Server->>-Client: Send MCP Response: content="Welcome..."
While @server.resource()
is the easiest way, the SDK also provides classes like TextResource
, BinaryResource
, FileResource
(see server/fastmcp/resources/types.py
) that you could potentially instantiate and add directly using server.add_resource(MyTextResource(...))
, but the decorator handles wrapping your functions nicely.
Conclusion
You’ve learned about FastMCP Resources – the way to share data from your server like items in a digital library.
- Resources (
Resource
) are data sources (text, files, dynamic content) identified by URIs. - The
ResourceManager
keeps track of all registered resources. - The
@server.resource()
decorator is the easiest way to add resources by wrapping Python functions. - Resources can be static (returning the same data) or dynamic (generating data when read).
- Resource Templates allow you to handle parameterized URIs (like
weather://forecast/{city}
) efficiently. - Clients use
listResources
,listResourceTemplates
, andreadResource
to interact with your server’s data library.
Resources are essential for providing context, configuration, or any other data your clients might need to consume without executing a complex action.
In the next chapter, we’ll take a closer look at the other main building block we briefly saw in Chapter 2: FastMCP Tools (Tool
, ToolManager
), and explore how they handle actions and inputs in more detail.
Generated by AI Codebase Knowledge Builder