Skip to main content

claude-graph

PyPI version PyPI downloads Python Platform License Network calls Tests CI

claude-graph is a local MCP server that builds a structural knowledge graph of your codebase for Claude Code. It answers questions like "what calls this function?", "what breaks if I change this file?", and "is this covered by tests?" — without sending any code to the cloud.

It parses your repo with Tree-sitter, stores a call graph (functions, classes, calls, imports, test-coverage links) in a local SQLite file, and exposes it to Claude Code over the Model Context Protocol (MCP) — so Claude can answer structural questions without reading your entire repository into context.

claude-graph interactive visualization

Why this exists, and what it deliberately doesn't do

Built for a corporate setting with one hard requirement: everything happens locally, with zero network calls, ever.

  • No cloud or local embeddings/semantic search. Claude Code itself is already the LLM in the loop — it reads the candidates this tool returns (keyword search + graph neighbors) and does the semantic reasoning itself. No vectors, no model downloads, no API calls.
  • No hooks. Nothing runs automatically when you edit a file. You (or Claude Code) call build_or_update_graph explicitly.
  • No multi-platform support. This only configures Claude Code. It won't touch Cursor, Windsurf, Zed, or anything else.
  • No home-directory writes. Everything this tool writes lives inside the repository you run it in (.claude-graph/, .mcp.json, .claude/skills/).
  • No telemetry, no daemon, no multi-repo registry.

See tests/test_no_network.py for the automated proof: it runs a full build + query + impact + search + viz + MCP server startup cycle with outbound sockets disabled and asserts nothing tries to connect anywhere.

Token savings benchmark

Real numbers — benchmarked by running claude-graph build on a shallow clone of Django main.

Build time:  17 seconds
Files:       3,040
Nodes:       45,096  (functions + classes + modules)
Edges:       938,763  (calls + imports + tests_for)

Without claude-graph, Claude Code answers structural questions by reading entire source files into context. With claude-graph, it issues a single MCP tool call and receives only the relevant nodes and edges.

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 entire Django source naively: 1,475,429 tokens — 7.4× Claude's 200k context window. Claude cannot reason about Django's structure at all without a tool like this.

claude-graph makes it possible in a single tool call. The graph is built once (claude-graph build) and updated incrementally (claude-graph update) — queries are instant SQLite lookups, not re-parses.

Contents

Requirements

  • macOS, Linux, or Windows
  • Python 3.11+
  • git

Install

pip install claude-graph

For file-watching support (claude-graph watch):

pip install "claude-graph[watch]"

GitHub Packages doesn't support pip-installable Python packages directly (only npm, Docker, Maven, Gradle, NuGet, and RubyGems are native registry types there), so if you'd rather install straight from this repo without going through PyPI, each release also ships the wheel as a downloadable asset:

pip install https://github.com/mohansagark/claude-graph/releases/download/v0.1.2/claude_graph-0.1.2-py3-none-any.whl

Or from source, for local development or to track main:

git clone https://github.com/mohansagark/claude-graph.git
cd claude-graph
pip install -e .

Docker

A container image is published to GitHub Container Registry on every release — this is a real GitHub Package, unlike the two options above:

docker pull ghcr.io/mohansagark/claude-graph:latest
docker run --rm -v "$PWD:/repo" ghcr.io/mohansagark/claude-graph build

Every command mounts your repo at /repo and takes the same arguments as the native CLI, e.g. docker run --rm -v "$PWD:/repo" ghcr.io/mohansagark/claude-graph viz --symbol foo. Note this is more friction than pip install for day-to-day use — in particular, wiring claude-graph serve up as Claude Code's MCP server via Docker means .mcp.json's command becomes a docker run -v ... invocation instead of a bare binary, which is why claude-graph install (below) generates the native-binary form by default. Docker is mainly useful when you don't want a Python environment on the host at all.

Then, inside the project you want a graph for:

cd /path/to/your/project
claude-graph install   # writes .mcp.json and .claude/skills/ in that repo
claude-graph build      # parses the repo and writes .claude-graph/graph.db

