Lesson 6 - Step 3 of 5

The resources/list Method

Implement the method that tells clients what resources are available.

Purpose of resources/list

The resources/list method allows clients to discover what resources your server provides. Clients call this to get a list of all available resources and their metadata.

Advertising Capabilities

Remember from the MCP handshake: your server must advertise that it supports resources by including "resources": {} in its capabilities. Only then will clients call resources/list.

Request Format

The client sends a simple request with no required parameters:

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "resources/list",
    "params": {}
}

Response Format

The server responds with an array of resource objects:

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "resources": [
            {
                "uri": "file:///project/config.json",
                "name": "config.json",
                "description": "Application configuration",
                "mimeType": "application/json"
            },
            {
                "uri": "file:///project/src/main.py",
                "name": "main.py",
                "description": "Main application entry point",
                "mimeType": "text/x-python"
            },
            {
                "uri": "db://app/users/current",
                "name": "Current User",
                "description": "Currently logged in user profile",
                "mimeType": "application/json"
            }
        ]
    }
}

Implementing in Python

Here's how to implement the resources/list handler:

python
"""
resource_server.py - MCP server with resources/list support
"""

import json
from http.server import HTTPServer, BaseHTTPRequestHandler

# Define available resources
RESOURCES = [
    {
        "uri": "file:///project/config.json",
        "name": "config.json",
        "description": "Application configuration",
        "mimeType": "application/json"
    },
    {
        "uri": "file:///project/README.md",
        "name": "README.md",
        "description": "Project documentation",
        "mimeType": "text/markdown"
    }
]


def handle_resources_list(params: dict) -> dict:
    """Handle resources/list requests."""
    return {
        "resources": RESOURCES
    }


class MCPHandler(BaseHTTPRequestHandler):
    """Handle MCP requests."""

    def send_json(self, data: dict, status: int = 200):
        """Send a JSON response."""
        body = json.dumps(data, indent=2).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_POST(self):
        """Handle POST requests."""
        # Read and parse request
        content_length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(content_length)
        request = json.loads(body.decode("utf-8"))

        method = request.get("method")
        params = request.get("params", {})
        request_id = request.get("id")

        # Route to appropriate handler
        if method == "resources/list":
            result = handle_resources_list(params)
            self.send_json({
                "jsonrpc": "2.0",
                "id": request_id,
                "result": result
            })
        else:
            self.send_json({
                "jsonrpc": "2.0",
                "id": request_id,
                "error": {
                    "code": -32601,
                    "message": f"Method not found: {method}"
                }
            })

Pagination Support

For servers with many resources, MCP supports pagination using cursors. This prevents overwhelming clients with huge lists.

json
// Request with cursor
{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "resources/list",
    "params": {
        "cursor": "eyJvZmZzZXQiOiAxMH0="
    }
}

// Response with next cursor
{
    "jsonrpc": "2.0",
    "id": 2,
    "result": {
        "resources": [
            // ... resources 11-20 ...
        ],
        "nextCursor": "eyJvZmZzZXQiOiAyMH0="
    }
}

Implementing Pagination

python
"""
pagination.py - Resources with pagination support
"""

import base64
import json

PAGE_SIZE = 10


def encode_cursor(offset: int) -> str:
    """Encode offset as a cursor string."""
    return base64.b64encode(
        json.dumps({"offset": offset}).encode()
    ).decode()


def decode_cursor(cursor: str) -> int:
    """Decode cursor string to offset."""
    try:
        data = json.loads(base64.b64decode(cursor))
        return data.get("offset", 0)
    except Exception:
        return 0


def handle_resources_list(params: dict) -> dict:
    """Handle resources/list with pagination."""
    # Get cursor from params
    cursor = params.get("cursor")
    offset = decode_cursor(cursor) if cursor else 0

    # Get page of resources
    page = RESOURCES[offset:offset + PAGE_SIZE]

    # Build response
    result = {"resources": page}

    # Add next cursor if there are more resources
    if offset + PAGE_SIZE < len(RESOURCES):
        result["nextCursor"] = encode_cursor(offset + PAGE_SIZE)

    return result

Dynamic Resources

Resources don't have to be static. You can generate them dynamically based on the current state of your application:

python
"""
dynamic_resources.py - Dynamically generated resources
"""

import os
from pathlib import Path


def get_file_resources(directory: str) -> list[dict]:
    """Generate resources from files in a directory."""
    resources = []
    base_path = Path(directory)

    for file_path in base_path.rglob("*"):
        if file_path.is_file():
            # Determine MIME type from extension
            mime_types = {
                ".py": "text/x-python",
                ".js": "text/javascript",
                ".json": "application/json",
                ".md": "text/markdown",
                ".txt": "text/plain",
            }
            ext = file_path.suffix.lower()
            mime_type = mime_types.get(ext, "application/octet-stream")

            resources.append({
                "uri": f"file://{file_path.absolute()}",
                "name": file_path.name,
                "description": f"File in {file_path.parent.name}/",
                "mimeType": mime_type
            })

    return resources


def handle_resources_list(params: dict) -> dict:
    """Handle resources/list with dynamic file discovery."""
    project_dir = os.environ.get("PROJECT_DIR", ".")
    resources = get_file_resources(project_dir)
    return {"resources": resources}
Performance Tip

If generating resources is expensive (e.g., scanning large directories or querying databases), consider caching the results and refreshing periodically or when notified of changes.

Testing resources/list

Test your implementation with curl:

bash
curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "resources/list",
    "params": {}
  }'

Expected response:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resources": [
      {
        "uri": "file:///project/config.json",
        "name": "config.json",
        "description": "Application configuration",
        "mimeType": "application/json"
      },
      {
        "uri": "file:///project/README.md",
        "name": "README.md",
        "description": "Project documentation",
        "mimeType": "text/markdown"
      }
    ]
  }
}