MCP Resources: Exposing Read-Only Data Sources to AI Agents
Learn how MCP resources differ from tools, how to define resource URIs and templates, expose read-only data to AI agents with proper content types, and implement pagination for large datasets.
Resources vs Tools: A Critical Distinction
MCP defines two primary ways to expose data to AI agents: tools and resources. Tools are functions the agent calls to perform actions — they take input, do something, and return output. Resources are read-only data sources the agent can access without executing any logic.
Think of it this way: a tool is like calling an API endpoint with parameters. A resource is like reading a file from a known path. Tools have side effects and dynamic behavior. Resources are static or semi-static data that the agent can pull into its context.
This distinction matters for agent design. When an agent needs to read a configuration file, fetch documentation, or retrieve system status, a resource is more appropriate than a tool. Resources are explicitly read-only, which means the agent runtime can prefetch them, cache them, and include them in the prompt without worrying about side effects.
Defining Resources in Python
In FastMCP, resources are defined with the @mcp_server.resource() decorator. Each resource has a URI that the agent uses to request it:
from mcp.server.fastmcp import FastMCP
mcp_server = FastMCP(name="DataServer")
@mcp_server.resource("config://app/settings")
async def get_app_settings() -> str:
"""Return the current application configuration as JSON."""
import json
settings = {
"version": "2.1.0",
"environment": "production",
"max_connections": 100,
"features": {
"caching": True,
"rate_limiting": True,
"audit_logging": True,
},
}
return json.dumps(settings, indent=2)
@mcp_server.resource("docs://api/endpoints")
async def get_api_docs() -> str:
"""Return API endpoint documentation."""
return """
GET /users - List all users (paginated)
GET /users/{id} - Get user by ID
POST /users - Create a new user
PUT /users/{id} - Update user
DELETE /users/{id} - Delete user
GET /health - Health check endpoint
"""
The URI scheme is flexible — you can use config://, docs://, data://, or any custom scheme that makes semantic sense for your domain. The agent sees these URIs during resource discovery and can request any of them.
Resource Templates for Dynamic Data
Resource templates use URI patterns with placeholders, allowing agents to request data for specific entities:
@mcp_server.resource("users://{user_id}/profile")
async def get_user_profile(user_id: str) -> str:
"""Retrieve a user profile by ID."""
import json
import aiosqlite
async with aiosqlite.connect("app.db") as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"SELECT id, name, email, role, created_at "
"FROM users WHERE id = ?",
[user_id],
)
row = await cursor.fetchone()
if not row:
return json.dumps({"error": "User not found"})
return json.dumps(dict(row), indent=2, default=str)
@mcp_server.resource("metrics://{service_name}/summary")
async def get_service_metrics(service_name: str) -> str:
"""Get current performance metrics for a service."""
import json
import random
# In production, pull from Prometheus or your metrics store
metrics = {
"service": service_name,
"uptime_seconds": 86400 * 7,
"requests_per_minute": random.randint(100, 500),
"error_rate_percent": round(random.uniform(0.1, 2.0), 2),
"p99_latency_ms": random.randint(50, 200),
}
return json.dumps(metrics, indent=2)
When the agent calls resources/read with URI users://42/profile, FastMCP extracts 42 as the user_id parameter and invokes the function.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
Content Types and Binary Data
Resources can return different content types. Text resources are the most common, but MCP also supports binary content encoded as base64:
@mcp_server.resource(
"reports://daily/summary",
mime_type="application/json",
)
async def get_daily_report() -> str:
"""Return today's summary report as structured JSON."""
import json
from datetime import date
report = {
"date": str(date.today()),
"total_orders": 1247,
"revenue": 89450.00,
"top_products": [
{"name": "Widget A", "units": 342},
{"name": "Widget B", "units": 218},
],
}
return json.dumps(report, indent=2)
The mime_type parameter tells the agent what kind of data to expect. For JSON data, use application/json. For plain text, use text/plain. For markdown documentation, use text/markdown.
Pagination for Large Resources
When a resource returns a large dataset, implement pagination using URI query parameters or template parameters:
@mcp_server.resource("logs://app/recent/{page}")
async def get_recent_logs(page: str) -> str:
"""Get recent application logs, paginated by page number."""
import json
import aiosqlite
page_num = int(page)
page_size = 50
offset = (page_num - 1) * page_size
async with aiosqlite.connect("app.db") as db:
cursor = await db.execute(
"SELECT timestamp, level, message FROM logs "
"ORDER BY timestamp DESC LIMIT ? OFFSET ?",
[page_size, offset],
)
rows = await cursor.fetchall()
count_cursor = await db.execute("SELECT COUNT(*) FROM logs")
total = (await count_cursor.fetchone())[0]
return json.dumps({
"page": page_num,
"page_size": page_size,
"total_records": total,
"total_pages": (total + page_size - 1) // page_size,
"logs": [
{"timestamp": r[0], "level": r[1], "message": r[2]}
for r in rows
],
}, indent=2)
The agent can iterate through pages by incrementing the page parameter in the URI template. Include total counts and page metadata so the agent knows when it has retrieved everything.
FAQ
When should I use a resource instead of a tool?
Use a resource when the data is read-only and does not require complex input parameters. Resources are ideal for configuration, documentation, status information, and reference data. Use a tool when the operation requires multiple parameters, has side effects, or involves computation beyond simple data retrieval.
Can resources be updated or are they always static?
Resources are read-only from the agent's perspective — there is no resources/write method in MCP. However, the data backing a resource can change over time (like metrics or logs). MCP supports resource subscriptions via resources/subscribe, where the server notifies the client when a resource changes so the agent can re-read it.
How does the agent discover available resources?
The agent calls resources/list to get a list of all available resources with their URIs, names, descriptions, and MIME types. For resource templates, the agent calls resources/templates/list to discover parameterized URI patterns it can fill in with specific values.
#MCP #Resources #AIAgents #DataSources #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.