Restart Claude Code (or run /mcp to confirm claude-graph is connected) and ask it something structural, e.g. "what calls parse_file in this repo?"

Build first. Calling any of the MCP query tools (query_graph_tool, get_impact_radius_tool, search_nodes_tool) before a graph has ever been built will not error — it silently returns empty results, and as a side effect creates an empty .claude-graph/graph.db file. Run claude-graph build (or let Claude Code call build_or_update_graph first) before expecting real answers.

CLI

Command What it does
claude-graph build Full parse of every git-tracked file
claude-graph update Re-parses only changed files since the last build
claude-graph status Prints 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 Render an interactive HTML graph view and open it in the browser (--symbol NAME or --impact FILE... to scope it, -o PATH to change output, --max-nodes N to cap node count, --no-open to skip browser)
claude-graph watch Watch for file changes and auto-run incremental updates (requires pip install claude-graph[watch])
claude-graph doctor Health check: grammars, FTS5, git, MCP wiring, graph existence
claude-graph query PATTERN TARGET Run a structural query (callers_of, callees_of, imports_of, tests_for, file_summary)
claude-graph search QUERY Keyword search over function/class names

Every command accepts --repo PATH to target a repo other than the current directory. status, install, viz, doctor, query, and search accept --json for machine-readable output.

MCP tools

Tool Purpose
build_or_update_graph Full build if no graph exists, incremental update otherwise
get_graph_stats Node/edge/file counts, languages detected
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 self-contained local HTML file

Graph visualization

claude-graph viz (or the render_graph_tool MCP tool) writes a single self-contained HTML file to .claude-graph/graph.html — open it directly in a browser via file://, no server involved. It embeds a vendored copy of D3 (ISC license) directly into the file, so it works fully offline, same as everything else in this tool. The screenshot above is real output — a small demo app rendered with claude-graph viz, no scoping.

claude-graph viz                              # the whole graph (capped at 500 nodes by default)
claude-graph viz --symbol NAME                # a function/class's direct
                                               # callers, callees, and its
                                               # file's imports
claude-graph viz --impact FILE [FILE...]      # the impact radius of those
                  [--depth N]                  # changed files, laid out
                                               # visually
claude-graph viz --max-nodes 200              # override the cap
claude-graph viz --max-nodes 0                # no cap (may be slow on large repos)
claude-graph viz -o custom/path.html          # change the output path

Click a node to highlight its direct neighborhood and see its file/line in a side panel; drag to reposition; scroll to zoom; type in the search box to find a node by name. Use the filter panel (bottom-left) to toggle node kinds (function/class/module) and edge kinds (calls/imports/tests_for) live. Nodes are grouped into translucent file-cluster hulls — toggle them off in the filter panel if they add noise. For very large repos use --max-nodes N to cap the view at the N highest-degree nodes.

Supported languages

31 languages built in. Powered by 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
Python .py
JavaScript .js .jsx .mjs .cjs
TypeScript .ts
TSX .tsx
Go .go
Rust .rs
Java .java
C# .cs
Ruby .rb
C .c .h
C++ .cpp .cc .cxx .hpp .hh
Swift .swift
Kotlin .kt .kts
Scala .scala .sc
PHP .php .phtml
Dart .dart
Elixir .ex .exs
Bash / Zsh .sh .bash .zsh
Lua .lua
Zig .zig
Dockerfile Dockerfile .dockerfile
HCL / Terraform .tf .hcl
Nix .nix
CMake CMakeLists.txt .cmake
Solidity .sol
CUDA .cu .cuh
R .r .R
Julia .jl
Gleam .gleam
Cairo .cairo

Add more by dropping a .claude-graph/languages.toml into your repo — see claude_graph/default_languages.toml for the schema (extensions, tree-sitter grammar name, and the node types that count as a function/class/import/call for that grammar). No code change needed.

How calls are resolved

  • A calls edge's source (caller) is always a function-kind node — only functions/methods make calls in this graph.
  • A calls edge's target prefers a function match; if no function with that name exists, it falls back to a class match (a call to a class name is treated as an instantiation, e.g. Foo()).
  • One calls edge is recorded per call site. If the same function calls bar() twice, you get two edges — this is intentional, not a bug, so call counts reflect actual call-site frequency.

