claude-graph
by mohansagark
README.md
<div align="center">
<img src="docs/images/logo.svg" alt="claude-graph" width="120" />
# claude-graph
**A local knowledge graph for Claude Code — structural code intelligence with zero network calls.**
[](https://pypi.org/project/claude-graph/)
[](https://pypi.org/project/claude-graph/)
[](https://github.com/mohansagark/claude-graph/actions/workflows/ci.yml)
[](https://pypi.org/project/claude-graph/)
[](#requirements)
[](LICENSE)
[](#zero-network-calls)
---
*Answer structural questions about your codebase in a single MCP tool call — without sending a single byte of code to the cloud.*
<!-- HERO GIF: record a short (~15s) screen capture of:
1. running `claude-graph build` in a terminal
2. the viz opening in the browser with nodes animating into place
3. clicking a node to highlight its neighborhood
Save as docs/images/demo.gif and replace this comment with:

-->
</div>
---
## Why claude-graph?
Claude Code answers structural questions ("what calls this?", "what breaks if I change this file?") by reading source files into context. On any non-trivial codebase that means:
- **Hundreds of files** that may not fit in the context window
- **Thousands of tokens** spent on code Claude won't actually use
- **No answer at all** for repos larger than the context window
claude-graph solves this by building a persistent **call graph** (functions → calls → functions, files → imports → files, tests → tests_for → source) stored in a local SQLite database. Claude issues a single MCP tool call and gets back only the relevant nodes and edges — typically **7× to 236× fewer tokens** than reading the source files directly.
**Everything happens on your machine. No embeddings, no cloud API, no telemetry — ever.**
---
## Real benchmark — Django (3,040 files)
> Built with `claude-graph build` on a shallow clone of [Django](https://github.com/django/django) `main`.
```
Build time: 17 seconds
Files: 3,040
Nodes: 45,096 (functions + classes + modules)
Edges: 938,763 (calls + imports + tests_for)
```
| Query | Tokens — naive (send matching files) | Tokens — claude-graph | Reduction |
|---|---|---|---|
| `callers_of HttpResponse` | 112,991 | 15,455 | **7× less** |
| `callees_of dispatch` | 92,539 | 3,143 | **29× less** |
| `callers_of authenticate` | 46,032 | 5,278 | **8× less** |
| `search permission` | 155,915 | 659 | **236× less** |
> **Dumping the full Django source naively = 1,475,429 tokens — 7.4× Claude's 200k context window.**
> Claude cannot answer structural questions about Django at all without a tool like this.
---
## Quick start
```bash
# 1. Install
pip install claude-graph
# 2. Wire it into your project (writes .mcp.json + .claude/skills/)
cd /path/to/your/project
claude-graph install
# 3. Build the graph
claude-graph build
```
Restart Claude Code (or run `/mcp` to confirm `claude-graph` is connected), then ask:
> *"What calls `save()` in this repo?"*
> *"What breaks if I change `auth/middleware.py`?"*
> *"Is `parse_file` covered by tests?"*
<!-- TERMINAL SCREENSHOT: capture the output of `claude-graph status` after a build on a real project.
Save as docs/images/status.png and replace this comment with:

-->
---
## Features
### 🔍 Structural queries
Ask Claude things it couldn't answer without reading the entire repo:
| Query pattern | What it returns |
|---|---|
| `callers_of NAME` | Every function that calls `NAME` |
| `callees_of NAME` | Every function that `NAME` calls |
| `imports_of NAME` | Every file that imports `NAME` |
| `tests_for FILE` | Test files linked to `FILE` |
| `file_summary FILE` | All symbols defined in `FILE` |
| `search KEYWORD` | Fuzzy search over all function/class names |
### 📡 Impact radius
Changed a file? Get the blast radius instantly:
```bash
claude-graph query callers_of parse_file
# or let Claude Code call get_impact_radius_tool automatically
```
### 🗺️ Interactive visualization
```bash
claude-graph viz # whole graph (capped at 500 nodes)
claude-graph viz --symbol HttpResponse # direct neighborhood of a symbol
claude-graph viz --impact db/models.py # visual blast radius of a file
claude-graph viz --max-nodes 0 # uncapped (may be slow on large repos)
```
<!-- VIZ SCREENSHOT: open the graph in a browser, click a node to show the panel,
take a screenshot. Save as docs/images/viz.png and replace this comment with:

-->
The viz renders a **self-contained HTML file** — no server, works fully offline. Features:
- Force-directed D3 layout with drag, zoom, and pan
- Click any node to highlight its direct neighborhood + see file/line
- Filter panel to toggle node kinds (function / class / module) and edge kinds (calls / imports / tests_for) live
- Translucent **file-cluster hulls** grouping nodes that share a file
- Search box to find any node by name
### 👁️ File watcher
```bash
pip install "claude-graph[watch]"
claude-graph watch # auto-rebuilds incrementally on every file change
```
### 🩺 Health check
```bash
claude-graph doctor
```
Verifies grammars, FTS5, git access, MCP wiring, and graph existence — with a pass/fail table and exit code.
---
## Zero network calls
Built for corporate and regulated environments with one hard requirement: **everything stays on your machine.**
- No embeddings, no vector index, no model downloads
- No cloud API calls — not even for tokenization
- No telemetry, no daemon, no background process
- No home-directory writes — everything lives inside your repo (`.claude-graph/`, `.mcp.json`, `.claude/skills/`)
- Nothing runs automatically — you or Claude Code call `build_or_update_graph` explicitly
See [`tests/test_no_network.py`](tests/test_no_network.py) for the automated proof: a full build + query + viz + MCP startup cycle with outbound sockets disabled.
---
## MCP tools (used by Claude Code automatically)
| Tool | What it does |
|---|---|
| `build_or_update_graph` | Full build if no graph exists, incremental update otherwise |
| `get_graph_stats` | Node / edge / file counts and detected languages |
| `query_graph_tool` | `callers_of` / `callees_of` / `imports_of` / `tests_for` / `file_summary` |
| `get_impact_radius_tool` | Blast radius of a set of changed files |
| `search_nodes_tool` | Keyword search over function/class names and signatures |
| `render_graph_tool` | Render the graph (or a scoped neighborhood) to a local HTML file |
---
## CLI reference
| Command | What it does |
|---|---|
| `claude-graph build` | Full parse of every git-tracked file |
| `claude-graph update` | Re-parses only files changed since the last build |
| `claude-graph status` | Node / edge / file counts (`--json` for machine-readable output) |
| `claude-graph install` | Writes `.mcp.json` and `.claude/skills/` for this repo |
| `claude-graph serve` | Starts the MCP server (stdio) — Claude Code launches this itself |
| `claude-graph viz` | Interactive HTML graph, opens in browser |
| `claude-graph watch` | Auto-rebuild on file change (requires `claude-graph[watch]`) |
| `claude-graph doctor` | Health check with pass/fail table |
| `claude-graph query PATTERN TARGET` | Run a structural query from the CLI |
| `claude-graph search QUERY` | Keyword search from the CLI |
Every command accepts `--repo PATH` to target a repo other than `$CWD`.
`status`, `install`, `viz`, `doctor`, `query`, and `search` accept `--json`.
---
## Supported languages
**31 built in.** Powered by [tree-sitter-language-pack](https://github.com/xberg-io/tree-sitter-language-pack) which bundles **306 grammars** — add any of them with a single `.claude-graph/languages.toml` entry, no code change needed.
| | Language | Extensions |
|---|---|---|
| <img src="https://cdn.simpleicons.org/python/3776AB" width="16"/> | Python | `.py` |
| <img src="https://cdn.simpleicons.org/javascript/F7DF1E" width="16"/> | JavaScript | `.js` `.jsx` `.mjs` `.cjs` |
| <img src="https://cdn.simpleicons.org/typescript/3178C6" width="16"/> | TypeScript | `.ts` |
| <img src="https://cdn.simpleicons.org/react/61DAFB" width="16"/> | TSX | `.tsx` |
| <img src="https://cdn.simpleicons.org/go/00ADD8" width="16"/> | Go | `.go` |
| <img src="https://cdn.simpleicons.org/rust/000000" width="16"/> | Rust | `.rs` |
| <img src="https://cdn.simpleicons.org/openjdk/ED8B00" width="16"/> | Java | `.java` |
| <img src="https://cdn.simpleicons.org/csharp/239120" width="16"/> | C# | `.cs` |
| <img src="https://cdn.simpleicons.org/ruby/CC342D" width="16"/> | Ruby | `.rb` |
| <img src="https://cdn.simpleicons.org/c/A8B9CC" width="16"/> | C | `.c` `.h` |
| <img src="https://cdn.simpleicons.org/cplusplus/00599C" width="16"/> | C++ | `.cpp` `.cc` `.cxx` `.hpp` `.hh` |
| <img src="https://cdn.simpleicons.org/swift/F05138" width="16"/> | Swift | `.swift` |
| <img src="https://cdn.simpleicons.org/kotlin/7F52FF" width="16"/> | Kotlin | `.kt` `.kts` |
| <img src="https://cdn.simpleicons.org/scala/DC322F" width="16"/> | Scala | `.scala` `.sc` |
| <img src="https://cdn.simpleicons.org/php/777BB4" width="16"/> | PHP | `.php` `.phtml` |
| <img src="https://cdn.simpleicons.org/dart/0175C2" width="16"/> | Dart | `.dart` |
| <img src="https://cdn.simpleicons.org/elixir/4B275F" width="16"/> | Elixir | `.ex` `.exs` |
| <img src="https://cdn.simpleicons.org/gnubash/4EAA25" width="16"/> | Bash / Zsh | `.sh` `.bash` `.zsh` |
| <img src="https://cdn.simpleicons.org/lua/2C2D72" width="16"/> | Lua | `.lua` |
| <img src="https://cdn.simpleicons.org/zig/F7A41D" width="16"/> | Zig | `.zig` |
| <img src="https://cdn.simpleicons.org/docker/2496ED" width="16"/> | Dockerfile | `Dockerfile` `.dockerfile` |
| <img src="https://cdn.simpleicons.org/terraform/844FBA" width="16"/> | HCL / Terraform | `.tf` `.hcl` |
| <img src="https://cdn.simpleicons.org/nixos/5277C3" width="16"/> | Nix | `.nix` |
| <img src="https://cdn.simpleicons.org/cmake/064F8C" width="16"/> | CMake | `CMakeLists.txt` `.cmake` |
| <img src="https://cdn.simpleicons.org/solidity/363636" width="16"/> | Solidity | `.sol` |
| <img src="https://cdn.simpleicons.org/nvidia/76B900" width="16"/> | CUDA | `.cu` `.cuh` |
| <img src="https://cdn.simpleicons.org/r/276DC3" width="16"/> | R | `.r` `.R` |
| <img src="https://cdn.simpleicons.org/julia/9558B2" width="16"/> | Julia | `.jl` |
| <img src="https://cdn.simpleicons.org/gleam/FFAFF3" width="16"/> | Gleam | `.gleam` |
| <img src="https://cdn.simpleicons.org/cairo/000000" width="16"/> | Cairo | `.cairo` |
**Adding a language:** drop a `.claude-graph/languages.toml` into your repo. See [`claude_graph/default_languages.toml`](claude_graph/default_languages.toml) for the schema (extensions, grammar name, node types for functions/classes/imports/calls).
---
## Install options
### pip (recommended)
```bash
pip install claude-graph
# With file-watching support
pip install "claude-graph[watch]"
```
### Docker
```bash
docker pull ghcr.io/mohansagark/claude-graph:latest
docker run --rm -v "$PWD:/repo" ghcr.io/mohansagark/claude-graph build
docker run --rm -v "$PWD:/repo" ghcr.io/mohansagark/claude-graph viz --symbol MyClass
```
### From source
```bash
git clone https://github.com/mohansagark/claude-graph.git
cd claude-graph
pip install -e .
```
### From a release asset (no PyPI)
Each [GitHub Release](https://github.com/mohansagark/claude-graph/releases) ships a wheel as a downloadable asset:
```bash
pip install https://github.com/mohansagark/claude-graph/releases/download/v0.2.3/claude_graph-0.2.3-py3-none-any.whl
```
---
## Requirements
- Python 3.11+
- git
- macOS, Linux, or Windows
---
## How call resolution works
- A `calls` edge's **source** is always a function-kind node — only functions/methods make calls.
- A `calls` edge's **target** prefers a function match; if none exists, falls back to a class match (instantiation, e.g. `Foo()`).
- One edge per call site — if `bar()` is called twice, you get two edges. Call frequency is preserved.
- Cross-file resolution is name-based: a call to `save()` matches every function named `save` in the graph. This is a deliberate precision/recall trade-off — better to surface too many candidates for Claude to disambiguate than to miss a real caller.
---
## Known limitations
- **Name-based call resolution** — cross-file calls match by name only, not by scope. Highly common names (`save`, `get`, `run`) produce over-broad results on large repos.
- **`tests_for` linking** — naming convention + imports only (`test_foo.py` ↔ `foo.py`, `foo.spec.ts` ↔ `foo.ts`). Tests that don't follow a convention aren't linked.
- **Import resolution** — best-effort path matching, not real module resolution. Won't follow `tsconfig.json` path aliases or Python namespace packages.
- **Incremental edge re-linking** — `claude-graph update` only re-links edges for changed files. Moving a symbol to another file keeps stale cross-file edges until the next full `claude-graph build`.
---
## Contributing
```bash
git clone https://github.com/mohansagark/claude-graph.git
cd claude-graph
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
```
---
## Releasing (maintainers)
Bump `version` in `pyproject.toml`, then cut a [GitHub Release](https://github.com/mohansagark/claude-graph/releases/new). Publishing the release triggers `.github/workflows/publish.yml` which uploads to PyPI via OIDC trusted publishing and pushes the Docker image to GHCR — no API tokens stored in this repo.
---
## License
[MIT](LICENSE) © [Mohansagar Killamsetty](https://github.com/mohansagark)
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues