Skip to main content

MCP Resources — Static & Dynamic Data

Calculating read time…

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
❌ The Problem:

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:

✅ The Solution:

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://app or user://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
💡 Quick Decision Rule:

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 number
  • config://database → Database connection settings
  • docs://api-reference → API documentation
  • license://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 profile
  • weather://current/{city} → Weather for a city
  • inventory://stock/{product_id} → Product availability
  • time://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 updates
  • logs://server/errors → Real-time error log stream
  • metrics://cpu-usage → System performance metrics
  • chat://messages/{room_id} → Live chat messages
🎓 Choosing the Right Type

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
✅ What You Just Built:

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 profile
  • user://profile/bob → Bob's profile
  • time://current/UTC → Current UTC time
  • calc://result/add/10/5 → 10 + 5 = 15
✅ What You Just Built:

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):
    ...
⚠️ Security Warning:

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

✅ Use Streaming For:

• 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)
❌ Don't Use Streaming For:

• 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()
🎓 Streaming Implementation Note

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 documentation
  • weather://cities → Supported cities list
  • weather://current/london → London weather
  • weather://forecast/tokyo/5 → Tokyo 5-day forecast
  • weather://compare/dubai/sydney → Compare two cities
✅ Congratulations!

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

❌ Bad URIs:

res1://data → What is "res1"?
x://y/{z} → Meaningless names
resource://resource → Redundant
✅ Good URIs:

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

💡 Error Handling Checklist:

✓ 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

❌ Wrong:

@mcp.resource("action://delete/{user_id}")

Resources are read-only! Don't use them for actions that modify data.
✅ Correct:

Use a Tool for delete operations:
@mcp.tool() def delete_user(user_id: str):

Mistake #2: Returning Huge Data

❌ Wrong:

@mcp.resource("data://all-users")
Returns 1 million user records → Crashes client
✅ Correct:

Use pagination parameters:
@mcp.resource("data://users/{page}/{limit}")
Returns manageable chunks

Mistake #3: No Input Validation

❌ Wrong:

@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
✅ Correct:

@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

❌ Wrong:

Sometimes returns string, sometimes dict, sometimes list
Clients can't handle unpredictable responses
✅ Correct:

Always return the same structure:
{"success": bool, "data": any, "error": str | null}

📊 Quick Summary & Key Takeaways

What Are Resources?

Named pieces of data that AI can read.
Read-only, application/user-controlled access.
Three Types:

1. Static → Fixed data, no parameters
2. Dynamic → Generated on-demand with parameters
3. Streaming → Real-time updates (advanced)
Key Implementation Points:

• Use @mcp.resource(uri) decorator
• URI templates with {parameters} for dynamic
• Return strings, dicts, or lists
• Always include error handling
• Write clear docstrings
Best Practices:

✓ 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

  1. Configuration Server
    Create resources for app settings, feature flags, and environment variables
  2. Bookmark Manager
    bookmark://list and bookmark://category/{name}
  3. Quote of the Day
    quote://daily and quote://random/{category}

Intermediate Projects

  1. Database Browser
    db://tables, db://schema/{table}, db://query/{table}/{limit}
  2. File System Explorer
    fs://list/{directory}, fs://read/{path}, fs://info/{file}
  3. API Aggregator
    Combine multiple APIs into unified resources

Advanced Projects

  1. Real Weather Integration
    Connect to actual weather API (OpenWeather, WeatherAPI)
  2. Log Analyzer
    Stream real-time logs with logs://stream/{service}
  3. Multi-Source Dashboard
    Aggregate data from databases, APIs, and files

📚 Essential Resources

Official Documentation:
• 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