Cartograph
Cartograph
Agenten-native Code-Intelligenz. Verwandle jedes Repository in einen abfragbaren Code-Graphen und stelle ihn Code-Agenten über MCP bereit — damit ein Agent fragen kann: „Was bricht, wenn ich das ändere?“ statt zu greppen und zu hoffen.
tree-sitter + SQLite. Keine Embeddings, kein Vektor-Store, keine API-Schlüssel, kein Server, keine Kosten.
→ Live-Demo — generiert aus einem echten Index dieses Repos bei jedem Push.
Das Problem
Gib einem Code-Agenten ein großes, unbekanntes Repo und beobachte, was es tut: grep, eine Datei lesen, nochmal grep, eine andere Datei lesen. Es verbrennt Kontext, um eine Struktur zu rekonstruieren, die ein Parser in einem einzigen Aufruf hätte liefern können – und es übersieht trotzdem den Aufrufer drei Module weiter, den seine Änderung gerade kaputt gemacht hat.
Die übliche Lösung ist RAG: Codebasis einbetten, „ähnliche“ Chunks abrufen. Aber „Wer ruft diese Funktion auf?“ ist keine Ähnlichkeitsfrage. Sie hat eine exakte Antwort, und diese Antwort lebt im Call-Graphen.
Cartograph baut den Graphen und gibt Agenten dann zehn Werkzeuge, die darauf zugeschnitten sind, wie sie tatsächlich arbeiten.
$ cartograph blast src/cartograph/graph/store.py
## Blast radius — file `src/cartograph/graph/store.py`
17 dependent file(s), 31 affected symbol(s), 7 test file(s).
**Tests to run first**
- `tests/test_cli.py`
- `tests/test_docs.py`
- `tests/test_incremental.py`
- `tests/test_mcp.py`
- `tests/test_resolver.py`
- `tests/test_traversal.py`
- `tests/test_views.py`
**Dependent files** (by import distance)
- `src/cartograph/graph/resolver.py` · d1
- `src/cartograph/indexer/pipeline.py` · d1
- `src/cartograph/service.py` · d1
- `src/cartograph/cli.py` · d2
…Ein einziger Aufruf, vor der Bearbeitung. Nicht sieben Greps, nachdem die Testsuite rot wird.
Schnellstart
uv tool install cartograph-mcp # or: pipx install cartograph-mcp
cartograph index ~/code/my-repo # builds .cartograph/cartograph.db
cartograph arch # modules, layers, cycles, hotspots
cartograph blast src/auth/token.py # what a change here could break
cartograph callers validate_token # reverse call treeBinde es in einen Agenten ein
Claude Code:
claude mcp add cartograph -- cartograph serve /path/to/repoOder einen beliebigen MCP-Client über mcp.json:
{
"mcpServers": {
"cartograph": {
"command": "cartograph",
"args": ["serve", "/path/to/repo"]
}
}
}serve indexiert beim ersten Lauf, wenn noch kein Index existiert. Frag dann deinen Agenten „Was würde brechen, wenn ich den Token-Validator ändere?“ und er wird blast_radius aufrufen, statt zu raten.
Die zehn Werkzeuge
Werkzeug | Antworten |
| Wo ist X definiert? (nach struktureller Wichtigkeit eingestuft) |
| Volltext über Namen, Signaturen, Docstrings (BM25) |
| Ein Symbol: Signatur, Doku, Member, Aufrufer, Aufgerufene, Quelle |
| Rückwärts-Aufrufbaum – bevor du eine Signatur änderst |
| Vorwärts-Aufrufbaum – Code verstehen, ohne jede Datei zu lesen |
| Was eine Änderung brechen könnte, und welche Tests ausgeführt werden sollten |
| „Was sollte ich sonst noch lesen?“ per personalisiertem PageRank |
| Was eine Datei definiert, importiert und wer sie importiert |
| Module, Schichtung, Importzyklen, Hotspots, Einstiegspunkte |
| Index-Gesundheit und die Aufschlüsselung der Kantenauflösung nach Regel |
Plus MCP-Ressourcen (cartograph://architecture, cartograph://stats) und einen orient-Prompt für einen graph-zentrierten ersten Durchgang durch ein unbekanntes Repo.
Sprachen: Python, TypeScript, TSX, JavaScript, Go.
Designentscheidungen, über die sich streiten lässt
1. Konfidenz ist eine erstklassige Spalte
Ohne Typchecker kannst du nicht wissen, dass store.who_calls() GraphStore.who_calls bedeutet. Du kannst Hypothesen nur einstufen. Statt also so zu tun, als wäre es eindeutig, zeichnet jede Kante die Regel auf, die sie erzeugt hat, plus eine Konfidenz:
Regel | Konfidenz | Intuition |
| 0.95 | die Definition ist direkt im Scope vorhanden |
| 0.90 | die Datei hat diesen Namen explizit importiert |
| 0.85 |
|
| 0.75 | Geschwisterdatei im selben Paket |
| 0.60 | genau ein Repo-Symbol hat diesen Namen, nackter Aufruf |
| 0.45 | eine Übereinstimmung, aber auf einem untypisierten Empfänger |
| ≤0.40 | N Kandidaten, als N Kanten zu je 1/N behalten |
| 0.00 | an einem Drittanbieter-/Stdlib-Import verwurzelt |
| 0.00 | wirklich unbekannt (dynamisch oder eine typisierte Methode) |
Die Aufrufer wählen dann ihren eigenen Arbeitspunkt. who_calls hat standardmäßig ≥0,5 – Präzision zuerst, weil ein Agent auf die Antwort handelt. blast_radius geht auf 0,3 herunter – Recall zuerst, denn ein übersehener betroffener Test ist der teure Fehler, und ein falsch Positives kostet einen Reviewer nur einen Blick.
Diese name-only-Stufe existiert wegen eines echten Bugs. seen.add(...) auf einem eingebauten set wurde der add-Methode einer Repo-Klasse zugeordnet, nur weil der Name zufällig eindeutig war – und es tauchte als Aufrufer mit hoher Konfidenz auf. Ein Methodenname auf einem Empfänger, den du nicht typisieren kannst, ist kein Beleg, also landet er jetzt unterhalb der Präzisionslinie. (test)
external existiert, um bei den Metriken ehrlich zu sein: In den meisten Repos wird der „unresolved“-Eimer von typer.Option und sqlite3.execute dominiert. Wenn man sie dazurechnet, sieht die Abdeckung viel schlechter aus, als sie ist. Deshalb meldet Cartograph die interne Auflösung – von den Aufrufstellen, die ein Repo-Symbol treffen könnten, wie viele es getan haben.
2. Parsing ist inkrementell; Auflösung nie
Eine Datei wird nur neu geparst, wenn sich ihr sha256 ändert. Aber rohe Referenzen werden als Fakten in einer refs-Tabelle gespeichert, und edges wird als reine Funktion von (refs × symbols) neu berechnet, wenn sich etwas geändert hat.
Das macht „nach jeder Bearbeitung neu indexieren“ vertrauenswürdig. Wenn die Auflösung ebenfalls inkrementell wäre, könnte das Bearbeiten einer Datei eine Kante in einer anderen Datei hinterlassen, die auf ein verschobenes Symbol zeigt. Globale Neuauflösung macht das strukturell unmöglich. (test)
Die Kosten sind real, daher gibt es genau eine sichere Abkürzung: Wenn keine Datei hinzugefügt, neu geparst oder entfernt wurde, sind beide Eingabetabellen unverändert und die Auflösung ist nachweislich identisch – also wird sie übersprungen. Das hat einen No-op-Reindex von Django von 7,5s auf 0,67s reduziert, mit einem byte-identischen Graphen.
3. PageRank statt Embeddings
„Welchen get meintest du?“ ist eine strukturelle Frage. Der get, von dem vierzig Aufrufstellen abhängen, ist der, den der Agent will, und der Call-Graph weiß das bereits. Daher ist das Symbol-Ranking ein gewichteter PageRank über den Call-Graphen – stabil, erklärbar und kostenlos. Kein Modell, kein Indexaufbau, kein Vektor-Store.
related_symbols erweitert dieselbe Idee: personalisierter PageRank, der mit einem Symbol gestartet wird, wobei der Graph als ungerichtet behandelt wird, denn wenn du eine Funktion ändern willst, sind sowohl ihre Aufrufer als auch ihre aufgerufenen Funktionen relevanter Kontext. Es ist das strukturelle Gegenstück zur semantischen Suche und benötigt keine Embeddings.
4. Werkzeuge liefern Markdown, nicht JSON, unter einem Token-Budget
Der Konsument ist ein Kontextfenster. Ein JSON-Array mit 40 Symbolen verbraucht Tausende von Token für geschweifte Klammern und wiederholte Schlüssel, und das Modell formatiert es ohnehin neu. Jede Ansicht hier ist kompaktes Markdown mit einem harten Token-Budget.
Entscheidend ist, dass jede Kürzung angekündigt wird. Ein Agent, dem 20 von 87 Aufrufern ohne Marker übergeben werden, wird zuversichtlich schlussfolgern, dass die anderen 67 nicht existieren, und dann etwas löschen.
5. Traversierung läuft in SQLite, nicht in Python
who_calls auf Tiefe 4 ist ein rekursiver CTE, sodass die gesamte Traversierung in der C-Schleife von SQLite bleibt. Bei Djangos Graphen mit 252k Kanten sind das ~5ms. Die Kantentabelle nach Python zu holen, um sie zu durchlaufen, wäre das nicht.
Benchmarks
Echte Repositories, M-Serie-Laptop, einzelner Prozess. Kalt = vollständiger Index von Grund auf; warm = No-op-Reindex.
Repo | Dateien | KLOC | Symbole | Kanten | Kalt | Warm | DB | Interne Auflösung |
2,973 | 534 | 45,394 | 252,441 | 11.9s | 0.67s | 80 MB | 83.2% | |
gin (Go) | 98 | 24 | 1,610 | 9,179 | 0.32s | 0.03s | 2.5 MB | 88.1% |
83 | 18 | 1,624 | 4,271 | 0.21s | 0.03s | 1.7 MB | 87.4% |
Abfragelatenz (Median von 5, warm):
Repo |
|
|
|
|
django | 12.3ms | 5.1ms | 5.6ms | 68.5ms |
gin | 0.4ms | 0.4ms | 0.5ms | 1.2ms |
flask | 0.5ms | 1.1ms | 1.3ms | 1.8ms |
Reproduzierbar mit scripts/bench.py.
Architektur
flowchart LR
subgraph index["cartograph index"]
W[walker<br/>git ls-files] --> P[tree-sitter<br/>+ .scm queries]
P --> X[extract<br/>defs · refs · imports]
end
X --> DB[(SQLite<br/>symbols · refs<br/>edges · FTS5)]
DB --> R[resolver<br/>rule cascade]
R --> DB
DB --> RK[PageRank<br/>Tarjan SCC]
RK --> DB
DB --> S[service facade]
S --> V[views<br/>token-budgeted MD]
V --> M[MCP server<br/>10 tools]
V --> C[CLI]
M --> A((coding agent))Modul | Verantwortung |
| Dateierkennung – verlässt sich auf |
| Ein Adapter pro Sprache: Erweiterungen, Queries, Docstrings, Modulschlüssel, Importauflösung |
| AST → Symbole/Referenzen/Importe, sprachunabhängig |
| tree-sitter-Capture-Muster – das sprachspezifische Wissen als Daten |
| Der Graph: |
| Die Konfidenz-Kaskade |
| PageRank, personalisierter PageRank, iteratives Tarjan-SCC, Schichtung |
| Rekursive-CTE-Traversierung, rangierte Suche, Aggregate |
| Eine Fassade, damit CLI und MCP-Server nicht auseinanderdriften |
| Markdown mit Token-Budget |
Scoping ohne kombinatorische Queries
Der Trick, der queries/*.scm klein hält: Der Scope wird nie in der Query kodiert. Jede erfasste Definition wird über ihre tree-sitter-Node-ID indexiert, und das umgebende Symbol einer Referenz wird gefunden, indem man ihre parent-Kette entlanggeht, bis man auf eines trifft. Das ist O(Baumtiefe) pro Referenz und handhabt Closures, Methoden, innere Klassen und Pfeilfunktionen kostenlos – keine Muster pro Form.
Hinzufügen einer Sprache
Erstelle eine Unterklasse von LanguageAdapter (~40 Zeilen) und lege eine .scm-Datei ab. GoAdapter ist das kürzeste vollständige Beispiel. tests/test_queries.py kompiliert dann deine Queries automatisch gegen die Grammatik und stellt sicher, dass sie etwas erfassen.
Entwicklung
git clone https://github.com/GokulRaj2210/cartograph-mcp && cd cartograph-mcp
uv sync
uv run pytest -q # 209 tests
uv run ruff check .
uv run mypy # strictDie CI führt die Suite auf Python 3.11/3.12/3.13 (plus macOS) aus und betreibt dann Dogfooding: Sie indexiert dieses Repo, schlägt bei Importzyklen fehl, stellt sicher, dass ein No-op-Reindex nichts neu parst, und steuert den MCP-Server über echtes stdio. Außerdem installiert sie das gebaute Wheel in eine saubere venv und indexiert damit, denn verpackte .scm-Dateien lassen sich leicht aus einem Wheel weglassen und lokal unmöglich bemerken.
Das Zyklus-Gate hat sich bereits bezahlt gemacht – es hat einen store → resolver → store-Zyklus erwischt, den ich in diesem Repo eingeführt hatte, und der behoben wurde, indem der verursachende Helfer verschoben wurde, statt das Gate zu lockern.
Bemerkenswerte Tests
tests/test_queries.py— jede.scm-Datei kompiliert gegen jede Grammatik, die sie lädt, und erfasst etwas. Ein in JavaScript gültiges Muster ((class_heritage (identifier))) ist in TypeScript ein unmögliches Muster, da TypeScript Supertypen inextends_clausekapselt. Diese eine Zeile erzeugte stillschweigend null TypeScript-Symbole.tests/test_incremental.py— keine veralteten Kanten nach Bearbeitungen, Löschungen oder wenn ein Symbol zwischen Dateien verschoben wird.tests/test_resolver.py— jede Regel greift, und keine überschätzt ihre Konfidenz.tests/test_cli.py— ein Leser und ein Indexierer können die Datenbank gleichzeitig geöffnet halten.tests/test_docs.py— die generierte Demoseite ist wohlgeformtes HTML mit ausbalancierten Tags, wodurch der Überkreuz-Tag-Fehler des Markdown-Renderers beimin_confidenceentdeckt wurde.
Einschränkungen
Ganz offen gesagt: Ein Code-Intelligence-Tool, das seine Präzision übermäßig anpreist, ist schlimmer als nutzlos.
Keine Typinferenz.
self.conn.execute(...)kann ohne Kenntnis des Typs vonconnnicht zu einem Repo-Symbol aufgelöst werden. Diese landen inunresolvedund bilden den Großteil dessen, was bei einer internen Auflösung von ~85% übrig bleibt.Dynamischer Dispatch ist unsichtbar.
getattr(obj, name)(), Dekorator-Registries und DI-Container erscheinen nicht als Kanten.Sprachübergreifende Kanten werden nicht erfasst. Ein TypeScript-Frontend, das einen Python-Endpunkt aufruft, ergibt zwei getrennte Teilgraphen.
Nur Definitionen, nicht jede Referenz. Ein Symbol, das als Wert verwendet wird (als Callback übergeben), ist im Graphen schwächer als eines, das aufgerufen wird.
Roadmap: Rust- und Java-Adapter, optionale LSP-Anreicherung für exakte Auflösung, wenn ein Sprachserver verfügbar ist, und ein --changed-since <ref>-Modus für den Blast-Radius auf PR-Ebene.
Warum es das gibt
Ich wollte wissen, ob die größte Schwäche eines Coding-Agenten bei großen Repos – kein strukturelles Modell des Codes – durch statische Analyse und eine gut gestaltete Tool-Oberfläche behoben werden kann, statt durch ein größeres Modell oder eine Vektordatenbank. Größtenteils ist das möglich.
Lizenz
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Give your AI agent a persistent map of your project's structure, dependencies, and bugs.
Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.
AI Agent with Architectural Memory. Impact analysis (free), tests and code from the graph (pro).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/GokulRaj2210/cartograph-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server