Skip to content

Commit befa86c

Browse files
DvirDukhanCopilot
andcommitted
MCP-T13 + T14: cgraph init-agent + Docker mode switch
T13 — agent guidance bundle: - Add `api/mcp/templates/` shipped as package data (claude_mcp_section.md, cursorrules.template) with the canonical 8-tool MCP guidance. - Add `cgraph init-agent [--force]` Typer command that drops CLAUDE.md and .cursorrules into CWD. - Update AGENTS.md with the MCP tool table and env-var reference. T14 — packaging / image dual-mode: - `start.sh` dispatches on `CGRAPH_MODE` env var (web|mcp). Default (web) preserves the existing FastAPI behaviour; mcp execs cgraph-mcp. - `docker-compose.yml` gains an opt-in `code-graph-mcp` service under the `mcp` profile for stdio attach. - README quickstart section for both `claude mcp add-json` registration and Docker compose profile usage. Tests: 4 new (CliRunner against tmp_path); MCP suite green at 61 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d6bfbde commit befa86c

9 files changed

Lines changed: 299 additions & 5 deletions

File tree

AGENTS.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,26 @@ cgraph info [--repo <name>] # Repo stats + metadata
154154
```
155155

156156
`--repo` defaults to the current directory name. Claude Code skill in `skills/code-graph/`.
157+
158+
## MCP server (for agents)
159+
160+
`cgraph-mcp` exposes the code graph over MCP stdio. Eight tools:
161+
`index_repo`, `search_code`, `get_callers`, `get_callees`,
162+
`get_dependencies`, `impact_analysis`, `find_path`, `ask`.
163+
164+
Drop the canonical agent guidance into any repo:
165+
166+
```bash
167+
cgraph init-agent # writes CLAUDE.md + .cursorrules
168+
cgraph init-agent --force # overwrite existing files
169+
```
170+
171+
See `api/mcp/templates/claude_mcp_section.md` for the full tool table
172+
and rules of thumb (start with `search_code`; prefer structural tools
173+
over `ask`; run `impact_analysis` before refactoring).
174+
175+
Environment:
176+
177+
- `CODE_GRAPH_AUTO_INDEX=true` — auto-index CWD on MCP startup.
178+
- `CGRAPH_MODE=mcp` — run `cgraph-mcp` instead of the FastAPI web
179+
server when using the Docker image.

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,48 @@ npx skills add FalkorDB/code-graph
222222

223223
Then ask Claude things like *"what functions call analyze_sources?"* or *"find the dependency chain between parse_config and send_request"* — it will handle the indexing and querying automatically.
224224

225+
### MCP server (`cgraph-mcp`)
226+
227+
For agents that speak the [Model Context Protocol](https://modelcontextprotocol.io)
228+
(Claude Code, Cursor, Cline, …), code-graph ships a stdio MCP server
229+
that exposes the knowledge graph as 8 first-class tools: `index_repo`,
230+
`search_code`, `get_callers`, `get_callees`, `get_dependencies`,
231+
`impact_analysis`, `find_path`, and `ask` (NL→Cypher via GraphRAG).
232+
233+
Quickstart — Claude Code:
234+
235+
```bash
236+
# 1. Install (in any venv with the cgraph package on PATH)
237+
pip install code-graph # or: uv pip install code-graph
238+
239+
# 2. Register with Claude Code
240+
claude mcp add-json code-graph '{
241+
"command": "cgraph-mcp",
242+
"env": {
243+
"FALKORDB_HOST": "localhost",
244+
"FALKORDB_PORT": "6379",
245+
"CODE_GRAPH_AUTO_INDEX": "true"
246+
}
247+
}'
248+
249+
# 3. Drop agent guidance into your repo
250+
cd /path/to/your/repo
251+
cgraph init-agent # writes CLAUDE.md and .cursorrules
252+
```
253+
254+
Quickstart — Docker Compose:
255+
256+
```bash
257+
docker compose up -d falkordb # start the DB
258+
docker compose --profile mcp run --rm -i code-graph-mcp # attach via stdio
259+
```
260+
261+
The MCP server auto-bootstraps FalkorDB if it's missing on localhost
262+
(via `cgraph ensure-db`). When `CODE_GRAPH_AUTO_INDEX=true` is set,
263+
the current working directory is indexed automatically on start.
264+
265+
**Transport:** Phase 1 is stdio only. HTTP/SSE is deferred.
266+
225267
## Running with Docker
226268

227269
### Using Docker Compose
@@ -232,18 +274,34 @@ docker compose up --build
232274

233275
This starts FalkorDB and the CodeGraph app together. The checked-in compose file sets `CODE_GRAPH_PUBLIC=1` for the app service.
234276

277+
To run the **MCP stdio server** instead of the web app from the same
278+
image, set `CGRAPH_MODE=mcp` and use the `mcp` profile:
279+
280+
```bash
281+
docker compose --profile mcp run --rm -i code-graph-mcp
282+
```
283+
235284
### Using Docker directly
236285

237286
```bash
238287
docker build -t code-graph .
239288

