In the previous lesson, you learned about lifespan management and how it helps you properly manage resources like database connections.
In this challenge, you will build a Movies GraphRAG Server that uses lifespan management to establish and maintain a connection to a Neo4j database.
Challenge Goals
To complete this challenge, you will:
-
Install required Python packages
-
Configure environment variables for Neo4j connection details
-
Create a lifespan function that initializes a Neo4j driver on startup
-
Update your server to use the lifespan function
-
Verify the connection works
Solution Available
If you get stuck, you can review the complete solution in the repository at solutions/2c-add-neo4j-connection/main.py.
To see the solution in action, run:
uv --directory solutions/2c-add-neo4j-connection run main.pyStep 1: Install Required Packages
First, create a new directory for your MCP server, use the uv init command to initialize a new project:
mkdir server
cd server
uv initNext, use the uv add command to install the required packages to your project:
uv add mcp neo4j python-dotenvStep 2: Configure Environment Variables
Create a .env file in your server/ directory with your Neo4j connection details:
NEO4J_URI={instance-scheme}://{instance-ip}:{instance-boltPort}
NEO4J_USERNAME={instance-username}
NEO4J_PASSWORD={instance-password}
NEO4J_DATABASE={instance-database}The .env file is already in .gitignore so your credentials won’t be committed to version control.
Step 3: Implement Lifespan Management
Let’s build the lifespan management step by step. First, create a new file main.py in your server directory and add the required imports:
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from neo4j import AsyncGraphDatabase, AsyncDriver
from mcp.server.fastmcp import FastMCP
# Load environment variables from .env file
from dotenv import load_dotenv
load_dotenv()Next, create a context class to hold the Neo4j driver and database configuration:
@dataclass
class AppContext:
"""Application context with Neo4j driver."""
driver: AsyncDriver
database: strNow, implement the lifespan function that will manage the Neo4j driver:
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
"""Manage Neo4j driver lifecycle."""
# Read connection details from environment
uri = os.getenv("NEO4J_URI", "bolt://localhost:7687")
username = os.getenv("NEO4J_USERNAME", "neo4j")
password = os.getenv("NEO4J_PASSWORD", "password")
database = os.getenv("NEO4J_DATABASE", "neo4j")
# Initialize driver on startup
driver = AsyncGraphDatabase.driver(uri, auth=(username, password))
try:
# Yield context with driver
yield AppContext(driver=driver, database=database)
finally:
# Close driver on shutdown
await driver.close()Then define a new FastMCP server instance with the name Movies GraphRAG Server and pass the app_lifespan function as the lifespan parameter.
# Create server with lifespan
mcp = FastMCP("Movies GraphRAG Server", lifespan=app_lifespan)Step 4: Add a Graph Statistics Tool
Now that we have our lifespan management set up, let’s create a tool to return the number of nodes and relationships in the graph.
Create a graph_statistics tool function that uses ctx.request_context.lifespan_context to access the driver and database from the lifespan context, then executes a Cypher query to count nodes and relationships:
Using the Database Configuration
The database_ parameter is used to specify the database to execute the query on.
Any named arguments that do not end with an underscore will be passed as parameters to the Cypher query.
You can learn more about the Driverin the Using Neo4j with Python course.
Step 5: Add the Main Function
Finally, add the main function at the bottom of your main.py file to run the server:
if __name__ == "__main__":
mcp.run(transport="streamable-http")Step 6: Run the Server and Test with the Interactive Client
Now let’s test your server with the interactive Python client.
Start the Server
First, start your MCP server in a terminal:
uv --directory server run main.pyIf the server starts successfully, you should see output similar to:
INFO: Started server process [92061]
INFO: Waiting for application startup.
StreamableHTTP session manager started
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)Run the Interactive Client
In a separate terminal, start the MCP Inspector:
uv --directory client run main.pyThe client will display a menu of available tools. Select the graph_statistics tool and press Enter to execute it (this tool requires no parameters).
You should see the node and relationship counts from your database returned in the results:
✨ Result:
------------------------------------------------------------
{
"nodes": 28863,
"relationships": 332522
}
------------------------------------------------------------Troubleshooting
If you’re having issues:
-
Check that your
.envfile has the correct Neo4j credentials -
Verify the environment variables are being loaded (add print statements to debug)
-
Ensure the Neo4j database is running and accessible
-
Check the client output for error messages
Complete Solution
You can view the complete solution in the solutions/2c-add-neo4j-connection/main.py file.
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from neo4j import AsyncGraphDatabase, AsyncDriver
from mcp.server.fastmcp import FastMCP
# Load environment variables from .env file
from dotenv import load_dotenv
load_dotenv()
@dataclass
class AppContext:
"""Application context with Neo4j driver."""
driver: AsyncDriver
database: str
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
"""Manage Neo4j driver lifecycle."""
# Read connection details from environment
uri = os.getenv("NEO4J_URI", "bolt://localhost:7687")
username = os.getenv("NEO4J_USERNAME", "neo4j")
password = os.getenv("NEO4J_PASSWORD", "password")
database = os.getenv("NEO4J_DATABASE", "neo4j")
# Initialize driver on startup
driver = AsyncGraphDatabase.driver(uri, auth=(username, password))
try:
# Yield context with driver
yield AppContext(driver=driver, database=database)
finally:
# Close driver on shutdown
await driver.close()
# Create server with lifespan
mcp = FastMCP("Movies GraphRAG Server", lifespan=app_lifespan)
from mcp.server.fastmcp import Context
@mcp.tool()
async def graph_statistics(ctx: Context) -> dict[str, int]:
"""Count the number of nodes and relationships in the graph."""
# Access the driver from lifespan context
driver = ctx.request_context.lifespan_context.driver
database = ctx.request_context.lifespan_context.database
# Use the driver to query Neo4j with the correct database
records, summary, keys = await driver.execute_query(
r"RETURN COUNT {()} AS nodes, COUNT {()-[]-()} AS relationships",
database_=database
)
# Process the results
if records:
return dict(records[0])
return {"nodes": 0, "relationships": 0}
if __name__ == "__main__":
mcp.run(transport="streamable-http")Summary
In this challenge, you successfully added lifespan management to your MCP server:
-
Package installation - Added the
neo4jandpython-dotenvpackages to your project -
Environment variables - Stored credentials in
.envfile and loaded them withpython-dotenv -
Lifespan function - Created an async context manager to initialize and clean up the Neo4j driver
-
Context access - Used
ctx.request_context.lifespan_contextto access the driver in tools -
Connection testing - Verified the connection works with the
graph_statisticstool via the interactive client
Your server now properly manages the Neo4j driver lifecycle, creating it once on startup and reusing it across all tool calls.
In the next lesson, you’ll learn how to add more advanced database features to your MCP server.