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 MemgraphIngestor and CypherGenerator instances initialized in create_server().
  • The MCPToolsRegistry in codebase_rag/mcp/tools.py binds all graph operations—read, query, mutate—to MCP tool names, ensuring consistent state across requests.
  • STDIO and HTTP transports in server.py provide flexible deployment options for local editors and remote services.
  • Lifecycle management via _service_lifecycle guarantees 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →