In the previous lessons, you learned about building tools with the @mcp.tool() decorator and using the Context object to access lifespan resources and provide logging.
In this challenge, you will build a tool that searches for movies in a specific genre, using nodes and relationships from the graph to provide relevant context to the LLM - the essence of GraphRAG.
Challenge Goals
To complete this challenge, you will:
-
Create a tool that accepts a genre parameter
-
Use the Context object to access the Neo4j driver
-
Query Neo4j for movies in the specified genre
-
Add logging to provide feedback during execution
-
Return structured output with movie information
-
Test the tool with the interactive client
Solution Available
If you get stuck, you can review the complete solution in the repository at solutions/6c-build-database-tool/main.py.
To see the solution in action, run:
uv --directory solutions/6c-build-database-tool run main.pyStep 1: Create the Tool Function
Add a new tool to your server/main.py file:
@mcp.tool()
async def get_movies_by_genre(genre: str, limit: int = 10, ctx: Context = None) -> list[dict]:
"""
Get movies by genre from the Neo4j database.
Args:
genre: The genre to search for (e.g., "Action", "Drama", "Comedy")
limit: Maximum number of movies to return (default: 10)
ctx: Context object (injected automatically)
Returns:
List of movies with title, tagline, and release year
"""
# Log the request
await ctx.info(f"Searching for {genre} movies (limit: {limit})...")
# Access the Neo4j driver from lifespan context
driver = ctx.request_context.lifespan_context.driver
# Log the query execution
await ctx.debug(f"Executing Cypher query for genre: {genre}")
try:
# Execute the query
records, summary, keys = await driver.execute_query(
"""
MATCH (m:Movie)-[:IN_GENRE]->(g:Genre {name: $genre})
RETURN m.title AS title,
m.imdbRating AS imdbRating,
m.released AS released
ORDER BY coalesce(m.imdbRating, 0) DESC
LIMIT $limit
""",
genre=genre,
limit=limit
)
# Convert records to list of dictionaries
movies = [record.data() for record in records]
# Log the result
await ctx.info(f"Found {len(movies)} {genre} movies")
if len(movies) == 0:
await ctx.warning(f"No movies found for genre: {genre}")
return movies
except Exception as e:
# Log any errors
await ctx.error(f"Query failed: {str(e)}")
raiseStructured Output:
Notice that the tool returns a list[dict].
FastMCP will automatically convert this to structured output that clients can parse and use programmatically.
The type hints help the LLM understand what the tool returns!
Step 2: Test with the Interactive Client
Start your server in one terminal:
uv --directory server run main.pyIn a separate terminal, run the interactive client from the project root:
uv --directory client run main.pySelect the get_movies_by_genre tool from the menu. The client will prompt you for parameters:
genre (required)
Type: string
Enter value: Action
limit (optional, default: 10)
Type: integer
Enter value: 5The tool will be called, and the results will be displayed in the client terminal:
✨ Result:
------------------------------------------------------------
{
"title": "Band of Brothers",
"imdbRating": 9.6,
"released": "2001-09-09"
}
{
"title": "Cowboy Bebop",
"imdbRating": 9.0,
"released": "2001-09-02"
}
{
"title": "Dark Knight, The",
"imdbRating": 9.0,
"released": "2008-07-18"
}
{
"title": "From the Earth to the Moon",
"imdbRating": 8.9,
"released": "1998-04-05"
}
{
"title": "Lord of the Rings: The Return of the King, The",
"imdbRating": 8.9,
"released": "2003-12-17"
}
------------------------------------------------------------Try experimenting with different genres, for example Action, Comedy, or Sci-Fi, and change the limit to see how it affects the results.
Experiment Further
Try enhancing your tool:
-
Add progress reporting for large queries
-
Include more movie properties (director, actors, rating)
-
Add error handling for connection issues
-
Create additional tools for other queries (by year, by rating, etc.)
Summary
In this challenge, you successfully built a Neo4j-backed tool using the Context object:
-
Context parameter - Added
ctx: Contextto access MCP capabilities -
Driver access - Retrieved the Neo4j driver from
ctx.request_context.lifespan_context -
Logging - Used
ctx.info(),ctx.debug(),ctx.warning(), andctx.error()for feedback -
Structured output - Returned typed data (
list[dict]) for client consumption -
Error handling - Caught and logged exceptions appropriately
Your tool now provides a great user experience with informative logging and structured data output.
In the next lesson, you’ll learn about resources and how to expose Neo4j data in a different way.