CodeGraph MCP
CodeGraph is a local MCP server that builds a function-level code graph for TypeScript and JavaScript repositories and exposes it as tools an AI assistant can query. It helps answer structural questions such as who calls a function, what a component renders, which middleware runs on a route, and which symbols are impacted by a change.
This repository is also structured to be installed and run as a Python package for MCP clients. It includes:
- a package entry point for MCP hosts to launch the server
- installable metadata for local or registry-based distribution
- a foundation for adding support for more languages over time
Install locally
python -m pip install -e .
Run as an MCP server
After installation, MCP clients can launch the server with:
codegraph-mcp
Everything runs locally. No source code leaves your machine.
Quick start
# 1. install (once)
E:\RedTokens\.venv\Scripts\python.exe -m pip install -r requirements.txt
cd extractors/typescript && npm install
Then register the server with your client (see Configuration) and, from a chat, point it at any repository you want to analyze:
use_repo("C:/path/to/your/repo")
That's it — the graph builds on first use, then keeps itself in sync. No manual indexing step.
Architecture
Your repo (.ts .tsx .js .jsx .mjs .cjs)
│
└─ ts-morph daemon (Node) ── TypeScript compiler API, stays warm
│ resolves calls semantically, incl. CommonJS require()
│
│ nodes: function arrow method objMethod route cron class hook value
│ edges: CALLS RENDERS INSTANTIATES EXTENDS IMPORTS EXPORTS MIDDLEWARE_OF
▼
KùzuDB (~/.codegraph/graphs/<repo-slug>/db) ← one graph PER REPO
│
│ + RELATED_TO edges, tags, notes ← written by the AI
│ + <db>_ai.json sidecar ← makes those survive rebuilds
│
┌───────┴────────────────────────────────┐
│ ONE process (codegraph/app.py) │
│ ├── MCP over stdio ──► Claude │
│ └── viz over HTTP ──► your browser │
└────────────────────────────────────────┘
Why one process
KùzuDB is a single-process embedded database. Two OS processes cannot open the same graph — even read_only=True fails against a live connection. But within one process, MCP tool calls and the viz's HTTP handlers share a single GraphStore whose every execute() is serialized through an RLock. So the AI can write annotations while you watch the visualization — the calls simply queue.
That's why server.py (MCP) and viz.py (HTTP) are launched together by app.py instead of as separate processes.
Two layers
| Layer | What it captures | Written by |
|---|---|---|
| Static | structural skeleton — what calls/renders/extends what | ts-morph, deterministic |
| AI evolution | semantics — "writes to this table", "authenticates this route" | AI, via annotate_symbol / add_relationship |
The static layer deliberately avoids ORM/framework-specific hardcoding (that would make it Mongoose-only, Express-only). The AI supplies meaning by reading code and persisting what it learns — and that knowledge survives code edits and restarts (see AI sidecar).
The 19 MCP tools
Setup
| Tool | Purpose |
|---|---|
use_repo(path, rebuild=False) |
Point at a project; builds on first use, else opens + syncs. Call this first. |
reindex() |
Force an immediate re-scan (rarely needed — reads auto-sync). |
Query structure
| Tool | Purpose |
|---|---|
search_symbol(query) |
Substring search → exact names. Entry point for every other tool. |
explain_symbol(symbol) |
Best first call — definitions, callers, callees, renders, class info, AI notes in one shot. |
get_callers / get_callees |
Direct (one hop). |
impact_of(symbol, depth) |
Transitive callers — "what breaks if I change this". |
trace_flow(symbol, depth) |
Transitive callees — execution path. |
get_render_tree(component) |
JSX composition: renders / rendered-by. |
get_class_hierarchy(class) |
extends / subclasses / instantiators. |
get_file_imports(file) |
File deps + reverse deps + exports. |
get_middleware_for(route) |
Middleware chain on a route. |
AI knowledge layer
| Tool | Purpose |
|---|---|
annotate_symbol(symbol, tags, note) |
Attach tags + a note. Tags merge. |
add_relationship(from, to, type, note) |
Record a semantic edge (WRITES_TO, AUTHENTICATES, …). |
get_relationships(symbol, type?) |
Recall AI edges, both directions. |
find_by_tag(tag) |
All symbols with a tag. |
Visualization
| Tool | Purpose |
|---|---|
start_viz(port=0) |
Start the 3D viz; returns the URL. 0 = first free port from 8000. |
stop_viz() |
Stop it, free the port. |
viz_status() |
Whether it's running, and where. |
Node kinds & edge types
| Node kind | Meaning |
|---|---|
function arrow method objMethod |
callables |
route |
HTTP route handler (carries httpMethod + httpPath) |
cron |
scheduled job |
class |
class declaration |
hook |
useXxx naming convention |
value |
PascalCase const from a call — e.g. const User = mongoose.model(...) |
| Edge | Meaning |
|---|---|
CALLS |
foo() / obj.foo() |
RENDERS |
<Foo/> inside another component's JSX |
INSTANTIATES |
new Foo() |
EXTENDS |
class A extends B |
IMPORTS / EXPORTS |
file-level |
MIDDLEWARE_OF |
middleware → route |
RELATED_TO |
AI-authored, kept in a separate table so discovered ≠ deterministic |
AI sidecar
Annotations live as columns on Symbol rows, and a rebuild (reset()) wipes the whole graph. To keep AI knowledge durable, every annotation is mirrored to a JSON sidecar and re-applied after each rebuild, matched by name + file (not symbol id — ids embed line numbers that shift on every edit).
- Written through on every
annotate_symbol/add_relationship - Replayed as the last step of every build
- Orphans (symbol deleted) are kept, so a note snaps back if the symbol returns
- Survives
use_repo(..., rebuild=True)
Visualization
An interactive 3D force graph, served by the same process that speaks MCP.
- Light/dark theme, colorful gradient background, glass panels
- 2D / 3D toggle, color by kind or file, optional node labels, focus mode
- Edge-type filters with live counts — isolate just
RENDERS, etc. - Gold nodes = AI-annotated; sidebar shows tags, notes, AI relationships
- ↻ Reset & Rescan — rebuild from the latest code, streamed live
- Detail panel: kind/async/route badges, direct connections by edge type, transitive impact & flow
Default http://127.0.0.1:8000. If that port is taken it automatically tries 8001–8009 — call viz_status() (or read the startup line on stderr) to see where it landed.
A busy port can never kill the MCP connection: uvicorn calls
sys.exit(3)on a failed bind, so ports are probed before uvicorn sees them andserve()is wrapped to swallowSystemExit.
Configuration
Two different clients read two different files — this trips people up.
| Client | Config file |
|---|---|
| Claude Code | C:\Users\<you>\.claude.json → top-level mcpServers |
| Claude Desktop | C:\Users\<you>\AppData\Roaming\Claude\claude_desktop_config.json |
{
"mcpServers": {
"codegraph": {
"command": "E:\\RedTokens\\.venv\\Scripts\\python.exe",
"args": ["-m", "codegraph.app"],
"env": { "PYTHONPATH": "E:\\RedTokens" }
}
}
}
PYTHONPATH is required — these clients ignore a cwd key, so it's the only way Python finds the package.
Optional env vars:
| Var | Effect |
|---|---|
CODEGRAPH_REPO |
Auto-open this repo at startup (skips needing use_repo) |
CODEGRAPH_VIZ_AUTOSTART=0 |
Don't start the viz at boot (use start_viz instead) |
If Claude Desktop and Claude Code both register it, each spawns its own server. That's now safe — the second gets its viz on 8001 and full MCP — but only one process can hold a given repo's graph (Kùzu). Register it in one client if you want a single shared graph.
Paths reference
Code
| Path | What |
|---|---|
E:\RedTokens\ |
project root |
codegraph\app.py |
entrypoint — MCP (stdio) + viz, one process |
codegraph\server.py |
the 19 MCP tools + server instructions |
codegraph\viz.py |
FastAPI viz app + VizController (start/stop/port fallback) |
codegraph\viz_page.html |
the entire 3D UI (single self-contained file) |
codegraph\workspace.py |
active repo, per-repo DB resolution, lazy auto-sync |
codegraph\graph_store.py |
KùzuDB wrapper, schema, queries, AI sidecar |
codegraph\indexer.py |
full build + incremental sync |
codegraph\callsites.py |
tree-sitter call sites (tier 2/3 fallback) |
codegraph\extractors\ |
tiered extractor protocol + tier-1 ts-morph client |
codegraph\AUTONOMY.md |
how to make the AI drive this by itself |
extractors\typescript\extractor.js |
the ts-morph daemon (Node) |
extractors\typescript\try.js |
manual daemon test: node try.js <repoPath> [symbol] |
.venv\ |
Python environment |
Data (nothing is stored inside your repos)
| Path | What |
|---|---|
~\.codegraph\graphs\<name>-<hash>\db |
that repo's KùzuDB graph |
~\.codegraph\graphs\<name>-<hash>\db_cache.json |
per-file content hashes for sync |
~\.codegraph\graphs\<name>-<hash>\db_ai.json |
AI sidecar — annotations + RELATED_TO |
~\.codegraph\state.json |
last-used repo |
The slug is basename-sha1(abspath)[:8], case-normalized — so two checkouts named backend never collide, and E:\Proj / E:\proj resolve to the same graph.
Runtime
| Viz URL | http://127.0.0.1:8000 (falls back to 8001–8009) |
| MCP transport | stdio (client-spawned) |
How syncing works
The graph is kept fresh automatically — you never reindex by hand.
- Every read tool calls
ensure_ready()first: hash every source file, compare to cache (debounced 3s). - Nothing changed → no-op, returns immediately.
- Files modified/created → incremental path: the daemon patches just those files and returns the recomputed edge set; only those files' node slices are swapped.
- Files deleted → full rebuild (the daemon has no remove-file path; a fresh glob drops them correctly).
- Either way, the AI sidecar is replayed last so annotations survive.
Note on performance: the incremental path is verified correct (byte-identical to a full rebuild) but is currently only marginally faster, because the daemon is spawned per sync call and recomputes all edges globally. Real speedup needs a persistent daemon across syncs + scoped edge extraction — see the known-limitations note below.
Running standalone (dev)
python -m codegraph.app # MCP over stdio + viz (what clients launch)
python -m codegraph.server # MCP only
python -m codegraph.viz # viz only
python -m codegraph.indexer <repo> [--sync] # build/sync a graph from the CLI
Don't run two of these against the same repo at once — Kùzu allows one process per graph.
Metadata
Release files for redtokens-codegraph-mcp 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| redtokens_codegraph_mcp-0.1.6.tar.gz | 2.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| redtokens_codegraph_mcp-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 5.5 MB
Release files / redtokens_codegraph_mcp-0.1.6.tar.gz
| Download URL | redtokens_codegraph_mcp-0.1.6.tar.gz |
|---|---|
| Size | 2.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
57d90d989be84b6d473546dee2a80553cc464ad279cd5a1708db7e95354ebcee
|
|
BLAKE2b-256 checksum How to use checksums |
63590d9d50191e273714ff0f1c0160de2c55bc1d3d0e4bd8a4dc6d31e3ffbcdc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.6
|
Release files / redtokens_codegraph_mcp-0.1.6-py3-none-any.whl
| Download URL | redtokens_codegraph_mcp-0.1.6-py3-none-any.whl |
|---|---|
| Size | 2.8 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c4b24bf149b30ab858696bdf9a0c62ab7e7dc818d8c76c200226ca1b064c1db
|
|
BLAKE2b-256 checksum How to use checksums |
62bab24eb8ff8265893971538ed236cc1b38097b0ac7de35f2a878e0a9f2a9b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.6
|