289+
# Web mode (default)
240290
docker run -p 5000:5000 \
241291
-e FALKORDB_HOST=host.docker.internal \
242292
-e FALKORDB_PORT=6379 \
243293
-e MODEL_NAME=gemini/gemini-flash-lite-latest \
244294
-e GEMINI_API_KEY=<YOUR_GEMINI_API_KEY> \
245295
-e SECRET_TOKEN=<YOUR_SECRET_TOKEN> \
246296
code-graph
297+
298+
# MCP stdio mode (same image)
299+
docker run --rm -i \
300+
-e CGRAPH_MODE=mcp \
301+
-e FALKORDB_HOST=host.docker.internal \
302+
-e FALKORDB_PORT=6379 \
303+
-e MODEL_NAME=gemini/gemini-flash-lite-latest \
304+
code-graph
247305
```
248306

249307
## Creating a Code Graph

api/cli.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,5 +413,47 @@ def info(
413413
_json_out({"repo": name, "branch": branch, **stats, "metadata": metadata})
414414

415415

416+
# ── init-agent ─────────────────────────────────────────────────────────
417+
418+
419+
_TEMPLATES_DIR = Path(__file__).parent / "mcp" / "templates"
420+
421+
422+
@app.command("init-agent")
423+
def init_agent(
424+
force: bool = typer.Option(
425+
False, "--force", "-f", help="Overwrite existing CLAUDE.md / .cursorrules."
426+
),
427+
) -> None:
428+
"""Drop AI-agent guidance files (CLAUDE.md, .cursorrules) into CWD.
429+
430+
Copies the canonical code-graph MCP guidance bundled with this
431+
package so any repo can announce the tools to Cursor and Claude
432+
Code with one command.
433+
"""
434+
targets = {
435+
"CLAUDE.md": _TEMPLATES_DIR / "claude_mcp_section.md",
436+
".cursorrules": _TEMPLATES_DIR / "cursorrules.template",
437+
}
438+
439+
cwd = Path.cwd()
440+
if not force:
441+
existing = [name for name in targets if (cwd / name).exists()]
442+
if existing:
443+
_json_error(
444+
f"Refusing to overwrite existing files: {', '.join(existing)}. "
445+
"Re-run with --force to clobber."
446+
)
447+
448+
written: List[str] = []
449+
for name, template in targets.items():
450+
dest = cwd / name
451+
dest.write_text(template.read_text(encoding="utf-8"), encoding="utf-8")
452+
written.append(str(dest))
453+
_stderr(f"Wrote {dest}")
454+
455+
_json_out({"status": "ok", "written": written, "force": force})
456+
457+
416458
if __name__ == "__main__":
417459
app()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# code-graph MCP server — agent guidance
2+
3+
This repo is indexed into a FalkorDB **code knowledge graph** exposed
4+
to you over MCP as `code-graph`. Use it instead of grepping when you
5+
need to understand how symbols connect.
6+
7+
## When to call each tool
8+
9+
| Tool | Call this when… | Example |
10+
|---|---|---|
11+
| `index_repo(path)` | **First** thing in a new repo; or after large changes outside your edits. | `index_repo(path=".")` |
12+
| `search_code(prefix, project)` | You know part of a symbol name and need its id. | `search_code(prefix="processPay", project="myrepo")` |
13+
| `get_callers(symbol_id, project)` | "Who calls this?" — refactoring a function, tracking down a regression. | `get_callers(symbol_id=42, project="myrepo")` |
14+
| `get_callees(symbol_id, project)` | "What does this call?" — understanding a function before editing it. | `get_callees(symbol_id=42, project="myrepo")` |
15+
| `get_dependencies(symbol_id, project)` | All edges out of a symbol (CALLS + IMPORTS + DEFINES). | `get_dependencies(symbol_id=42, project="myrepo")` |
16+
| `impact_analysis(symbol_id, project, direction, depth)` | **"What breaks if I change this?"** Transitive upstream callers. | `impact_analysis(symbol_id=42, project="myrepo", direction="IN", depth=3)` |
17+
| `find_path(source_id, dest_id, project)` | Show the call chain between two known symbols. | `find_path(source_id=10, dest_id=42, project="myrepo")` |
18+
| `ask(question, project)` | Open-ended natural-language question. **More expensive — use last.** | `ask(question="why does login fail when MFA is on?", project="myrepo")` |
19+
20+
## Rules of thumb
21+
22+
1. **Start with `search_code`** to turn names into ids. Most tools take a `symbol_id`.
23+
2. **Prefer structural tools over `ask`.** `get_callers` is one cheap Cypher
24+
hop; `ask` is two LLM round-trips. Use `ask` for fuzzy/conceptual
25+
questions, not for "who calls X".
26+
3. **`impact_analysis` before refactoring.** Even when you think you know
27+
the answer — the transitive closure often surprises you.
28+
4. **`branch` is optional** but pass it when working on a feature branch
29+
so you query the right per-branch index.
30+
31+
## Environment
32+
33+
- `CODE_GRAPH_AUTO_INDEX=true` — auto-index CWD on first tool call (off by
34+
default; opt-in because indexing big repos takes minutes).
35+
- `FALKORDB_HOST` / `FALKORDB_PORT` — defaults to `localhost:6379`. If
36+
unreachable on localhost, the server runs `cgraph ensure-db` to
37+
spin up the official Docker image.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Cursor rules — code-graph MCP
2+
3+
This project is indexed into a FalkorDB code knowledge graph via the
4+
`code-graph` MCP server. Use it instead of grepping when you need to
5+
understand how symbols connect.
6+
7+
## Tool selection
8+
9+
- Symbol lookup by name: use `code-graph.search_code` (gives you the
10+
numeric id every other tool needs).
11+
- "Who calls X?": `code-graph.get_callers`.
12+
- "What does X call?": `code-graph.get_callees`.
13+
- All outgoing edges (CALLS + IMPORTS + DEFINES): `code-graph.get_dependencies`.
14+
- Refactoring impact ("what breaks if I change X"):
15+
`code-graph.impact_analysis` with `direction="IN"`.
16+
- Call chain between two specific symbols: `code-graph.find_path`.
17+
- Natural-language question over the graph (expensive — last resort):
18+
`code-graph.ask`.
19+
20+
## Rules
21+
22+
- Always `search_code` first to resolve names to ids.
23+
- Prefer structural tools (`get_callers`, `find_path`, `impact_analysis`)
24+
over `ask` for "who/what/where" questions.
25+
- Run `impact_analysis(direction="IN", depth=3)` before any non-trivial
26+
refactor.
27+
- Pass `branch` when on a feature branch.
28+
29+
## First run
30+
31+
If the repo isn't indexed yet, call `index_repo(path=".")` once. After
32+
that, navigate via the structural tools.

docker-compose.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,21 @@ services:
2222
- FALKORDB_PORT=6379
2323
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
2424
- SECRET_TOKEN=${SECRET_TOKEN:-}
25-
- CODE_GRAPH_PUBLIC=1
25+
- CODE_GRAPH_PUBLIC=1
26+
27+
# MCP stdio server — opt-in. Bring up with:
28+
# docker compose run --rm -i code-graph-mcp
29+
# then point Claude Code / Cursor at the running container's stdio.
30+
code-graph-mcp:
31+
build: .
32+
depends_on:
33+
- falkordb
34+
profiles: ["mcp"]
35+
stdin_open: true
36+
tty: false
37+
environment:
38+
- CGRAPH_MODE=mcp
39+
- FALKORDB_HOST=falkordb
40+
- FALKORDB_PORT=6379
41+
- MODEL_NAME=${MODEL_NAME:-gemini/gemini-flash-lite-latest}
42+
- CODE_GRAPH_AUTO_INDEX=${CODE_GRAPH_AUTO_INDEX:-}

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ build-backend = "setuptools.build_meta"
4545
[tool.setuptools.packages.find]
4646
where = ["."]
4747

48+
[tool.setuptools.package-data]
49+
"api.mcp" = ["templates/*"]
50+
4851
[dependency-groups]
4952
dev = [
5053
"pytest>=9.0.2",

start.sh

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set -e
44
# Set default values if not set
55
FALKORDB_HOST="${FALKORDB_HOST:-localhost}"
66
FALKORDB_PORT="${FALKORDB_PORT:-6379}"
7+
CGRAPH_MODE="${CGRAPH_MODE:-web}"
78

89
# Start FalkorDB Redis server in background only if using a local address (not an external instance)
910
if [ "${FALKORDB_HOST}" = "localhost" ] || [[ "${FALKORDB_HOST}" =~ ^127\.0\.0\.[0-9]+$ ]]; then
@@ -12,7 +13,7 @@ fi
1213

1314
# Wait until FalkorDB is ready
1415
FALKORDB_WAIT_TIMEOUT="${FALKORDB_WAIT_TIMEOUT:-30}"
15-
echo "Waiting for FalkorDB to start on $FALKORDB_HOST:$FALKORDB_PORT (timeout: ${FALKORDB_WAIT_TIMEOUT}s)..."
16+
echo "Waiting for FalkorDB to start on $FALKORDB_HOST:$FALKORDB_PORT (timeout: ${FALKORDB_WAIT_TIMEOUT}s)..." >&2
1617

1718
SECONDS=0
1819
while ! nc -z "$FALKORDB_HOST" "$FALKORDB_PORT"; do
@@ -23,7 +24,16 @@ while ! nc -z "$FALKORDB_HOST" "$FALKORDB_PORT"; do
2324
sleep 0.5
2425
done
2526

26-
echo "FalkorDB is up - launching server..."
27+
echo "FalkorDB is up launching ${CGRAPH_MODE} mode..." >&2
2728

28-
# Start the backend
29-
exec uvicorn api.index:app --host "${HOST:-0.0.0.0}" --port "${PORT:-5000}" ${APP_RELOAD:+--reload}
29+
# Dispatch on CGRAPH_MODE. Default ("web") preserves the original
30+
# behaviour. "mcp" runs the stdio MCP server so the same image can be
31+
# attached to Claude Code / Cursor without rebuilding.
32+
case "${CGRAPH_MODE}" in
33+
mcp)
34+
exec cgraph-mcp
35+
;;
36+
web|*)
37+
exec uvicorn api.index:app --host "${HOST:-0.0.0.0}" --port "${PORT:-5000}" ${APP_RELOAD:+--reload}
38+
;;
39+
esac

tests/mcp/test_init_agent.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Tests for `cgraph init-agent` (T13).
2+
3+
Verifies the CLI drops the MCP guidance templates into CWD and
4+
respects `--force`.
5+
"""
6+
7+
import os
8+
9+
import pytest
10+
from typer.testing import CliRunner
11+
12+
from api.cli import app
13+
14+
15+
@pytest.fixture
16+
def runner() -> CliRunner:
17+
return CliRunner()
18+
19+
20+
def _cd(monkeypatch: pytest.MonkeyPatch, path) -> None:
21+
monkeypatch.chdir(path)
22+
23+
24+
def test_init_agent_writes_both_files(runner, tmp_path, monkeypatch):
25+
_cd(monkeypatch, tmp_path)
26+
27+
result = runner.invoke(app, ["init-agent"])
28+
29+
assert result.exit_code == 0, result.output
30+
claude = tmp_path / "CLAUDE.md"
31+
cursor = tmp_path / ".cursorrules"
32+
assert claude.exists()
33+
assert cursor.exists()
34+
# Templates should mention at least one core MCP tool.
35+
body = claude.read_text() + cursor.read_text()
36+
assert "search_code" in body
37+
assert "code-graph" in body.lower()
38+
39+
40+
def test_init_agent_refuses_overwrite_without_force(runner, tmp_path, monkeypatch):
41+
_cd(monkeypatch, tmp_path)
42+
(tmp_path / "CLAUDE.md").write_text("preexisting\n")
43+
44+
result = runner.invoke(app, ["init-agent"])
45+
46+
assert result.exit_code != 0
47+
# Existing file untouched.
48+
assert (tmp_path / "CLAUDE.md").read_text() == "preexisting\n"
49+
# And we didn't write the other one either.
50+
assert not (tmp_path / ".cursorrules").exists()
51+
52+
53+
def test_init_agent_force_overwrites(runner, tmp_path, monkeypatch):
54+
_cd(monkeypatch, tmp_path)
55+
(tmp_path / "CLAUDE.md").write_text("preexisting\n")
56+
(tmp_path / ".cursorrules").write_text("old rules\n")
57+
58+
result = runner.invoke(app, ["init-agent", "--force"])
59+
60+
assert result.exit_code == 0, result.output
61+
assert "preexisting" not in (tmp_path / "CLAUDE.md").read_text()
62+
assert "old rules" not in (tmp_path / ".cursorrules").read_text()
63+
assert "search_code" in (tmp_path / "CLAUDE.md").read_text()
64+
65+
66+
def test_init_agent_templates_ship_with_package():
67+
"""Smoke check: the bundled template files exist on disk where the
68+
CLI expects them. Guards against pyproject.toml drift."""
69+
from api.cli import _TEMPLATES_DIR
70+
71+
assert (_TEMPLATES_DIR / "claude_mcp_section.md").is_file()
72+
assert (_TEMPLATES_DIR / "cursorrules.template").is_file()

0 commit comments

Comments
 (0)