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 the Resource objects registered with the FastMCP 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:

  1. server = FastMCP(...): Creates our server (the library). Inside, it creates a ResourceManager (the librarian).
  2. @server.resource(...): This is our decorator “button”.
    • uri="data://greeting": We assign a unique URI (call number) to this resource. The data:// part is just a convention here, you can choose meaningful schemes.
    • description="...": A helpful description for clients browsing the library.
  3. def welcome_message() -> str:: This function provides the content for the resource.
    • -> str: The type hint tells FastMCP this resource provides text data. It will automatically set the mime_type to text/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.
  4. server.run(): Starts the server. The ResourceManager now knows about data://greeting.

If you run this server (mcp run library_server.py), a client could:

  1. Call listResources and see data://greeting in the list.
  2. Call readResource with the URI data://greeting.
  3. FastMCP would ask the ResourceManager, 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:

  1. @server.resource(uri="weather://forecast/{city_name}", ...): We define a URI with a placeholder {city_name}.
  2. def get_weather_forecast(city_name: str) -> str:: The function signature includes a parameter city_name that exactly matches the name inside the curly braces in the URI.
  3. 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 the city_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 with city_name="paris".

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?

  1. Registration: When Python processes your code and sees @server.resource(uri="data://greeting") above the welcome_message function, it calls an internal server.resource() method.
    • This method analyzes the URI and the function.
    • If the URI has no {} parameters and the function takes no arguments (or only a Context argument), it creates a FunctionResource object. This object essentially wraps your welcome_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} and get_weather_forecast(city_name: str)), it creates a ResourceTemplate object instead.
    • It then tells the ResourceManager (the librarian) to store this FunctionResource or ResourceTemplate. (This happens via _resource_manager.add_resource or _resource_manager.add_template, referencing server/fastmcp/resources/resource_manager.py).
  2. Client Request (readResource):
    • A client sends an MCP message: {"method": "readResource", "params": {"uri": "data://greeting"}}.
    • FastMCP receives this and calls its internal read_resource handler (see server/fastmcp/server.py).
    • The handler asks the ResourceManager: “Do you have a resource for the URI data://greeting?” (_resource_manager.get_resource).
    • The ResourceManager checks its list of concrete resources. It finds the FunctionResource associated with data://greeting.
    • FastMCP (or the ResourceManager) calls the .read() method on that FunctionResource object (see server/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 MCP readResource response and sends it back to the client.
  3. Client Request (readResource with Template):
    • Client sends: {"method": "readResource", "params": {"uri": "weather://forecast/london"}}.
    • FastMCP asks ResourceManager for weather://forecast/london.
    • ResourceManager checks concrete resources – no match.
    • ResourceManager checks its ResourceTemplate list. It finds the weather://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 call get_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.

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, and readResource 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