Skip to content

Commit 12c0b0d

Browse files
authored
Merge branch 'staging' into dvirdukhan/mcp-t12-auto-init
2 parents 2757d75 + 347dd46 commit 12c0b0d

15 files changed

Lines changed: 1095 additions & 103 deletions

File tree

.env.template

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
FALKORDB_HOST=localhost
33
FALKORDB_PORT=6379
44

5+
# Database backend. Use "lite" to run against an embedded FalkorDBLite
6+
# instance instead of an external FalkorDB host/port.
7+
CODE_GRAPH_DB_BACKEND=falkordb
8+
FALKORDB_LITE_PATH=~/.cache/code-graph/falkordblite.rdb
9+
# Optional: expose FalkorDBLite on localhost for host/port-only integrations
10+
# such as GraphRAG chat. Structural CodeGraph/MCP tools do not need this.
11+
# FALKORDB_LITE_HOST=127.0.0.1
12+
# FALKORDB_LITE_PORT=6379
13+
514
# Optional FalkorDB authentication
615
FALKORDB_USERNAME=
716
FALKORDB_PASSWORD=

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ code-graph/
5656
- Python `>=3.12,<3.14`
5757
- Node.js 20+
5858
- [`uv`](https://docs.astral.sh/uv/)
59-
- A FalkorDB instance (local or cloud)
59+
- A FalkorDB instance (local/cloud) or the optional FalkorDBLite backend
6060

6161
### 1. Start FalkorDB
6262

@@ -68,6 +68,16 @@ code-graph/
6868
docker run -p 6379:6379 -it --rm falkordb/falkordb
6969
```
7070

71+
**Option C:** Use embedded FalkorDBLite:
72+
73+
```bash
74+
uv sync --extra light
75+
export CODE_GRAPH_DB_BACKEND=lite
76+
export FALKORDB_LITE_PATH=~/.cache/code-graph/falkordblite.rdb
77+
```
78+
79+
FalkorDBLite runs a local embedded server over a private Unix socket by default. Set `FALKORDB_LITE_PORT` only when a host/port-only integration, such as GraphRAG chat, must connect to the embedded database.
80+
7181
### 2. Configure environment variables
7282

7383
Copy the template and adjust it for your setup:
@@ -78,10 +88,14 @@ cp .env.template .env
7888

7989
| Variable | Description | Required | Default |
8090
|----------|-------------|----------|---------|
91+
| `CODE_GRAPH_DB_BACKEND` | Database backend: `falkordb` or `lite` | No | `falkordb` |
8192
| `FALKORDB_HOST` | FalkorDB hostname | No | `localhost` |
8293
| `FALKORDB_PORT` | FalkorDB port | No | `6379` |
8394
| `FALKORDB_USERNAME` | Optional FalkorDB username | No | empty |
8495
| `FALKORDB_PASSWORD` | Optional FalkorDB password | No | empty |
96+
| `FALKORDB_LITE_PATH` | FalkorDBLite database file path | No | `~/.cache/code-graph/falkordblite.rdb` |
97+
| `FALKORDB_LITE_HOST` | Host used when exposing FalkorDBLite over TCP | No | `127.0.0.1` |
98+
| `FALKORDB_LITE_PORT` | Optional TCP port for FalkorDBLite host/port clients | No | empty |
8599
| `SECRET_TOKEN` | Token checked by protected endpoints | No | empty |
86100
| `CODE_GRAPH_PUBLIC` | Set `1` to skip auth on read-only endpoints | No | `0` |
87101
| `ALLOWED_ANALYSIS_DIR` | Root path allowed for `/api/analyze_folder` | No | repository root |

api/cli.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ def _check_connection(host: str, port: int) -> bool:
6262
def ensure_db() -> None:
6363
"""Ensure FalkorDB is running, auto-starting a Docker container if needed."""
6464

65+
from .db import create_falkordb, is_lite_backend
66+
67+
if is_lite_backend():
68+
try:
69+
db = create_falkordb()
70+
db.connection.ping()
71+
except Exception as e:
72+
_json_error(f"Failed to initialize FalkorDBLite: {e}")
73+
_stderr("FalkorDBLite embedded backend is ready")
74+
_json_out({"status": "ok", "backend": "lite"})
75+
return
76+
6577
host = os.getenv("FALKORDB_HOST", "localhost")
6678
try:
6779
port = int(os.getenv("FALKORDB_PORT", "6379"))

api/db.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""Database backend selection for CodeGraph.
2+
3+
The default backend is a regular FalkorDB server addressed by host/port.
4+
Set ``CODE_GRAPH_DB_BACKEND=lite`` to use FalkorDBLite's embedded server.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import os
10+
from pathlib import Path
11+
from typing import Any
12+
13+
14+
LITE_BACKENDS = {"lite", "falkordblite"}
15+
DEFAULT_LITE_PATH = "~/.cache/code-graph/falkordblite.rdb"
16+
17+
18+
def is_lite_backend() -> bool:
19+
"""Return whether CodeGraph should use the embedded FalkorDBLite backend."""
20+
backend = os.getenv("CODE_GRAPH_DB_BACKEND", "falkordb").strip().lower()
21+
return backend in LITE_BACKENDS
22+
23+
24+
def _lite_db_path() -> str:
25+
path = Path(os.getenv("FALKORDB_LITE_PATH", DEFAULT_LITE_PATH)).expanduser()
26+
path.parent.mkdir(parents=True, exist_ok=True)
27+
return str(path)
28+
29+
30+
def _lite_serverconfig() -> dict[str, str]:
31+
"""Return FalkorDBLite server config from env.
32+
33+
FalkorDBLite defaults to a private Unix socket. Supplying
34+
``FALKORDB_LITE_PORT`` additionally exposes a local TCP port, which is
35+
useful for libraries that only accept host/port connection settings.
36+
"""
37+
port = os.getenv("FALKORDB_LITE_PORT")
38+
if not port:
39+
return {}
40+
return {
41+
"bind": os.getenv("FALKORDB_LITE_HOST", "127.0.0.1"),
42+
"port": port,
43+
}
44+
45+
46+
def create_falkordb() -> Any:
47+
"""Create a sync FalkorDB client for the configured backend."""
48+
if is_lite_backend():
49+
try:
50+
from redislite.falkordb_client import FalkorDB as LiteFalkorDB
51+
except ImportError as e:
52+
raise RuntimeError(
53+
"CODE_GRAPH_DB_BACKEND=lite requires the optional "
54+
"`falkordblite` dependency. Install with "
55+
"`uv sync --extra light` or `pip install 'falkordb-code-graph[light]'`."
56+
) from e
57+
58+
return LiteFalkorDB(_lite_db_path(), serverconfig=_lite_serverconfig())
59+
60+
from falkordb import FalkorDB
61+
62+
return FalkorDB(
63+
host=os.getenv("FALKORDB_HOST", "localhost"),
64+
port=os.getenv("FALKORDB_PORT", 6379),
65+
username=os.getenv("FALKORDB_USERNAME", None),
66+
password=os.getenv("FALKORDB_PASSWORD", None),
67+
)
68+
69+
70+
def create_async_falkordb() -> Any:
71+
"""Create an async FalkorDB client for the configured backend."""
72+
if is_lite_backend():
73+
try:
74+
from redislite.async_falkordb_client import AsyncFalkorDB as LiteAsyncFalkorDB
75+
except ImportError as e:
76+
raise RuntimeError(
77+
"CODE_GRAPH_DB_BACKEND=lite requires the optional "
78+
"`falkordblite` dependency. Install with "
79+
"`uv sync --extra light` or `pip install 'falkordb-code-graph[light]'`."
80+
) from e
81+
82+
client = LiteAsyncFalkorDB(_lite_db_path(), serverconfig=_lite_serverconfig())
83+
if not hasattr(client, "aclose"):
84+
client.aclose = client.close
85+
return client
86+
87+
from falkordb.asyncio import FalkorDB as AsyncFalkorDB
88+
89+
return AsyncFalkorDB(
90+
host=os.getenv("FALKORDB_HOST", "localhost"),
91+
port=int(os.getenv("FALKORDB_PORT", 6379)),
92+
username=os.getenv("FALKORDB_USERNAME", None),
93+
password=os.getenv("FALKORDB_PASSWORD", None),
94+
)
95+
96+
97+
def create_redis_connection() -> Any:
98+
"""Create a sync Redis-compatible connection for metadata operations."""
99+
if is_lite_backend():
100+
return create_falkordb().connection
101+
102+
import redis
103+
104+
return redis.Redis(
105+
host=os.getenv("FALKORDB_HOST", "localhost"),
106+
port=int(os.getenv("FALKORDB_PORT", "6379")),
107+
username=os.getenv("FALKORDB_USERNAME"),
108+
password=os.getenv("FALKORDB_PASSWORD"),
109+
decode_responses=True,
110+
)
111+
112+
113+
def create_async_redis_connection() -> Any:
114+
"""Create an async Redis-compatible connection for metadata operations."""
115+
if is_lite_backend():
116+
return create_async_falkordb().connection
117+
118+
import redis.asyncio as aioredis
119+
120+
return aioredis.Redis(
121+
host=os.getenv("FALKORDB_HOST", "localhost"),
122+
port=int(os.getenv("FALKORDB_PORT", "6379")),
123+
username=os.getenv("FALKORDB_USERNAME"),
124+
password=os.getenv("FALKORDB_PASSWORD"),
125+
decode_responses=True,
126+
)
127+
128+
129+
def graphrag_connection_kwargs() -> dict[str, Any]:
130+
"""Return host/port kwargs for GraphRAG SDK constructors.
131+
132+
GraphRAG SDK accepts host/port only. FalkorDBLite's private Unix socket is
133+
therefore usable for structural tools but not for GraphRAG unless a local
134+
TCP port is explicitly enabled with ``FALKORDB_LITE_PORT``.
135+
"""
136+
if is_lite_backend():
137+
port = os.getenv("FALKORDB_LITE_PORT")
138+
if not port:
139+
raise RuntimeError(
140+
"GraphRAG requires host/port access. When using "
141+
"CODE_GRAPH_DB_BACKEND=lite, set FALKORDB_LITE_PORT to expose "
142+
"the embedded FalkorDBLite instance on localhost."
143+
)
144+
create_falkordb()
145+
return {
146+
"host": os.getenv("FALKORDB_LITE_HOST", "127.0.0.1"),
147+
"port": int(port),
148+
"username": None,
149+
"password": None,
150+
}
151+
152+
return {
153+
"host": os.getenv("FALKORDB_HOST", "localhost"),
154+
"port": int(os.getenv("FALKORDB_PORT", 6379)),
155+
"username": os.getenv("FALKORDB_USERNAME", None),
156+
"password": os.getenv("FALKORDB_PASSWORD", None),
157+
}

api/git_utils/git_graph.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import os
21
import logging
3-
from falkordb import FalkorDB, Node
4-
from falkordb.asyncio import FalkorDB as AsyncFalkorDB
2+
from falkordb import Node
53
from typing import List, Optional
64

5+
from api.db import create_async_falkordb, create_falkordb
76
from pygit2 import Commit
87

98
# Configure logging
@@ -19,10 +18,7 @@ class GitGraph():
1918

2019
def __init__(self, name: str):
2120

22-
self.db = FalkorDB(host=os.getenv('FALKORDB_HOST', 'localhost'),
23-
port=os.getenv('FALKORDB_PORT', 6379),
24-
username=os.getenv('FALKORDB_USERNAME', None),
25-
password=os.getenv('FALKORDB_PASSWORD', None))
21+
self.db = create_falkordb()
2622

2723
self.g = self.db.select_graph(name)
2824

@@ -182,12 +178,7 @@ class AsyncGitGraph:
182178
"""Async read-only git graph for endpoint use."""
183179

184180
def __init__(self, name: str):
185-
self.db = AsyncFalkorDB(
186-
host=os.getenv('FALKORDB_HOST', 'localhost'),
187-
port=int(os.getenv('FALKORDB_PORT', 6379)),
188-
username=os.getenv('FALKORDB_USERNAME', None),
189-
password=os.getenv('FALKORDB_PASSWORD', None),
190-
)
181+
self.db = create_async_falkordb()
191182
self.g = self.db.select_graph(name)
192183

193184
def _commit_from_node(self, node: Node) -> dict:
@@ -205,4 +196,3 @@ async def list_commits(self) -> List[dict]:
205196

206197
async def close(self) -> None:
207198
await self.db.aclose()
208-

api/graph.py

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import os
21
import re
32
import time
4-
from .entities import *
53
from typing import Optional
6-
from falkordb import FalkorDB, Path, Node, QueryResult
7-
from falkordb.asyncio import FalkorDB as AsyncFalkorDB
4+
from falkordb import Path, Node, QueryResult
5+
from .db import create_async_falkordb, create_falkordb
6+
from .entities import File, encode_edge, encode_node
87

98
# Configure the logger
109
import logging
@@ -62,10 +61,7 @@ def parse_graph_name(graph_name: str) -> Optional[tuple[str, str]]:
6261

6362

6463
def graph_exists(name: str):
65-
db = FalkorDB(host=os.getenv('FALKORDB_HOST', 'localhost'),
66-
port=os.getenv('FALKORDB_PORT', 6379),
67-
username=os.getenv('FALKORDB_USERNAME', None),
68-
password=os.getenv('FALKORDB_PASSWORD', None))
64+
db = create_falkordb()
6965

7066
return name in db.list_graphs()
7167

@@ -86,10 +82,7 @@ def get_repos() -> list[dict]:
8682
single graph until the migration is run.
8783
"""
8884

89-
db = FalkorDB(host=os.getenv('FALKORDB_HOST', 'localhost'),
90-
port=os.getenv('FALKORDB_PORT', 6379),
91-
username=os.getenv('FALKORDB_USERNAME', None),
92-
password=os.getenv('FALKORDB_PASSWORD', None))
85+
db = create_falkordb()
9386

9487
repos = []
9588
for g in db.list_graphs():
@@ -140,10 +133,7 @@ def __init__(self, name: str, branch: Optional[str] = None) -> None:
140133
self.branch = branch or DEFAULT_BRANCH
141134
self.name = compose_graph_name(self.project, self.branch)
142135

143-
self.db = FalkorDB(host=os.getenv('FALKORDB_HOST', 'localhost'),
144-
port=os.getenv('FALKORDB_PORT', 6379),
145-
username=os.getenv('FALKORDB_USERNAME', None),
146-
password=os.getenv('FALKORDB_PASSWORD', None))
136+
self.db = create_falkordb()
147137
self.g = self.db.select_graph(self.name)
148138

149139
# Initialize the backlog as disabled by default
@@ -180,10 +170,7 @@ def from_raw_name(cls, raw_name: str) -> "Graph":
180170
obj.branch = DEFAULT_BRANCH
181171
else:
182172
obj.project, obj.branch = parsed
183-
obj.db = FalkorDB(host=os.getenv('FALKORDB_HOST', 'localhost'),
184-
port=os.getenv('FALKORDB_PORT', 6379),
185-
username=os.getenv('FALKORDB_USERNAME', None),
186-
password=os.getenv('FALKORDB_PASSWORD', None))
173+
obj.db = create_falkordb()
187174
obj.g = obj.db.select_graph(raw_name)
188175
obj.backlog = None
189176
return obj
@@ -297,7 +284,7 @@ def _query(self, q: str, params: Optional[dict] = None) -> QueryResult:
297284

298285
return result_set
299286

300-
def get_sub_graph(self, l: int) -> dict:
287+
def get_sub_graph(self, limit: int) -> dict:
301288

302289
q = """MATCH (src)
303290
OPTIONAL MATCH (src)-[e]->(dest)
@@ -306,7 +293,7 @@ def get_sub_graph(self, l: int) -> dict:
306293

307294
sub_graph = {'nodes': [], 'edges': [] }
308295

309-
result_set = self._query(q, {'limit': l}).result_set
296+
result_set = self._query(q, {'limit': limit}).result_set
310297
for row in result_set:
311298
src = row[0]
312299
e = row[1]
@@ -592,7 +579,7 @@ def set_file_coverage(self, path: str, name: str, ext: str, coverage: float) ->
592579

593580
params = {'path': path, 'name': name, 'ext': ext, 'coverage': coverage}
594581

595-
res = self._query(q, params)
582+
self._query(q, params)
596583

597584
def connect_entities(self, relation: str, src_id: int, dest_id: int, properties: dict = {}) -> None:
598585
"""
@@ -789,14 +776,9 @@ def unreachable_entities(self, lbl: Optional[str], rel: Optional[str]) -> list[d
789776
# Async helpers and read-only async graph wrapper
790777
# ---------------------------------------------------------------------------
791778

792-
def _async_db() -> AsyncFalkorDB:
779+
def _async_db():
793780
"""Create an async FalkorDB connection using environment config."""
794-
return AsyncFalkorDB(
795-
host=os.getenv('FALKORDB_HOST', 'localhost'),
796-
port=int(os.getenv('FALKORDB_PORT', 6379)),
797-
username=os.getenv('FALKORDB_USERNAME', None),
798-
password=os.getenv('FALKORDB_PASSWORD', None),
799-
)
781+
return create_async_falkordb()
800782

801783

802784
async def async_graph_exists(name: str) -> bool:
@@ -952,4 +934,3 @@ async def stats(self) -> dict:
952934

953935
async def close(self) -> None:
954936
await self.db.aclose()
955-

0 commit comments

Comments
 (0)