Search behavior

  • Search runs over SQLite FTS5 when available. Your query is split into tokens and each token is wrapped as a quoted phrase before being handed to FTS5 (e.g. foo-bar baz becomes "foo-bar" "baz") — FTS5's own query operators (AND, OR, NEAR, prefix *, column filters, etc.) are not supported by design; special characters are treated as literal text, not syntax.
  • If the local SQLite build lacks FTS5, search falls back to a LIKE query, with %, _, and \ escaped so wildcard-like characters in your query are matched literally rather than interpreted as SQL wildcards.
  • An empty or whitespace-only query returns [] immediately in both modes.

Known limitations

  • Bare-name, coarse node model. Nodes are keyed by (file, kind, name), not by fully-qualified path — so two same-kind, same-named symbols in the same file (e.g. two methods named the same thing on two different classes in that file) collapse into a single graph node, and cross-file call resolution is a global name-heuristic: a call to save() is matched against every function named save() in the graph, not just the one actually in scope. Two files with a same-named function can therefore produce over-broad callers_of/callees_of results (or, in the same-file collision case, an under-broad merged one). This is a deliberate precision/recall trade-off for a tool whose answers are read by an LLM that can disambiguate from context — better to flag too much than miss a real caller. See the docstrings in claude_graph/query.py for where this shows up in each query function.
  • tests_for linking is naming-convention only (test_foo.py / foo_test.py / foo.spec.ts / foo.test.ts matched against foo.py / foo.ts). Tests that don't follow one of these conventions aren't linked.
  • Import resolution is best-effort path matching, not real module resolution — it won't follow tsconfig.json path aliases or Python namespace packages.
  • Incremental update only re-links edges for files whose content changed. If you move a symbol to another file, calls into it from files you didn't touch keep pointing at the old resolution until the next full claude-graph build.
  • claude-graph viz's whole-graph view defaults to a cap of 500 nodes (highest-degree first). Override with --max-nodes N or remove the cap entirely with --max-nodes 0. On very large codebases use --symbol or --impact for a focused view instead. It can also pick up vendored/minified third-party files checked into the repo (e.g. a bundled .js library) as noisy, densely-connected nodes — exclude them via .claude-graph/languages.toml if that happens.

For teammates installing this themselves

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)

Publishing to PyPI is automated via .github/workflows/publish.yml using PyPI Trusted Publishing (OIDC — no API token stored in this repo). Bump the version in pyproject.toml, then cut a GitHub Release; publishing the release triggers the workflow, which builds the sdist/wheel and uploads them to PyPI.

License

MIT

Metadata

Release files for claude-graph 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for claude-graph 0.2.3
File Size Uploaded Provenance Actions
claude_graph-0.2.3.tar.gz 242.8 kB With provenance Details for claude_graph-0.2.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for claude-graph 0.2.3
File Interpreter ABI Platform Actions
claude_graph-0.2.3-py3-none-any.whl Python 3 none any Details for claude_graph-0.2.3-py3-none-any.whl

Total release size: 376.9 kB

Release files / claude_graph-0.2.3.tar.gz

Download URL claude_graph-0.2.3.tar.gz
Size 242.8 kB
Tags Source
SHA-256 checksum
How to use checksums
315c24a36e5ef5fb7e891d36effa333f8268c332166107e5d9654de49e4e1e9c
BLAKE2b-256 checksum
How to use checksums
7ad3e30923ed7e80e87ce2e39c2f1bb5f31723d49ef8a20a5323469e845bb2ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / claude_graph-0.2.3-py3-none-any.whl

Download URL claude_graph-0.2.3-py3-none-any.whl
Size 134.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60a6d9556e2db7810045ac5ae96cde266146657cb40189fcc197c83cbb5c34e7
BLAKE2b-256 checksum
How to use checksums
a690d6df8cd301107e1b10d45d442036a9b31b1e4e1be6a7566a81629d317b6c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Security for claude-graph 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for claude_graph-0.2.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page