Metadata-Version: 2.4
Name: redtokens-codegraph-mcp
Version: 0.1.4
Summary: A local MCP server for building structural code graphs from repositories
Author: Mohit P.
License-Expression: MIT
Keywords: mcp,codegraph,code intelligence,analysis
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp
Requires-Dist: kuzu
Requires-Dist: watchdog
Requires-Dist: tiktoken
Requires-Dist: multilspy
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"

# 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

```bash
python -m pip install -e .
```

### Run as an MCP server

After installation, MCP clients can launch the server with:

```bash
codegraph-mcp
```

> Everything runs locally. No source code leaves your machine.

## Quick start

```bash
# 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](#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](#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 and `serve()` is wrapped to swallow `SystemExit`.

---

## 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` |

```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.

1. Every read tool calls `ensure_ready()` first: hash every source file, compare to cache (debounced 3s).
2. **Nothing changed** → no-op, returns immediately.
3. **Files modified/created** → incremental path: the daemon patches just those files and returns the recomputed edge set; only those files' node slices are swapped.
4. **Files deleted** → full rebuild (the daemon has no remove-file path; a fresh glob drops them correctly).
5. 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)

```bash
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.
