Cartograph
Cartograph
Inteligencia de código nativa para agentes. Convierte cualquier repositorio en un grafo de código consultable y sírvelo a agentes de codificación a través de MCP — para que un agente pueda preguntarse «¿qué se rompe si cambio esto?» en lugar de hacer grep y esperar.
tree-sitter + SQLite. Sin embeddings, sin almacén vectorial, sin claves API, sin servidor, sin coste.
→ Demo en vivo — generado a partir de un índice real de este repositorio en cada push.
El problema
Dale a un agente de codificación un repositorio grande y desconocido y observa lo que hace: grep, leer un archivo, grep otra vez, leer otro archivo. Quema contexto reconstruyendo una estructura que un parser podría haberle dicho en una sola llamada — y aun así se le escapa el llamador tres módulos más allá que su cambio acaba de romper.
La solución habitual es RAG: embeber el código base y recuperar fragmentos «similares». Pero «¿quién llama a esta función?» no es una pregunta de similitud. Tiene una respuesta exacta, y esa respuesta vive en el grafo de llamadas.
Cartograph construye el grafo y luego entrega a los agentes diez herramientas diseñadas para cómo trabajan realmente.
$ 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
…Una sola llamada, antes de la edición. No siete greps después de que la suite de pruebas se ponga roja.
Inicio rápido
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 treeConéctalo a un agente
Claude Code:
claude mcp add cartograph -- cartograph serve /path/to/repoO cualquier cliente MCP, mediante mcp.json:
{
"mcpServers": {
"cartograph": {
"command": "cartograph",
"args": ["serve", "/path/to/repo"]
}
}
}serve indexa en la primera ejecución si no existe ningún índice. Luego pregúntale a tu agente «¿qué se rompería si cambiara el validador de tokens?» y llamará a blast_radius en lugar de adivinar.
Las diez herramientas
Herramienta | Respuestas |
| ¿Dónde se define X? (ordenado por importancia estructural) |
| Búsqueda de texto completo sobre nombres, firmas y docstrings (BM25) |
| Un símbolo: firma, documentación, miembros, llamadores, llamados, fuente |
| Árbol de llamadas inverso — antes de cambiar una firma |
| Árbol de llamadas directo — entiende el código sin leer cada archivo |
| Qué podría romper un cambio, y qué pruebas ejecutar |
| «¿Qué más debería leer?» mediante PageRank personalizado |
| Qué define un archivo, qué importa y quién lo importa |
| Módulos, capas, ciclos de importación, puntos calientes, puntos de entrada |
| Salud del índice y desglose de resolución de aristas por regla |
Además, recursos MCP (cartograph://architecture, cartograph://stats) y un prompt orient para una primera pasada orientada al grafo en un repositorio desconocido.
Lenguajes: Python, TypeScript, TSX, JavaScript, Go.
Decisiones de diseño que merecen debate
1. La confianza es una columna de primera clase
Sin un comprobador de tipos no puedes saber que store.who_calls() significa GraphStore.who_calls. Solo puedes clasificar hipótesis. Así que, en lugar de fingir, cada arista registra la regla que la produjo y una confianza:
Regla | Confianza | Intuición |
| 0.95 | la definición está justo ahí, en el ámbito |
| 0.90 | el archivo importó explícitamente este nombre |
| 0.85 |
|
| 0.75 | archivo hermano en el mismo paquete |
| 0.60 | exactamente un símbolo del repositorio tiene este nombre, llamada sin calificador |
| 0.45 | una coincidencia, pero sobre un receptor sin tipo |
| ≤0.40 | N candidatos, mantenidos como N aristas a 1/N cada uno |
| 0.00 | anclado en una importación de terceros/biblioteca estándar |
| 0.00 | genuinamente desconocido (dinámico, o un método con tipo) |
Los llamadores eligen entonces su propio punto de operación. who_calls usa por defecto ≥0.5 — primero la precisión, porque un agente actúa según la respuesta. blast_radius baja a 0.3 — primero la exhaustividad, porque una prueba afectada que se pasa por alto es el error caro, y un falso positivo solo le cuesta al revisor un vistazo.
Ese nivel name-only existe por un bug real. seen.add(...) sobre un set incorporado se resolvía al método add de una clase del repositorio, únicamente porque el nombre resultó ser único — y aparecía como un llamador con alta confianza. Un nombre de método sobre un receptor que no puedes tipar no es evidencia, así que ahora cae por debajo de la línea de precisión. (test)
external existe por honestidad con las métricas: en la mayoría de los repositorios, el grupo de «no resueltos» está dominado por typer.Option y sqlite3.execute. Meterlos en el mismo saco hace que la cobertura parezca mucho peor de lo que es, así que Cartograph informa de la resolución interna — de los lugares de llamada que podrían alcanzar un símbolo del repositorio, cuántos lo hicieron.
2. El análisis sintáctico es incremental; la resolución nunca lo es
Un archivo se vuelve a analizar solo cuando cambia su sha256. Pero las referencias brutas se almacenan como hechos en una tabla refs, y edges se recalcula como una función pura de (refs × símbolos) cuando algo cambia.
Esto es lo que hace fiable «reindexar después de cada edición». Si la resolución también fuera incremental, editar un archivo podría dejar una arista en otro archivo apuntando a un símbolo que se hubiera movido. La re-resolución global hace que eso sea estructuralmente imposible. (test)
El coste es real, así que hay exactamente un atajo seguro: si no se ha añadido, vuelto a analizar o eliminado ningún archivo, ambas tablas de entrada no cambian y la resolución es demostrablemente idéntica — por lo que se omite. Eso redujo un reindexado sin cambios de Django de 7.5s a 0.67s con un grafo byte-idéntico.
3. PageRank en lugar de embeddings
«¿Qué get querías decir?» es una pregunta estructural. El get del que dependen cuarenta lugares de llamada es el que el agente quiere, y el grafo de llamadas ya lo sabe. Así que el ranking de símbolos es PageRank ponderado sobre el grafo de llamadas — estable, explicable y gratuito. Sin modelo, sin construir índice, sin almacén vectorial.
related_symbols extiende la misma idea: PageRank personalizado con semilla en un símbolo, tratando el grafo como no dirigido, porque cuando estás a punto de cambiar una función, tanto sus llamadores como sus llamados son contexto relevante. Es el análogo estructural de la búsqueda semántica, y no necesita embeddings.
4. Las herramientas devuelven Markdown, no JSON, bajo un presupuesto de tokens
El consumidor es una ventana de contexto. Un array JSON de 40 símbolos gasta miles de tokens en llaves y claves repetidas, y el modelo lo reformatea de todos modos. Cada vista aquí es Markdown compacto con un presupuesto de tokens estricto.
Críticamente, cada truncamiento se anuncia. Un agente que recibe 20 de 87 llamadores sin ningún marcador concluirá con confianza que los otros 67 no existen, y luego borrará algo.
5. El recorrido se ejecuta en SQLite, no en Python
who_calls a profundidad 4 es una CTE recursiva, por lo que todo el recorrido permanece dentro del bucle C de SQLite. En el grafo de 252k aristas de Django eso son ~5ms. Traer la tabla de aristas a Python para recorrerla no lo sería.
Puntos de referencia
Repositorios reales, portátil con chip M, proceso único. Frío = índice completo desde cero; cálido = reindexado sin cambios.
Repositorio | Archivos | KLOC | Símbolos | Aristas | Frío | Cálido | BD | Resolución interna |
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% |
Latencia de consulta (mediana de 5, en cálido):
Repositorio |
|
|
|
|
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 |
Reprodúcelo con scripts/bench.py.
Arquitectura
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))Módulo | Responsabilidad |
| Descubrimiento de archivos — delega en |
| Un adaptador por lenguaje: extensiones, consultas, docstrings, claves de módulo, resolución de importaciones |
| AST → símbolos/referencias/importaciones, independiente del lenguaje |
| Patrones de captura de tree-sitter — el conocimiento específico del lenguaje, como datos |
| El grafo: |
| La cascada de confianza |
| PageRank, PageRank personalizado, SCC iterativo de Tarjan, capas |
| Recorrido con CTE recursivo, búsqueda clasificada, agregados |
| Una fachada para que la CLI y el servidor MCP no diverjan |
| Markdown con presupuesto de tokens |
Alcance sin consultas combinatorias
El truco que mantiene queries/*.scm pequeño: el ámbito nunca se codifica en la consulta. Cada definición capturada se indexa por su id de nodo de tree-sitter, y el símbolo contenedor de una referencia se encuentra recorriendo su cadena parent hasta dar con uno. Eso es O(profundidad del árbol) por referencia y gestiona cierres, métodos, clases internas y funciones flecha sin coste adicional — sin patrones por forma.
Añadir un lenguaje
Subclasifica LanguageAdapter (~40 líneas) y añade un archivo .scm. GoAdapter es el ejemplo completo más corto. tests/test_queries.py entonces compila automáticamente tus consultas contra la gramática y comprueba que capturan algo.
Desarrollo
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 # strictLa CI ejecuta la suite en Python 3.11/3.12/3.13 (además de macOS) y luego aplica dogfooding: indexa este repositorio, falla con ciclos de importación, verifica que un reindexado sin cambios no vuelve a analizar nada, y maneja el servidor MCP a través de stdio real. También instala la rueda construida en un venv limpio e indexa con ella, porque los archivos .scm empaquetados son fáciles de omitir de una rueda e imposibles de notar localmente.
El control de ciclos ya ha demostrado su valor: detectó un ciclo store → resolver → store que introduje en este repositorio, que se corrigió moviendo el helper problemático en lugar de relajar el control.
Pruebas notables
tests/test_queries.py— cada.scmcompila contra cada gramática que lo carga, y captura algo. Un patrón válido en JavaScript ((class_heritage (identifier))) es un patrón imposible en TypeScript, que envuelve los supertipos enextends_clause. Esa única línea produjo silenciosamente cero símbolos de TypeScript.tests/test_incremental.py— sin aristas obsoletas después de ediciones, eliminaciones o un símbolo que se mueve entre archivos.tests/test_resolver.py— cada regla se activa, y ninguna exagera su confianza.tests/test_cli.py— un lector y un indexador pueden mantener la base de datos a la vez.tests/test_docs.py— la página de demostración generada es HTML bien formado con etiquetas equilibradas, que es como se detectó el error de etiquetas cruzadas del renderizador de Markdown enmin_confidence.
Limitaciones
Dicho claramente, porque una herramienta de inteligencia de código que sobrevende su precisión es peor que inútil:
Sin inferencia de tipos.
self.conn.execute(...)no se puede resolver a un símbolo del repositorio sin conocer el tipo deconn. Esos terminan enunresolved, y son la mayor parte de lo que queda con una resolución interna de ~85%.El despacho dinámico es invisible.
getattr(obj, name)(), los registros de decoradores y los contenedores de DI no aparecen como aristas.Las aristas entre lenguajes no se rastrean. Un frontend en TypeScript que llama a un endpoint de Python son dos subgrafos desconectados.
Solo definiciones, no todas las referencias. Un símbolo utilizado como valor (pasado como callback) es más débil en el grafo que uno que es llamado.
Hoja de ruta: adaptadores para Rust y Java, enriquecimiento opcional con LSP para una resolución exacta cuando haya un servidor de lenguaje disponible, y un modo --changed-since <ref> para el radio de impacto limitado a un PR.
Por qué existe esto
Quería saber si la mayor debilidad de un agente de codificación en repositorios grandes — la falta de un modelo estructural del código — podía solucionarse con análisis estático y una superficie de herramientas bien diseñada, en lugar de con un modelo más grande o una base de datos vectorial. En su mayoría, se puede.
Licencia
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