codegraph
A code graph for Claude Code, served over MCP.
It parses your repo into a local SQLite graph, so "who calls this", "what breaks if I change this" and "what did we work out about this last time" are one lookup instead of a search-and-read loop.
Python, TypeScript/JavaScript and Vue — including .vue <template>
bindings, which is where most of a Vue component's methods are actually
called from.
Install
pipx install "codemap-mcp[server,ts]"
codemap-index /path/to/repo
cd /path/to/repo
claude mcp add --scope local codegraph -- codemap-mcp --repo-root /path/to/repo
Start Claude Code in that repo and ask it something you'd normally grep
for — "who calls useApiClient?", "what breaks if I change the nav
composable?". Nothing is written inside your repo; every index lives
under ~/.codegraph/.
Re-run codemap-index after pulling. It's incremental — well under a
second for a no-op on a 4,000-file repo.
Extras, and why there are any
pip install codemap-mcp pulls zero third-party packages. The
indexer and query layer use nothing but the standard library (ast,
sqlite3), and CI asserts that rather than the README claiming it.
| extra | brings | needed for |
|---|---|---|
server |
mcp |
the MCP server — skip it if you only want the CLI |
ts |
tree-sitter grammars | TypeScript / JavaScript / Vue. Without it those files are skipped with a warning and Python still indexes in full |
The ts extra installs tree_sitter_languages on Python ≤3.12 and
tree-sitter-language-pack on 3.13+, because no single pack covers all
versions. The newer pack is measurably worse at TypeScript — on a real
4,113-file repo it fixes 3 files and breaks 8 — so on 3.13 expect slightly
more files skipped as parse errors. Measurements in
docs/DESIGN.md.
Installing from a checkout instead: ./install.sh creates
~/.codegraph/venv and installs everything into it.
What it does that grep doesn't
| grep / read | codegraph | |
|---|---|---|
| Every caller of a function | multi-file search, easy to miss one | neighbors(symbol, "in") |
| Blast radius before a change | read and infer by hand | impacted_by(symbol) — reverse-reachability |
| What imports this file | grep the path, hope the alias matches | neighbors("src/lib/db.ts", "in") |
A Vue method called only from <template> |
invisible — it's never called from script | recorded as a call from the template |
toRecordAlias<T>(...) |
a \btoRecordAlias( pattern misses the generic form |
resolved from the AST |
| "What did we learn here?" | nothing persists between sessions | search_memory |
That fifth row is not hypothetical. During review, a hand-written grep baseline missed 4 of 8 real call sites because TypeScript generics defeat a naive pattern — the graph had all 8.
Honest about what it doesn't know
This is the part that matters most, and the reason to trust the rest.
- It refuses to guess. When a name matches several definitions and
nothing binds it, the call is recorded as
ambiguous:<name>with the candidates, not resolved to a coin-flip. When the target is outside the repo it'sexternal:<name>. A wrong edge is worse than a missing one. - A miss tells you whether the index is stale. Every empty or unresolved result carries how many files have changed since the index was built, so "this doesn't exist" and "your index predates it" are different answers.
- Truncation is reported.
truncated: truewith the realtotal, never a quiet subset. - Type inference is deliberately narrow —
self/this, constructor assignments, field annotations, andnew X().method(). Not data-flow analysis.get_helper().assist()stays external, because resolving it would require guessing a return type.
How it works
flowchart LR
A[Your repo's<br/>source files] -->|"codemap-index<br/>(parse + resolve)"| B[(graph.db<br/>outside the repo)]
S[Past session<br/>transcripts] -. "opt-in: --sessions" .-> B
B --> M["codemap-mcp<br/>(MCP tools)"]
M <--> C[Claude Code session]
M -. "every tool call" .-> U[(usage.jsonl)]
classDef source fill:#e0e7ff,stroke:#4338ca,stroke-width:1px,color:#1e1b4b
classDef store fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#052e16
classDef server fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#451a03
classDef session fill:#fce7f3,stroke:#be185d,stroke-width:1px,color:#500724
class A,S source
class B,U store
class M server
class C session
Three layers over one local database:
| Layer | What it is | Tools |
|---|---|---|
| Code graph | modules, classes, functions, calls, imports, inheritance | search_code, neighbors, impacted_by, path_between |
| Session memory | durable notes a session records — why something is shaped a certain way, what broke last time | add_note, search_memory |
| Session history (opt-in) | full-text search over past Claude Code transcripts for this repo | search_sessions, list_sessions |
Indexing is two passes. Pass one parses each changed file and records what it can resolve locally, plus the references it can't resolve yet — a call, a base class, an import target, an attribute type. Pass two resolves those repo-wide in priority order: import binding first, then a same-file match, then the narrow type-inference pass. Only changed files are re-parsed, and a file that fails to parse is recorded by content hash so it isn't retried every run.
Tools
| Tool | Use for |
|---|---|
search(query) |
First stop — symbol plus any notes on it, together |
search_code(query) |
Structure only |
search_memory(query) |
Notes only |
neighbors(symbol, direction, limit) |
What it calls/imports, or what calls/imports it. Pass a file path to ask what imports that module |
impacted_by(symbol, max_depth, limit) |
Blast radius. Pass a file path for route handlers wired up by import rather than called |
path_between(a, b) |
How two symbols connect |
add_note(note, symbol, ...) |
Record a durable finding |
search_sessions(query, role, kind) |
Search past transcripts (opt-in) |
list_sessions(limit) |
What history is indexed |
codemap-usage <repo> reports whether any of this is actually being
used — calls by tool, found-anything rate, and what share of lookups went
through codegraph rather than grep.
What's been tested, and what hasn't
Behaviour varies a lot by codebase shape, so this is worth stating plainly.
Exercised hard: a 4,113-file Vue 3 + TypeScript frontend, a Node/TS API
service, a Next.js site, and the Python standard library (1,439 files).
Three adversarial review passes against real production code, each
cross-checking results against grep ground truth.
Barely exercised: Django, Flask, FastAPI, Rails-adjacent layouts, monorepos, anything over ~5,000 files. The class-body call fix was worth 692 edges on the Python stdlib and one edge on the Vue frontend — same change, two orders of magnitude apart. Expect differences on a shape not listed above.
Out of scope by design: anything spanning repositories. Cross-repo
imports appear as external: edges, so you can see which shared packages
a file depends on, but nothing about what flows through them. Data
contracts — DynamoDB shapes, job payloads, queue messages — have no edges
here at all. A clean impacted_by is not evidence that a schema change
is safe.
Optional hooks
Two shell helpers ship as commands, for ~/.claude/settings.json:
// SessionStart (startup|resume), async — keeps the index fresh
"codemap-reindex-if-opted-in \"$CLAUDE_PROJECT_DIR\""
// PostToolUse (Grep|Glob|Bash), async — measures codegraph against grep
"codemap-log-builtin-tool"
Both act only on repos that already have an index, so a session started anywhere else does nothing. The second records tool names only — never what was searched for.
Development
git clone https://github.com/manoranjan14/codemap-mcp && cd codemap-mcp
pip install -e ".[dev]"
pytest
288 tests across Python 3.10–3.13, with and without the optional tree-sitter dependency.
docs/DESIGN.md is the full engineering record: every decision with the measurement behind it, every bug found by review with its reproduction, and the things deliberately left undone. Names in it are anonymised; the numbers are real.
License
MIT
Metadata
Release files for codemap-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| codemap_mcp-0.1.0.tar.gz | 161.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| codemap_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 251.6 kB
Release files / codemap_mcp-0.1.0.tar.gz
| Download URL | codemap_mcp-0.1.0.tar.gz |
|---|---|
| Size | 161.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cb0717e91d36595d71ea2e51bda36da927fad3e2e1aa4820a8f52343f17a5f4a
|
|
BLAKE2b-256 checksum How to use checksums |
b6945b67a5a4e2d08ca59b8a83d20a628213638ff45e7e9c97095f56dcd9c1ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / codemap_mcp-0.1.0-py3-none-any.whl
| Download URL | codemap_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 90.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a61db2776370e52319443730175fd227217958e1228fb464ee8775328b817fa
|
|
BLAKE2b-256 checksum How to use checksums |
6981a7db2ae1a28840b8b530dbf7018f598402f1408b7ddf272d93946ce3baab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|