Create a Movie Resource

In the previous lesson, you learned about resources and how they differ from tools.

In this challenge, you will add a resource to your Movies GraphRAG Server that exposes detailed movie information by its tmdbId property,

Challenge Goals

To complete this challenge, you will:

  1. Add a movie resource with a dynamic URI pattern

  2. Query Neo4j for comprehensive movie details

  3. Return structured data in a consistent format

  4. Include cast, genres, directors, and metadata

  5. Test the resource with the interactive client

Solution Available

If you get stuck, you can review the complete solution in the repository at solutions/8c-create-resource/main.py.

To see the solution in action, run:

bash
uv --directory solutions/8c-create-resource run main.py

Step 1: Understanding the Resource URI

Resources use URI patterns to identify what data to fetch.

For a movie resource, we’ll use the pattern: movie://{tmdb_id}

Examples:

  • movie://603 - The Matrix

  • movie://605 - The Matrix Reloaded

  • movie://13 - Forrest Gump

This allows clients to request specific movies by their TMDB ID.

Step 2: Create the Resource Function

Add this resource to your server/main.py file, after your existing tools:

python
@mcp.resource("movie://{tmdb_id}")
async def get_movie(tmdb_id: str, ctx: Context) -> str:
    """
    Get detailed information about a specific movie by TMDB ID.
    
    Args:
        tmdb_id: The TMDB ID of the movie (e.g., "603" for The Matrix)
    
    Returns:
        Formatted string with movie details including title, plot, cast, and genres
    """
    await ctx.info(f"Fetching movie details for TMDB ID: {tmdb_id}")
    
    context = ctx.request_context.lifespan_context
    
    try:
        records, _, _ = await context.driver.execute_query(
            """
            MATCH (m:Movie {tmdbId: $tmdb_id})
            RETURN m.title AS title,
               m.released AS released,
               m.tagline AS tagline,
               [ (m)-[:IN_GENRE]->(g:Genre) | g.name ] AS genres,
               [ (p)-[:ACTED_IN]->(m) | p.name ] AS actors,
               [ (d)-[:DIRECTED]->(m) | d.name ] AS directors
            """,
            tmdb_id=tmdb_id,
            database_=context.database
        )
        
        if not records:
            await ctx.warning(f"Movie with TMDB ID {tmdb_id} not found")
            return f"Movie with TMDB ID {tmdb_id} not found in database"
        
        movie = records[0].data()
        
        # Format the output
        output = []
        output.append(f"# {movie['title']} ({movie['released']})")
        output.append("")
        
        if movie['tagline']:
            output.append(f"_{movie['tagline']}_")
            output.append("")
        
        output.append(f"**Rating:** {movie['rating']}/10")
        output.append(f"**Runtime:** {movie['runtime']} minutes")
        output.append(f"**Genres:** {', '.join(movie['genres'])}")
        
        if movie['directors']:
            output.append(f"**Director(s):** {', '.join(movie['directors'])}")
        
        output.append("")
        output.append("## Plot")
        output.append(movie['plot'])
        
        if movie['cast']:
            output.append("")
            output.append("## Cast")
            for actor in movie['cast']:
                if actor['role']:
                    output.append(f"- {actor['name']} as {actor['role']}")
                else:
                    output.append(f"- {actor['name']}")
        
        result = "\n".join(output)
        
        await ctx.info(f"Successfully fetched details for '{movie['title']}'")
        
        return result
        
    except Exception as e:
        await ctx.error(f"Failed to fetch movie: {str(e)}")
        raise

Key points in this code:

  1. URI pattern: movie://{tmdb_id} - The tmdb_id parameter is extracted from the URI

  2. Comprehensive query: Fetches movie details, genres, cast, and directors in one query

  3. Structured data: Returns a consistent JSON-compatible dictionary

  4. Error handling: Handles missing movies gracefully

Step 3: Test with the Interactive Client

Start your server in one terminal:

bash
uv --directory server run main.py

In a separate terminal, run the interactive client from the project root:

bash
uv --directory client run main.py

The client will show available resource templates. Select the movie resource template and enter a TMDB ID when prompted.

Try these TMDB IDs:

  • 603 - The Matrix (1999)

  • 13 - Forrest Gump (1994)

  • 550 - Fight Club (1999)

  • 680 - Pulp Fiction (1994)

The client will prompt you:

tmdb_id (required)
  Type: string
  Enter value: 603

You should see structured output like:

json
{
    "title": "The Matrix",
    "released": 1999,
    "tagline": "Welcome to the Real World",
    "rating": 8.7,
    "runtime": 136,
    "genres": ["Action", "Science Fiction"],
    "directors": ["Lilly Wachowski", "Lana Wachowski"],
    "plot": "Set in the 22nd century, The Matrix tells the story of a computer hacker...",
    "cast": [
        {"name": "Keanu Reeves", "role": "Neo"},
        {"name": "Laurence Fishburne", "role": "Morpheus"},
        {"name": "Carrie-Anne Moss", "role": "Trinity"},
        {"name": "Hugo Weaving", "role": "Agent Smith"},
        {"name": "Gloria Foster", "role": "Oracle"}
    ]
}

This structured format makes it easy to use the data programmatically and is perfect for sampling in advanced features.

Summary

In this challenge, you successfully created a movie resource:

  • Dynamic URI pattern - movie://{tmdb_id} for flexible resource access

  • Comprehensive query - Fetched movie details, cast, genres, and directors

  • Structured data - Returned consistent, JSON-compatible output

  • Error handling - Gracefully handled missing movies

  • Resource design - Understood when to use resources vs tools

Your server now exposes data in two ways: tools for searches and resources for specific entities.

In the next lesson, you’ll learn about pagination for handling large datasets.

Chatbot

How can I help you today?