# How the MCP Server Integrates with the Codebase Knowledge Graph in Code-Graph-RAG

> Learn how the MCP server integrates with the codebase knowledge graph in Code-Graph-RAG. Discover how to query and modify the Memgraph knowledge graph via STDIO or HTTP.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-16

---

**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`](https://github.com/vitali87/code-graph-rag/blob/main/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

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/mcp/tools.py) (lines 75-84) receives both the ingestor and cypher generator during instantiation:

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/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:

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/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

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/mcp/tools.py). Each demonstrates a distinct pattern of MCP-knowledge graph integration:

### List Projects: Direct Graph Query

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/graph_service.py).

### Index Repository: Graph Mutation via GraphUpdater

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/graph_updater.py)—the same component used in standalone ingestion workflows.

### Query Code Graph: Natural Language to Cypher

```python

# 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`](https://github.com/vitali87/code-graph-rag/blob/main/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

```python
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

```python
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

```json
{
  "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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/mcp/server.py) | Server bootstrap, lifecycle management, transport handlers (STDIO/HTTP) |
| [`codebase_rag/mcp/tools.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/mcp/tools.py) | `MCPToolsRegistry` definition, tool handler implementations |
| [`codebase_rag/constants/mcp.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/mcp.py) | Tool name enumerations, schema constants, defaults |
| [`codebase_rag/services/graph_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/graph_service.py) | `MemgraphIngestor` — the core graph database client |
| [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) | `CypherGenerator` — natural language to Cypher translation |
| [`codebase_rag/graph_updater.py`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/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.