# How the Cypher Query Engine Works in Codebase-Memory-MCP: OpenCypher Support and Implementation

> Explore the Cypher query engine in codebase-memory-mcp. Learn how it implements OpenCypher, compiles queries to SQLite SQL, and executes them safely against internal knowledge graphs.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: internals
- Published: 2026-07-23

---

**The Cypher query engine in DeusData/codebase-memory-mcp implements a lightweight OpenCypher subset that lexes, parses, and compiles graph queries into parameterized SQLite SQL, executing them against an internal knowledge graph with enforced safety ceilings.**

The `codebase-memory-mcp` repository provides a Model Context Protocol (MCP) server that stores codebases as knowledge graphs. Its **Cypher query engine** translates OpenCypher syntax into executable SQL, enabling semantic code search while preventing resource exhaustion through built-in limits and authorisation checks.

## Four-Stage Query Processing Pipeline

The engine processes every Cypher query through a tightly-coupled pipeline implemented primarily in [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c) and [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c).

### Lexing and Tokenization

The first stage uses a hand-written lexer to scan raw query strings into tokens. Located in [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c) (lines 45–120), the lexer handles string escaping, number literals, comments, and two-character operators such as `!=` and `..`. Key functions include `lex_push` for token emission, `lex_string_literal` for quote handling, and `lex_try_number` for numeric parsing.

### Recursive-Descent Parsing

Next, a recursive-descent parser constructs an abstract syntax tree (AST). The parser recognises node patterns, relationship patterns, optional matches, `WHERE` clauses, and return items while explicitly rejecting unsupported constructs. Core entry points include `parse_node`, `parse_rel`, and `parse_where`, with expression builders like `parse_or_expr` and `parse_and_expr` handling boolean logic (see [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c), lines 445–575). If the parser encounters unsupported clauses such as `CREATE`, `MERGE`, or `CALL`, it returns a clear error via `unsupported_clause_error` (lines 813–838) before reaching the SQL layer.

### AST-to-SQL Translation

During the planning stage, the AST walker generates **parameterised** SQLite queries. Each `MATCH` pattern becomes a series of joins on the `node` and `edge` tables. Labels and relationship-type alternations compile to `IN` predicates, while optional matches render as `LEFT OUTER JOIN`s. The planner also injects pagination (`LIMIT`, `SKIP`) and aggregates (`COUNT`, `SUM`). Variable-length hop ranges parsed by `parse_hop_range` (lines 998–1020) are translated into recursive or bounded join patterns depending on the specified depth.

### Execution with SQLite Safety Controls

The final stage executes the generated SQL in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c). The `cbm_query_graph` function (lines 350–420) runs the query through SQLite, while a custom authoriser installed in `cbm_db_open` (line 639) blocks disallowed statements such as `ATTACH` or `DROP`. Result rows are marshalled into JSON for the client, ensuring the underlying database cannot be abused by malformed Cypher patterns.

## Supported OpenCypher Features and Limitations

The engine supports a read-only subset of OpenCypher optimised for graph traversal and property filtering.

**Supported constructs include:**

- `MATCH` clauses with node and relationship patterns
- Variable-length paths using `*` and range operators (e.g., `[:CALL*1..3]`)
- `WHERE` clauses with boolean expressions and property comparisons
- `RETURN` with aggregates (`COUNT`, `SUM`) and pagination (`LIMIT`, `SKIP`)
- Label and relationship type filtering

**Explicitly unsupported constructs:**

The parser rejects write operations and procedural extensions. Any query containing `CREATE`, `MERGE`, `DELETE`, `SET`, `REMOVE`, or `CALL` triggers the `unsupported_clause_error` handler (lines 813–838), returning an immediate error to the CLI without touching the SQL layer.

## Safety Mechanisms for Open Queries

The engine treats "open" queries—those without explicit termination or with unbounded variable-length hops—through multiple protective layers.

### Variable-Length Hop Ceilings

When parsing hops like `[:CALL*]`, the `parse_hop_range` function (lines 998–1020) extracts minimum and maximum bounds. If the upper bound is omitted, the planner emits an unbounded join but enforces a **hard-coded safety ceiling of 100,000 rows** to prevent runaway traversals.

### Default LIMIT Injection

If a query omits the `LIMIT` clause, the planner automatically appends a default ceiling of **100,000 rows** to the generated SQL. This limit is documented in the CLI usage text ([`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c), lines 590–595) and enforced via `WHERE rowid < 100000` predicates in the final query.

### SQL Injection Prevention

All generated SQL passes through a custom SQLite authoriser registered in `cbm_db_open` (line 639). This authoriser rejects any attempt to execute `ATTACH`, `DROP`, or other schema-modifying statements, ensuring that even syntactically valid Cypher cannot be exploited to escape the intended query shape.

## Query Examples

Execute these via the `query_graph` tool handled by `handle_query_graph` in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (line 2912):

```bash

# Simple open query without LIMIT (automatically capped at 100k rows)

codebase-memory-mcp query_graph '{"project":"demo","query":"MATCH (n) RETURN n"}'

# Variable-length hop with unbounded depth (capped at 100k rows)

codebase-memory-mcp query_graph '{"project":"demo","query":"MATCH (a)-[:CALL*]->(b) RETURN a,b"}'

# Explicit LIMIT to override the default ceiling

codebase-memory-mcp query_graph '{"project":"demo","query":"MATCH (n) RETURN n LIMIT 50"}'

```

## Summary

- The **Cypher query engine** compiles OpenCypher into SQLite SQL through lexing, parsing, AST translation, and execution stages.
- The lexer and parser in [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c) handle identifiers, literals, patterns, and expressions while rejecting `CREATE`, `MERGE`, and `CALL`.
- The planner generates parameterised joins on `node` and `edge` tables, encoding labels as `IN` predicates and optional matches as `LEFT OUTER JOIN`s.
- **Safety ceilings** of 100,000 rows protect against unbounded variable-length hops (`*`) and missing `LIMIT` clauses.
- A custom SQLite authoriser in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) prevents SQL injection by blocking `ATTACH`, `DROP`, and other dangerous statements.

## Frequently Asked Questions

### What happens if I use CREATE or MERGE in a query?

The parser explicitly rejects write operations. When `CREATE`, `MERGE`, `DELETE`, or `SET` clauses are detected, the `unsupported_clause_error` function (lines 813–838 in [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c)) returns an error string immediately, preventing the query from reaching the SQL execution layer.

### How does the engine handle infinite-length path queries like `[:REL*]`?

The `parse_hop_range` function recognises the unbounded `*` operator and passes it to the planner, which generates a recursive join pattern. However, the execution layer enforces a hard limit of **100,000 rows** to prevent resource exhaustion, effectively capping the traversal depth regardless of the open-ended syntax.

### Can I perform SQL injection through the Cypher query interface?

No. Even if the Cypher parser accepts unusual syntax, all generated SQL executes through SQLite with a custom authoriser installed in `cbm_db_open` (line 639 of [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c)). This authoriser blocks `ATTACH`, `DROP`, `ALTER`, and other schema-modifying commands, ensuring the query remains confined to read-only operations on the knowledge graph tables.

### Where is the default row limit of 100,000 defined?

The default ceiling is hard-coded in the query planner and documented in the CLI help text located in [`src/cli/cli.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cli/cli.c) (lines 590–595). When no `LIMIT` clause is present, the planner injects `WHERE rowid < 100000` into the generated SQL to protect the server from runaway queries.