Understanding MCP Resources
If Tools are the “hands” of an AI that allow it to act, Resources are the “eyes” that allow it to read.
Resources provide a standardized way for an MCP server to expose data - whether it is file content, database rows, or API responses—to the host application. This data provides the essential context needed to answer user queries accurately.
The Role of Context
Imagine you are building an AI travel assistant. A user asks: “Plan a week-long itinerary for me in Japan.”
To create a realistic plan, the AI cannot just hallucinate times and prices. It needs hard data:
- Real-time flight schedules.
- Train timetables between Tokyo and Kyoto.
- Hotel availability.
An MCP Server uses Resources to expose this specific information.
Host vs. Model: Who Reads What?
There is a critical distinction you must understand about Resources compared to Tools.
Tools are designed for the Model (LLM). The model explicitly selects a tool and requests its execution.
Resources are designed for the Host Application.
An LLM cannot “call” a resource directly. Instead, the Host Application (like an IDE or a sophisticated AI agent wrapper) reads the resources.
Why this distinction? Efficiency.
Imagine you have a database of 10,000 train schedules. If you exposed this as a Tool, the LLM might try to request the entire dataset, burning through your token budget and context window instantly.
Instead, the workflow usually looks like this:
- The Host Application connects to the MCP server and reads the resources (e.g., flight data).
- The Host Application performs logic - perhaps filtering the data, performing a vector search, or summarising the content.
- The Host Application pastes only the relevant snippets into the prompt (the Context Window) for the LLM.
- The LLM uses that context to write the itinerary.
While you can technically feed resource data directly to an LLM, Resources are most powerful when used by the application layer to gather context before the conversation even begins.
Resources for UI Templates
Resources are not limited to just raw data like JSON or text. They can also serve code, such as HTML or JavaScript.
A prime example of this is the OpenAI Apps SDK.
When you build a visual app for ChatGPT (like a To-Do list widget), ChatGPT needs to know how to render that interface. It doesn’t guess the UI; it asks the MCP server for it.
The server exposes the entire HTML, CSS, and JavaScript of the widget as a Resource (e.g., ui://widget/todo.html). ChatGPT (the host) reads this resource and renders it inside an iframe in the chat window. This allows developers to deliver full-fledged graphical applications directly through the MCP protocol.
Types of Resources
MCP defines two primary ways to expose data:
1. Direct Resources
These point to fixed data sources. They look like static file paths or URLs.
bus://schedule/londonfile:///logs/error.txt-
ui://widget/dashboard.html(A UI template)
These are useful for data that is always located at a specific “place.”
2. Resource Templates
These are dynamic. They use parameters to fetch data based on variables.
train://schedule/{city}database://users/{id}/profile
The Host Application can fill in {city} or {id} to request specific slices of data.
Building a Resource Server
Now, you will build a server that demonstrates both types of resources. You will continue working in your lesson_2 directory.
Create a new file named resources_demo_server.py and add the following code:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Resource Demo")
@mcp.resource("bus://schedule/london")
def get_bus_schedule_in_london() -> str:
"""Returns the bus schedule for London."""
# In a real app, this would fetch from a database
schedule = "08:00 AM (Regular), 02:00 PM (Regular)"
return schedule
@mcp.resource("train://schedule/{city}")
def get_train_schedule(city: str) -> str:
"""Returns the train schedule for a specific city."""
# In a real app, this would fetch from a database
schedules = {
"berlin": "08:00 AM (Regular), 02:00 PM (Regular)",
"london": "08:00 AM (Express), 02:00 PM (Regular)",
"paris": "09:30 AM (High Speed), 04:00 PM (Regular)",
}
return schedules.get(city.lower(), "No schedule found for this city.")
if __name__ == "__main__":
mcp.run(transport="streamable-http")
Analyzing the Code
-
@mcp.resource("URI"): This decorator registers the function as a resource. -
URIs: Notice we are using custom schemes like
bus://andtrain://. MCP is protocol-agnostic; you don’t have to usehttp://orfile://. You can invent schemes that make sense for your domain. -
Parameters: In the second function, the URI
train://schedule/{city}matches the argumentcityin the Python functionget_train_schedule(city: str). FastMCP handles the routing automatically.
Running the Server
Run your new resource server using uv:
$ uv run --with mcp resources_demo_server.py
Your server is now exposing data endpoints! Unlike the tool server, which waited for a command to calculate something, this server is waiting to be read.
In the next section, you will inspect these resources to see exactly how the data is structured and retrieved.