How the MCP Server Integrates with the Codebase Knowledge Graph in Code-Graph-RAG
The MCP server in code-graph-rag exposes the Memgraph knowledge graph as a set of remote tools that MCP-compatible clients can query and modify through STDIO or HTTP endpoints.
The Model Context Protocol (MCP) transforms code-graph-rag from a local Python library into a serviceable interface. Any MCP-compatible client—such as Claude Code—can interact with your codebase's knowledge graph without direct database access. The integration centers on three tightly-coupled components: a MemgraphIngestor for graph operations, a CypherGenerator for natural-language-to-query translation, and an MCPToolsRegistry that mediates all client requests.
Core Components of the MCP Integration
Server Initialization and Graph Client Setup
The entry point create_server() in codebase_rag/mcp/server.py (lines 93-106) instantiates the two primary dependencies:
- MemgraphIngestor — the low-level client that executes reads, writes, and schema operations against the Memgraph database
- CypherGenerator — the LLM-driven translator that converts natural language questions into executable Cypher queries
# codebase_rag/mcp/server.py (lines 93-106)
def create_server() -> Server:
ingestor = MemgraphIngestor() # Graph connection
cypher_generator = CypherGenerator() # NL → Cypher translation
# ... tools registry initialization follows
These objects are constructed once at startup and shared across all subsequent tool invocations, ensuring state consistency throughout the MCP session.
Tool Registry Architecture
The MCPToolsRegistry class defined in codebase_rag/mcp/tools.py (lines 75-84) receives both the ingestor and cypher generator during instantiation:
# codebase_rag/mcp/tools.py (lines 75-84)
class MCPToolsRegistry:
def __init__(self, ingestor: BaseGraphIngestor, cypher_generator: CypherGenerator):
self._ingestor = ingestor
self._cypher_generator = cypher_generator
self._handlers: Dict[MCPTool, Callable[..., Awaitable[Any]]] = {}
self._schemas: Dict[MCPTool, Any] = {}
self._register_all() # Binds list_projects, index_repository, query_code_graph, etc.
Each registered tool accesses the same ingestor and cypher_generator instances. This design guarantees that every MCP request operates on the same up-to-date knowledge graph—there is no stale cache or connection pooling ambiguity.
Request Handling: STDIO and HTTP Entry Points
The MCP server exposes two transport modes in codebase_rag/mcp/server.py:
| Transport | Function | Lines | Use Case |
|---|---|---|---|
| STDIO | serve_stdio() |
188-200 | Claude Code, local CLI tools |
| HTTP | serve_http() |
58-104 | Remote services, custom integrations |
Both modes converge on the same dispatch logic:
# codebase_rag/mcp/server.py → call_tool()
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
handler, returns_json = tools.get_tool_handler(name) # Resolve from registry
result = await handler(**arguments) # Execute graph operation
return [TextContent(type="text", text=format_result(result, returns_json))]
The call_tool handler fetches the appropriate function from MCPToolsRegistry and invokes it with JSON arguments received from the MCP client.
Lifecycle Management and Resource Safety
The _service_lifecycle context manager in codebase_rag/mcp/server.py (lines 68-85) handles connection hygiene:
- On entry: Opens the Memgraph connection via
with ingestor: - On exit: Calls
close_qdrant_client()to release the vector-store connection
# codebase_rag/mcp/server.py (lines 68-85)
@contextlib.asynccontextmanager
async def _service_lifecycle(server: Server):
with ingestor: # Memgraph connection active for all tool calls
try:
yield
finally:
await close_qdrant_client() # Clean shutdown
This ensures that graph operations never execute against a closed connection and that vector embeddings remain synchronized with the graph state.
Graph-Aware Tool Implementations
The MCP server exposes five core operations through codebase_rag/mcp/tools.py. Each demonstrates a distinct pattern of MCP-knowledge graph integration:
List Projects: Direct Graph Query
# codebase_rag/mcp/tools.py (lines 92-99)
@register(MCPTool.LIST_PROJECTS, returns_json=True)
async def list_projects(ingestor: MemgraphIngestor) -> List[Dict]:
return await ingestor.list_projects() # Runs MATCH (p:Project) RETURN p.name
The tool delegates directly to ingestor.list_projects(), which issues optimized Cypher against codebase_rag/services/graph_service.py.
Index Repository: Graph Mutation via GraphUpdater
# codebase_rag/mcp/tools.py
@register(MCPTool.INDEX_REPOSITORY)
async def index_repository(repo_path: str, ingestor: MemgraphIngestor) -> str:
updater = GraphUpdater(ingestor=ingestor)
await updater.build_graph_for_repo(repo_path) # Parses and populates entities
return f"Indexed {repo_path}"
This triggers GraphUpdater from codebase_rag/graph_updater.py—the same component used in standalone ingestion workflows.
Query Code Graph: Natural Language to Cypher
# codebase_rag/mcp/tools.py (lines 51-62)
@register(MCPTool.QUERY_CODE_GRAPH, returns_json=True)
async def query_code_graph(
natural_language_query: str,
ingestor: MemgraphIngestor,
cypher_generator: CypherGenerator
) -> List[Dict]:
cypher = await cypher_generator.generate(natural_language_query) # LLM translation
return await ingestor.execute_query(cypher) # Direct graph execution
The CypherGenerator from codebase_rag/services/llm.py handles prompt engineering and response parsing, enabling clients to ask "Which functions call fetch_user?" without knowing Cypher syntax.
Running the MCP Server
STDIO Mode for Claude Code Integration
import asyncio
from codebase_rag.mcp.server import serve_stdio
asyncio.run(serve_stdio())
This is the standard configuration for Claude Code and other MCP-compatible editors that communicate over standard input/output.
HTTP Mode for Custom Services
import asyncio
from codebase_rag.mcp.server import serve_http
asyncio.run(serve_http(host="127.0.0.1", port=8030))
HTTP mode enables remote deployments and language-agnostic clients that speak JSON-RPC.
Sample MCP Client Request
{
"jsonrpc": "2.0",
"method": "callTool",
"params": {
"name": "query_code_graph",
"arguments": {
"natural_language_query": "Which functions call `fetch_user`?"
}
},
"id": 1
}
The server resolves this through the MCPToolsRegistry, executes the generated Cypher, and returns structured results as TextContent.
Key Source Files
| File | Responsibility |
|---|---|
codebase_rag/mcp/server.py |
Server bootstrap, lifecycle management, transport handlers (STDIO/HTTP) |
codebase_rag/mcp/tools.py |
MCPToolsRegistry definition, tool handler implementations |
codebase_rag/constants/mcp.py |
Tool name enumerations, schema constants, defaults |
codebase_rag/services/graph_service.py |
MemgraphIngestor — the core graph database client |
codebase_rag/services/llm.py |
CypherGenerator — natural language to Cypher translation |
codebase_rag/graph_updater.py |
GraphUpdater — repository parsing and graph population |
Summary
- The MCP server integrates with the code-graph-rAG knowledge graph through shared
MemgraphIngestorandCypherGeneratorinstances initialized increate_server(). - The
MCPToolsRegistryincodebase_rag/mcp/tools.pybinds all graph operations—read, query, mutate—to MCP tool names, ensuring consistent state across requests. - STDIO and HTTP transports in
server.pyprovide flexible deployment options for local editors and remote services. - Lifecycle management via
_service_lifecycleguarantees that Memgraph connections are active during tool execution and properly released on shutdown. - Natural-language queries are supported through
CypherGenerator, enabling non-technical MCP clients to explore code structure without learning Cypher.
Frequently Asked Questions
What MCP clients are compatible with code-graph-rag?
Any client implementing the Model Context Protocol specification can connect. The repository is explicitly tested with Claude Code (STDIO mode) and provides HTTP endpoints for custom integrations. The protocol is transport-agnostic, so Python, TypeScript, or Go clients can all interact with the same tool registry.
Does the MCP server maintain separate database connections per request?
No. The MemgraphIngestor is instantiated once during create_server() and shared across all tool calls through the MCPToolsRegistry. The _service_lifecycle context manager ensures this single connection remains open for the server's lifetime, preventing connection overhead and guaranteeing that all tools observe the same graph state.
How does natural language querying work through the MCP interface?
The query_code_graph tool receives natural-language input, passes it to CypherGenerator.generate() in codebase_rag/services/llm.py, and executes the resulting Cypher against Memgraph via ingestor.execute_query(). The LLM translation happens transparently to the MCP client, which only sees structured JSON responses.
What happens if the Memgraph database is unavailable during an MCP request?
The _service_lifecycle context manager (lines 68-85 in server.py) wraps tool execution. If the ingestor cannot establish a connection, the context manager raises an exception before any tool handler executes, surfacing a clear error to the MCP client without partial state corruption.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →