Imagine you're building a smart library assistant powered by AI. The AI needs access to information: book catalogs, current inventory counts, user profiles, real-time availability status.
But here's the question: How does the AI actually get this data?
You could copy-paste information into every conversation, but that's tedious and doesn't scale. You could build custom APIs, but then you're reinventing the wheel for every new data source.
This is exactly what MCP Resources solve. They provide a standardized, elegant way to expose data to AI — whether that data is static (like configuration files) or dynamic (like real-time weather).
In this guide, you'll master MCP Resources from the ground up. We'll build three types: static resources, dynamic resources, and streaming resources. By the end, you'll create a working weather resource that AI can query on demand! 🌤️
🎯 Why Resources Matter: The Data Access Problem
Before diving into code, let's understand the fundamental problem Resources solve.
The Old Way: Manual Context Injection
Without MCP Resources, getting data to AI required manual steps:
- Copy-paste approach: User copies data, pastes into chat → Tedious and error-prone
- File upload: Attach files every conversation → Limited by file size and format
- Custom code: Write specific API integrations for each data source → Time-consuming
User: "What's our server version?"
AI: "I don't have access to that information."
User: *Goes to server, checks version, copies text*
User: "The version is 2.3.1"
AI: "Thank you! The server is running version 2.3.1"
Frustrating manual workflow for something that should be automatic!
The MCP Way: Automatic Data Access
With MCP Resources, data becomes discoverable and accessible:
User: "What's our server version?"
AI: *Automatically reads
version://info resource*
AI: "The server is running version 2.3.1"
Seamless! AI gets data directly from the source.
Resources turn data into first-class citizens that AI can discover, understand, and access on demand.
📚 What Exactly ARE Resources?
Let's establish a precise mental model before we start coding.
The Simple Definition
Resources are named pieces of data that AI can read.
Think of them as bookmarks or endpoints that point to information. Each resource has:
- URI (Unique Resource Identifier): An address like
config://apporuser://profile/123 - Name: Human-readable label like "Application Configuration"
- Description: What this resource contains
- Content: The actual data (text, JSON, files, etc.)
- MIME type: Format specification (text/plain, application/json, etc.)
Resources vs Tools: The Key Difference
Remember from previous lessons: control is what separates them.
| Aspect | Resources | Tools |
|---|---|---|
| Who Decides? | User or Application | AI Model |
| Access Method | Explicit selection/attachment | Automatic invocation |
| Can Modify Data? | ❌ No (read-only) | ✅ Yes |
| Use Case | Reference material, context | Actions, queries, operations |
If users should consciously choose to share the data → Use Resource
If AI should automatically fetch when needed → Use Tool
Example: Company salary data = Resource (sensitive, explicit choice)
Example: Public weather data = Could be either (depends on your design)
🔧 The Three Types of Resources
MCP Resources come in three flavors, each serving different use cases.
Type 1: Static Resources 📄
Definition: Data that doesn't change (or changes rarely).
Characteristics:
- Fixed URI (no parameters)
- Same content every time you read it
- Fast to serve (can be cached)
- Simple implementation
Examples:
version://server→ Server version numberconfig://database→ Database connection settingsdocs://api-reference→ API documentationlicense://terms→ Software license text
Type 2: Dynamic Resources 🔄
Definition: Data generated on-demand based on parameters.
Characteristics:
- URI with parameters (templates)
- Content varies based on inputs
- Generated when requested
- Can call APIs, query databases, etc.
Examples:
user://profile/{user_id}→ Specific user's profileweather://current/{city}→ Weather for a cityinventory://stock/{product_id}→ Product availabilitytime://current/{timezone}→ Current time in timezone
Type 3: Streaming Resources 📡
Definition: Data that updates continuously in real-time.
Characteristics:
- Pushes updates as they happen
- Client subscribes to changes
- Server sends notifications
- Efficient for frequently-changing data
Examples:
stock://price/AAPL→ Live stock price updateslogs://server/errors→ Real-time error log streammetrics://cpu-usage→ System performance metricschat://messages/{room_id}→ Live chat messages
Static: Data changes never or rarely (hours/days)
Dynamic: Data changes per request or needs parameters
Streaming: Data changes frequently (seconds/minutes) and clients need updates
🛠️ Building Your First Static Resource
Let's start simple and build up. We'll create a static resource that returns server version information.
Step 1: Set Up Your Environment
First, create a project folder and install the MCP SDK:
# Create project directory
mkdir mcp-resources-tutorial
cd mcp-resources-tutorial
# Create virtual environment
python -m venv venv
# Activate it
# On Windows:
venv\Scripts\activate
# On Mac/Linux:
source venv/bin/activate
# Install MCP SDK
pip install mcp
Step 2: Create Your First Static Resource
Create a file called static_resource.py:
"""
MCP Server with Static Resources
Demonstrates: Fixed data that doesn't change
"""
from mcp.server.fastmcp import FastMCP
# Create the MCP server
mcp = FastMCP("Static Resources Demo")
# ============================================
# STATIC RESOURCE 1: Server Version
# ============================================
@mcp.resource("version://server")
def get_server_version() -> str:
"""
Returns the current server version.
This is static data - same every time.
"""
return "Server Version 2.3.1 (Build 20240215)"
# ============================================
# STATIC RESOURCE 2: Configuration Info
# ============================================
@mcp.resource("config://app")
def get_app_config() -> dict:
"""
Returns application configuration.
Notice: We return a dict, MCP automatically converts to JSON.
"""
return {
"environment": "production",
"max_connections": 100,
"timeout_seconds": 30,
"features": {
"caching": True,
"logging": True,
"analytics": False
}
}
# ============================================
# STATIC RESOURCE 3: Welcome Message
# ============================================
@mcp.resource("info://welcome")
def get_welcome_message() -> str:
"""
Returns a static welcome message.
"""
return """
Welcome to the MCP Resources Demo Server!
This server demonstrates static resources that provide
fixed information. Static resources are perfect for:
- Configuration data
- Version information
- Documentation
- License terms
- Any data that rarely changes
"""
# ============================================
# Run the server
# ============================================
if __name__ == "__main__":
# This starts the server using stdio transport
# (communication via standard input/output)
mcp.run()
Step 3: Test Your Static Resources
Run the server with MCP's built-in inspector:
mcp dev static_resource.py
This opens an interactive web interface where you can:
- See all available resources
- Click to read each resource
- View the returned data
- Test different resources
Three static resources that:
• Have fixed URIs (no parameters)
• Return consistent data
• Can return different formats (string, dict/JSON)
• Are automatically discoverable by MCP clients
Understanding the Code
Let's break down what's happening:
1. The @mcp.resource() decorator:
@mcp.resource("version://server")
def get_server_version() -> str:
return "Server Version 2.3.1"
This decorator tells MCP:
- "This function provides a resource"
- "The resource URI is
version://server" - "When a client reads this URI, call this function"
- "The function's docstring becomes the resource description"
2. URI Structure:
MCP URIs follow a scheme similar to URLs:
scheme://path
Examples:
version://server
config://app
file:///home/user/data.txt
The scheme (version, config, etc.) is arbitrary — you choose whatever makes sense.
3. Return Types:
Resources can return:
- Strings: Plain text (MIME: text/plain)
- Dicts: Automatically converted to JSON (MIME: application/json)
- Lists: Also converted to JSON
- Custom objects: With proper serialization
🔄 Building Dynamic Resources
Now let's level up! Dynamic resources accept parameters to generate customized data.
The Power of URI Templates
Dynamic resources use URI templates with placeholders:
user://profile/{user_id}
↑
This is a parameter
When client requests: user://profile/alice
Your function receives: user_id = "alice"
Example: User Profile Resource
Create dynamic_resource.py:
"""
MCP Server with Dynamic Resources
Demonstrates: Data that changes based on parameters
"""
from mcp.server.fastmcp import FastMCP
from datetime import datetime
import json
# Create the MCP server
mcp = FastMCP("Dynamic Resources Demo")
# ============================================
# DYNAMIC RESOURCE 1: User Profiles
# ============================================
# Mock database of users
USER_DATABASE = {
"alice": {
"name": "Alice Johnson",
"email": "alice@example.com",
"role": "Developer",
"joined": "2023-01-15"
},
"bob": {
"name": "Bob Smith",
"email": "bob@example.com",
"role": "Designer",
"joined": "2023-03-22"
},
"charlie": {
"name": "Charlie Davis",
"email": "charlie@example.com",
"role": "Manager",
"joined": "2022-11-30"
}
}
@mcp.resource("user://profile/{user_id}")
def get_user_profile(user_id: str) -> dict:
"""
Get profile information for a specific user.
The {user_id} in the URI becomes a function parameter!
"""
# Look up user in our mock database
user = USER_DATABASE.get(user_id.lower())
if user:
return {
"success": True,
"user_id": user_id,
"profile": user
}
else:
return {
"success": False,
"error": f"User '{user_id}' not found",
"available_users": list(USER_DATABASE.keys())
}
# ============================================
# DYNAMIC RESOURCE 2: Current Time by Timezone
# ============================================
@mcp.resource("time://current/{timezone}")
def get_current_time(timezone: str) -> str:
"""
Get current time in a specific timezone.
Examples:
- time://current/UTC
- time://current/America/New_York
- time://current/Asia/Tokyo
"""
from datetime import datetime
import pytz
try:
# Get timezone object
tz = pytz.timezone(timezone)
# Get current time in that timezone
current_time = datetime.now(tz)
# Format nicely
return f"""
Current Time Information
========================
Timezone: {timezone}
Time: {current_time.strftime('%Y-%m-%d %H:%M:%S %Z')}
Day of Week: {current_time.strftime('%A')}
ISO Format: {current_time.isoformat()}
""".strip()
except pytz.exceptions.UnknownTimeZoneError:
return f"Error: Unknown timezone '{timezone}'"
# ============================================
# DYNAMIC RESOURCE 3: File Content Reader
# ============================================
@mcp.resource("file://read/{filename}")
def read_file_content(filename: str) -> str:
"""
Read content from a file.
WARNING: In production, validate filenames for security!
"""
import os
# Security: Only allow specific directory
ALLOWED_DIR = "./data"
filepath = os.path.join(ALLOWED_DIR, filename)
# Security: Prevent directory traversal
if ".." in filename or filename.startswith("/"):
return "Error: Invalid filename (security violation)"
try:
with open(filepath, 'r') as f:
content = f.read()
return f"File: {filename}\n\n{content}"
except FileNotFoundError:
return f"Error: File '{filename}' not found in {ALLOWED_DIR}"
except Exception as e:
return f"Error reading file: {str(e)}"
# ============================================
# DYNAMIC RESOURCE 4: Math Calculator
# ============================================
@mcp.resource("calc://result/{operation}/{a}/{b}")
def calculate(operation: str, a: str, b: str) -> dict:
"""
Perform basic math operations.
Examples:
- calc://result/add/5/3
- calc://result/multiply/7/6
"""
try:
num_a = float(a)
num_b = float(b)
operations = {
"add": num_a + num_b,
"subtract": num_a - num_b,
"multiply": num_a * num_b,
"divide": num_a / num_b if num_b != 0 else "Cannot divide by zero"
}
result = operations.get(operation.lower())
if result is None:
return {
"error": f"Unknown operation: {operation}",
"valid_operations": list(operations.keys())
}
return {
"operation": operation,
"a": num_a,
"b": num_b,
"result": result,
"formula": f"{num_a} {operation} {num_b} = {result}"
}
except ValueError:
return {"error": "Invalid numbers provided"}
except Exception as e:
return {"error": str(e)}
# ============================================
# Run the server
# ============================================
if __name__ == "__main__":
# Install pytz first: pip install pytz
mcp.run()
Install Required Dependencies
pip install pytz
Test Your Dynamic Resources
mcp dev dynamic_resource.py
Now try accessing:
user://profile/alice→ Alice's profileuser://profile/bob→ Bob's profiletime://current/UTC→ Current UTC timecalc://result/add/10/5→ 10 + 5 = 15
Dynamic resources that:
• Accept parameters via URI templates
• Generate customized responses
• Handle multiple parameters (calc example)
• Include error handling
• Return different data based on inputs
Understanding URI Templates
URI templates use curly braces to define parameters:
Single parameter:
@mcp.resource("user://profile/{user_id}")
def get_profile(user_id: str):
...
Multiple parameters:
@mcp.resource("data://query/{table}/{id}")
def query_data(table: str, id: str):
...
Complex example:
@mcp.resource("api://endpoint/{version}/{resource}/{action}")
def api_call(version: str, resource: str, action: str):
...
Always validate parameters! Users can put anything in URIs.
Bad:
user://profile/{user_id} → Run SQL directly with user_id
Good: Validate user_id against allowed patterns, sanitize input
File paths are especially dangerous (directory traversal attacks).
Always whitelist allowed directories and reject ".." in paths.
📡 Introduction to Streaming Resources
Streaming resources are the most advanced type. They push updates to clients automatically.
How Streaming Works
Traditional resources: Pull model
Client: "Give me data"
Server: "Here's the data"
[Connection closes]
Want updates? Client must request again.
Streaming resources: Push model
Client: "Subscribe me to updates"
Server: "Here's initial data"
[Connection stays open]
Server: "Update: new data available"
Server: "Update: new data available"
...
Client: "Unsubscribe"
[Connection closes]
When to Use Streaming
• Live stock prices (updates every second)
• Real-time logs (continuous flow)
• System metrics (CPU, memory changing constantly)
• Chat messages (new messages arrive unpredictably)
• IoT sensor data (continuous readings)
• Data that changes hourly or daily (use dynamic instead)
• One-time queries (use static/dynamic)
• Large datasets (overhead not worth it)
• Data that can be cached effectively
Basic Streaming Example
Create streaming_resource.py:
"""
MCP Server with Streaming Resources
Demonstrates: Real-time data updates
"""
from mcp.server.fastmcp import FastMCP
import asyncio
from datetime import datetime
# Create the MCP server
mcp = FastMCP("Streaming Resources Demo")
# ============================================
# STREAMING RESOURCE 1: Server Clock
# ============================================
@mcp.resource("clock://live")
async def streaming_clock() -> str:
"""
Streams the current time every second.
This is an async generator that yields updates.
"""
# Note: This is a simplified example
# Real streaming requires subscription support
while True:
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
yield f"Current Time: {current_time}"
await asyncio.sleep(1)
# ============================================
# STREAMING RESOURCE 2: Simulated Metrics
# ============================================
@mcp.resource("metrics://cpu")
async def streaming_cpu_usage() -> dict:
"""
Streams simulated CPU usage data.
In production, this would read real system metrics.
"""
import random
while True:
# Simulate CPU usage
usage = random.uniform(10.0, 90.0)
yield {
"timestamp": datetime.now().isoformat(),
"cpu_percent": round(usage, 2),
"status": "high" if usage > 70 else "normal"
}
await asyncio.sleep(2) # Update every 2 seconds
# Note: Full streaming support requires additional
# client-side implementation for subscriptions
# This example shows the server-side structure
if __name__ == "__main__":
mcp.run()
Full streaming requires:
1. Server implements notifications (shown above)
2. Client subscribes to resource
3. Server sends
notifications/resources/updated messages
4. Client re-reads resource to get new data
This is an advanced topic. Start with static and dynamic resources first!
🌤️ Hands-On Exercise: Build a Weather Resource
Time to put everything together! Let's build a complete weather resource system.
Project Overview
We'll create:
- Static resource: Supported cities list
- Dynamic resource: Current weather for any city
- Dynamic resource: Weather forecast
Step 1: Create the Weather Server
Create weather_server.py:
"""
Complete Weather MCP Server
Demonstrates all three resource types with a practical example
"""
from mcp.server.fastmcp import FastMCP
from datetime import datetime, timedelta
import random
# Create the MCP server
mcp = FastMCP("Weather Information Server")
# ============================================
# MOCK WEATHER DATA
# ============================================
# In production, this would call a real weather API
# For learning, we'll use mock data
SUPPORTED_CITIES = {
"london": {"country": "UK", "timezone": "Europe/London"},
"newyork": {"country": "USA", "timezone": "America/New_York"},
"tokyo": {"country": "Japan", "timezone": "Asia/Tokyo"},
"paris": {"country": "France", "timezone": "Europe/Paris"},
"sydney": {"country": "Australia", "timezone": "Australia/Sydney"},
"dubai": {"country": "UAE", "timezone": "Asia/Dubai"},
}
def generate_mock_weather(city: str) -> dict:
"""Generate realistic-looking mock weather data"""
conditions = ["Sunny", "Cloudy", "Rainy", "Partly Cloudy", "Windy"]
# Different temperature ranges by city
temp_ranges = {
"london": (10, 20),
"newyork": (15, 25),
"tokyo": (18, 28),
"paris": (12, 22),
"sydney": (20, 30),
"dubai": (30, 42),
}
min_temp, max_temp = temp_ranges.get(city, (15, 25))
return {
"temperature_celsius": round(random.uniform(min_temp, max_temp), 1),
"condition": random.choice(conditions),
"humidity_percent": random.randint(40, 80),
"wind_speed_kmh": random.randint(5, 30),
"timestamp": datetime.now().isoformat()
}
# ============================================
# STATIC RESOURCE: Supported Cities
# ============================================
@mcp.resource("weather://cities")
def get_supported_cities() -> dict:
"""
Returns list of cities for which weather data is available.
This is static - the list doesn't change often.
"""
cities_info = []
for city, details in SUPPORTED_CITIES.items():
cities_info.append({
"city": city.title(),
"country": details["country"],
"timezone": details["timezone"],
"query_uri": f"weather://current/{city}"
})
return {
"total_cities": len(SUPPORTED_CITIES),
"cities": cities_info,
"last_updated": "2024-02-15"
}
# ============================================
# STATIC RESOURCE: API Information
# ============================================
@mcp.resource("weather://info")
def get_api_info() -> str:
"""
Returns information about the weather API.
Static documentation resource.
"""
return """
Weather MCP Server - API Information
====================================
This server provides weather data for major cities worldwide.
AVAILABLE RESOURCES:
1. weather://cities
Get list of all supported cities
2. weather://current/{city}
Get current weather for a specific city
Example: weather://current/london
3. weather://forecast/{city}/{days}
Get weather forecast for upcoming days
Example: weather://forecast/tokyo/5
SUPPORTED CITIES:
London, New York, Tokyo, Paris, Sydney, Dubai
DATA FRESHNESS:
- Current weather: Real-time (mock data for demo)
- Forecasts: Generated dynamically
UNITS:
- Temperature: Celsius
- Wind Speed: km/h
- Humidity: Percentage
""".strip()
# ============================================
# DYNAMIC RESOURCE: Current Weather
# ============================================
@mcp.resource("weather://current/{city}")
def get_current_weather(city: str) -> dict:
"""
Get current weather for a specific city.
This is dynamic - data changes based on the city parameter.
"""
city_lower = city.lower()
# Check if city is supported
if city_lower not in SUPPORTED_CITIES:
return {
"success": False,
"error": f"City '{city}' not supported",
"supported_cities": list(SUPPORTED_CITIES.keys()),
"hint": "Use weather://cities to see all available cities"
}
# Generate weather data
weather_data = generate_mock_weather(city_lower)
city_info = SUPPORTED_CITIES[city_lower]
return {
"success": True,
"city": city.title(),
"country": city_info["country"],
"current_weather": weather_data,
"retrieved_at": datetime.now().isoformat()
}
# ============================================
# DYNAMIC RESOURCE: Weather Forecast
# ============================================
@mcp.resource("weather://forecast/{city}/{days}")
def get_weather_forecast(city: str, days: str) -> dict:
"""
Get weather forecast for upcoming days.
Demonstrates multiple parameters in dynamic resources.
"""
city_lower = city.lower()
# Validate city
if city_lower not in SUPPORTED_CITIES:
return {
"success": False,
"error": f"City '{city}' not supported"
}
# Validate days parameter
try:
num_days = int(days)
if num_days < 1 or num_days > 7:
return {
"success": False,
"error": "Days must be between 1 and 7"
}
except ValueError:
return {
"success": False,
"error": f"Invalid days value: '{days}'. Must be a number."
}
# Generate forecast
forecast = []
for day_offset in range(num_days):
forecast_date = datetime.now() + timedelta(days=day_offset)
weather = generate_mock_weather(city_lower)
forecast.append({
"date": forecast_date.strftime("%Y-%m-%d"),
"day_of_week": forecast_date.strftime("%A"),
"weather": weather
})
return {
"success": True,
"city": city.title(),
"country": SUPPORTED_CITIES[city_lower]["country"],
"forecast_days": num_days,
"forecast": forecast,
"generated_at": datetime.now().isoformat()
}
# ============================================
# DYNAMIC RESOURCE: Compare Cities
# ============================================
@mcp.resource("weather://compare/{city1}/{city2}")
def compare_weather(city1: str, city2: str) -> dict:
"""
Compare current weather between two cities.
Demonstrates processing multiple related parameters.
"""
city1_lower = city1.lower()
city2_lower = city2.lower()
# Validate both cities
if city1_lower not in SUPPORTED_CITIES:
return {"success": False, "error": f"City '{city1}' not supported"}
if city2_lower not in SUPPORTED_CITIES:
return {"success": False, "error": f"City '{city2}' not supported"}
# Get weather for both
weather1 = generate_mock_weather(city1_lower)
weather2 = generate_mock_weather(city2_lower)
# Calculate differences
temp_diff = abs(weather1["temperature_celsius"] - weather2["temperature_celsius"])
return {
"success": True,
"comparison": {
"city1": {
"name": city1.title(),
"weather": weather1
},
"city2": {
"name": city2.title(),
"weather": weather2
},
"analysis": {
"temperature_difference_celsius": round(temp_diff, 1),
"warmer_city": city1.title() if weather1["temperature_celsius"] > weather2["temperature_celsius"] else city2.title(),
"same_condition": weather1["condition"] == weather2["condition"]
}
},
"compared_at": datetime.now().isoformat()
}
# ============================================
# Run the server
# ============================================
if __name__ == "__main__":
print("🌤️ Weather MCP Server Starting...")
print("📍 Supported cities:", ", ".join(SUPPORTED_CITIES.keys()))
print("\n💡 Test with: mcp dev weather_server.py")
mcp.run()
Step 2: Test Your Weather Server
Run the server:
mcp dev weather_server.py
Try these resources:
weather://info→ API documentationweather://cities→ Supported cities listweather://current/london→ London weatherweather://forecast/tokyo/5→ Tokyo 5-day forecastweather://compare/dubai/sydney→ Compare two cities
You just built a complete MCP resource server with:
• Static resources (API info, cities list)
• Dynamic resources (current weather, forecasts)
• Multiple parameter handling (compare cities)
• Error handling and validation
• Realistic data structure
Step 3: Connect to Claude Desktop (Optional)
To use your weather server with Claude:
mcp install weather_server.py --name "Weather Info"
Now in Claude Desktop, you can ask:
- "What cities can you check weather for?"
- "What's the weather in Tokyo?"
- "Compare weather between London and Dubai"
Claude will automatically access your MCP resources to answer!
🎨 Resource Design Best Practices
1. Use Descriptive URI Schemes
res1://data → What is "res1"?
x://y/{z} → Meaningless names
resource://resource → Redundant
weather://current/{city} → Clear purpose
user://profile/{user_id} → Self-documenting
docs://api/{version} → Organized hierarchy
2. Return Consistent Data Structures
Always return the same shape for similar resources:
# GOOD: Consistent structure
@mcp.resource("data://item/{id}")
def get_item(id: str) -> dict:
return {
"success": True,
"data": {...},
"metadata": {...}
}
# If error:
return {
"success": False,
"error": "Description",
"error_code": "NOT_FOUND"
}
3. Include Helpful Metadata
Add timestamps, version info, and context:
return {
"data": actual_data,
"retrieved_at": datetime.now().isoformat(),
"source": "database",
"version": "2.0",
"cached": False
}
4. Handle Errors Gracefully
✓ Validate all parameters
✓ Return clear error messages
✓ Suggest valid alternatives
✓ Never let exceptions crash the server
✓ Log errors for debugging
@mcp.resource("data://query/{table}")
def query_table(table: str) -> dict:
ALLOWED_TABLES = ["users", "products", "orders"]
if table not in ALLOWED_TABLES:
return {
"success": False,
"error": f"Table '{table}' not allowed",
"allowed_tables": ALLOWED_TABLES,
"hint": "Use one of the allowed table names"
}
# Proceed with query...
5. Document Your Resources
Always write clear docstrings:
@mcp.resource("api://endpoint/{version}")
def get_api_endpoint(version: str) -> dict:
"""
Get API endpoint configuration for a specific version.
Parameters:
- version: API version (e.g., "v1", "v2", "v3")
Returns:
- Dictionary with endpoint URLs and configuration
Examples:
- api://endpoint/v1
- api://endpoint/v2
"""
# Implementation...
🚫 Common Mistakes to Avoid
Mistake #1: Using Resources for Actions
@mcp.resource("action://delete/{user_id}")
Resources are read-only! Don't use them for actions that modify data.
Use a Tool for delete operations:
@mcp.tool() def delete_user(user_id: str):
Mistake #2: Returning Huge Data
@mcp.resource("data://all-users")
Returns 1 million user records → Crashes client
Use pagination parameters:
@mcp.resource("data://users/{page}/{limit}")
Returns manageable chunks
Mistake #3: No Input Validation
@mcp.resource("file://read/{path}")
def read_file(path: str):
with open(path) as f: # DANGEROUS!
return f.read()
User could request:
file://read/../../../etc/passwd
@mcp.resource("file://read/{filename}")
def read_file(filename: str):
if ".." in filename or "/" in filename:
return {"error": "Invalid filename"}
safe_path = os.path.join(SAFE_DIR, filename)
# Validate path is within allowed directory
if not safe_path.startswith(SAFE_DIR):
return {"error": "Access denied"}
# Now safe to read
Mistake #4: Inconsistent Return Types
Sometimes returns string, sometimes dict, sometimes list
Clients can't handle unpredictable responses
Always return the same structure:
{"success": bool, "data": any, "error": str | null}
📊 Quick Summary & Key Takeaways
Named pieces of data that AI can read.
Read-only, application/user-controlled access.
1. Static → Fixed data, no parameters
2. Dynamic → Generated on-demand with parameters
3. Streaming → Real-time updates (advanced)
• Use
@mcp.resource(uri) decorator
• URI templates with
{parameters} for dynamic
• Return strings, dicts, or lists
• Always include error handling
• Write clear docstrings
✓ Descriptive URI schemes
✓ Consistent data structures
✓ Validate all inputs
✓ Include helpful metadata
✓ Handle errors gracefully
🎯 Next Steps: Practice Projects
Now that you understand resources, challenge yourself with these projects:
Beginner Projects
-
Configuration Server
Create resources for app settings, feature flags, and environment variables -
Bookmark Manager
bookmark://listandbookmark://category/{name} -
Quote of the Day
quote://dailyandquote://random/{category}
Intermediate Projects
-
Database Browser
db://tables,db://schema/{table},db://query/{table}/{limit} -
File System Explorer
fs://list/{directory},fs://read/{path},fs://info/{file} -
API Aggregator
Combine multiple APIs into unified resources
Advanced Projects
-
Real Weather Integration
Connect to actual weather API (OpenWeather, WeatherAPI) -
Log Analyzer
Stream real-time logs withlogs://stream/{service} -
Multi-Source Dashboard
Aggregate data from databases, APIs, and files
📚 Essential Resources
• MCP Specification: modelcontextprotocol.io/docs
• Python SDK: github.com/anthropics/mcp-python
• FastMCP Framework: github.com/jlowin/fastmcp
Testing Tools:
• MCP Inspector:
mcp dev your_server.py
• Claude Desktop: Connect real AI to your resources
Community:
• MCP Discord
• GitHub Discussions
• Example servers repository
Comments
Post a Comment