Skip to main content
Glama

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.

CI Python 3.11+ License MIT


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 tree

Binde es in einen Agenten ein

Claude Code:

claude mcp add cartograph -- cartograph serve /path/to/repo

Oder 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

find_symbol

Wo ist X definiert? (nach struktureller Wichtigkeit eingestuft)

search_code

Volltext über Namen, Signaturen, Docstrings (BM25)

get_symbol

Ein Symbol: Signatur, Doku, Member, Aufrufer, Aufgerufene, Quelle

who_calls

Rückwärts-Aufrufbaum – bevor du eine Signatur änderst

what_it_calls

Vorwärts-Aufrufbaum – Code verstehen, ohne jede Datei zu lesen

blast_radius

Was eine Änderung brechen könnte, und welche Tests ausgeführt werden sollten

related_symbols

„Was sollte ich sonst noch lesen?“ per personalisiertem PageRank

file_summary

Was eine Datei definiert, importiert und wer sie importiert

architecture_overview

Module, Schichtung, Importzyklen, Hotspots, Einstiegspunkte

index_stats

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

same-file

0.95

die Definition ist direkt im Scope vorhanden

import

0.90

die Datei hat diesen Namen explizit importiert

receiver-type

0.85

Foo.bar(), wobei Foo ein bekannter Container ist

same-module

0.75

Geschwisterdatei im selben Paket

unique-global

0.60

genau ein Repo-Symbol hat diesen Namen, nackter Aufruf

name-only

0.45

eine Übereinstimmung, aber auf einem untypisierten Empfänger

ambiguous

≤0.40

N Kandidaten, als N Kanten zu je 1/N behalten

external

0.00

an einem Drittanbieter-/Stdlib-Import verwurzelt

unresolved

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

django

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%

flask

83

18

1,624

4,271

0.21s

0.03s

1.7 MB

87.4%

Abfragelatenz (Median von 5, warm):

Repo

find_symbol

who_calls d3

blast_radius

architecture_overview

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

indexer/walker.py

Dateierkennung – verlässt sich auf git ls-files für korrekte .gitignore-Semantik

indexer/languages.py

Ein Adapter pro Sprache: Erweiterungen, Queries, Docstrings, Modulschlüssel, Importauflösung

indexer/extract.py

AST → Symbole/Referenzen/Importe, sprachunabhängig

queries/*.scm

tree-sitter-Capture-Muster – das sprachspezifische Wissen als Daten

graph/schema.sql

Der Graph: files, symbols, refs, edges, imports, FTS5

graph/resolver.py

Die Konfidenz-Kaskade

graph/algorithms.py

PageRank, personalisierter PageRank, iteratives Tarjan-SCC, Schichtung

graph/store.py

Rekursive-CTE-Traversierung, rangierte Suche, Aggregate

service.py

Eine Fassade, damit CLI und MCP-Server nicht auseinanderdriften

views.py

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

Die 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 in extends_clause kapselt. 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 bei min_confidence entdeckt 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 von conn nicht zu einem Repo-Symbol aufgelöst werden. Diese landen in unresolved und 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

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all MCP Connectors

Latest Blog Posts

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