Cartograph
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Cartographwhat would break if I change validate_token?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Cartograph
Agent-native code intelligence. Turn any repository into a queryable code graph and serve it to coding agents over MCP — so an agent can ask "what breaks if I change this?" instead of grepping and hoping.
tree-sitter + SQLite. No embeddings, no vector store, no API keys, no server, no cost.
→ Live demo — generated from a real index of this repo on every push.
The problem
Give a coding agent a large unfamiliar repo and watch what it does: grep, read a file, grep again, read another file. It burns context reconstructing structure that a parser could have told it in one call — and it still misses the caller three modules away that its change just broke.
The usual fix is RAG: embed the codebase, retrieve "similar" chunks. But "who calls this function?" is not a similarity question. It has an exact answer, and that answer lives in the call graph.
Cartograph builds the graph, then hands agents ten tools shaped for how they actually work.
$ 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
…One call, before the edit. Not seven greps after the test suite goes red.
Quickstart
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 treeWire it into an agent
Claude Code:
claude mcp add cartograph -- cartograph serve /path/to/repoOr any MCP client, via mcp.json:
{
"mcpServers": {
"cartograph": {
"command": "cartograph",
"args": ["serve", "/path/to/repo"]
}
}
}serve indexes on first run if no index exists. Then ask your agent "what would break if I changed the token validator?" and it will call blast_radius instead of guessing.
The ten tools
Tool | Answers |
| Where is X defined? (ranked by structural importance) |
| Full-text over names, signatures, docstrings (BM25) |
| One symbol: signature, doc, members, callers, callees, source |
| Reverse call tree — before you change a signature |
| Forward call tree — understand code without reading every file |
| What a change could break, and which tests to run |
| "What else should I read?" via personalized PageRank |
| What a file defines, imports, and who imports it |
| Modules, layering, import cycles, hotspots, entry points |
| Index health and the edge-resolution breakdown by rule |
Plus MCP resources (cartograph://architecture, cartograph://stats) and an orient prompt for a graph-first first pass at an unfamiliar repo.
Languages: Python, TypeScript, TSX, JavaScript, Go.
Design decisions worth arguing about
1. Confidence is a first-class column
Without a type checker you cannot know that store.who_calls() means GraphStore.who_calls. You can only rank hypotheses. So rather than pretending, every edge records the rule that produced it and a confidence:
Rule | Confidence | Intuition |
| 0.95 | the definition is right there in scope |
| 0.90 | the file explicitly imported this name |
| 0.85 |
|
| 0.75 | sibling file in the same package |
| 0.60 | exactly one repo symbol has this name, bare call |
| 0.45 | one match, but on an untyped receiver |
| ≤0.40 | N candidates, kept as N edges at 1/N each |
| 0.00 | rooted at a third-party/stdlib import |
| 0.00 | genuinely unknown (dynamic, or a typed method) |
Callers then choose their own operating point. who_calls defaults to ≥0.5 — precision first, because an agent acts on the answer. blast_radius drops to 0.3 — recall first, because a missed impacted test is the expensive mistake and a false positive only costs a reviewer a glance.
That name-only tier exists because of a real bug. seen.add(...) on a builtin set was resolving to a repo class's add method, purely because the name happened to be unique — and it showed up as a confident caller. A method name on a receiver you cannot type is not evidence, so it now lands below the precision line. (test)
external exists for honesty about metrics: on most repos the "unresolved" bucket is dominated by typer.Option and sqlite3.execute. Lumping those in makes coverage look far worse than it is, so Cartograph reports internal resolution — of the call sites that could hit a repo symbol, how many did.
2. Parsing is incremental; resolution never is
A file is reparsed only when its sha256 moves. But raw references are stored as facts in a refs table, and edges is recomputed as a pure function of (refs × symbols) whenever anything changed.
This is what makes "reindex after every edit" trustworthy. If resolution were also incremental, editing one file could leave an edge in another file pointing at a symbol that had moved. Global re-resolution makes that structurally impossible. (test)
The cost is real, so there is exactly one safe shortcut: if no file was added, reparsed, or removed, both input tables are unchanged and resolution is provably identical — so it is skipped. That took a no-op reindex of Django from 7.5s to 0.67s with a byte-identical graph.
3. PageRank instead of embeddings
"Which get did you mean?" is a structural question. The get that forty call sites depend on is the one the agent wants, and the call graph already knows that. So symbol ranking is weighted PageRank over the call graph — stable, explainable, and free. No model, no index build, no vector store.
related_symbols extends the same idea: personalized PageRank seeded on one symbol, treating the graph as undirected, because when you are about to change a function both its callers and its callees are relevant context. It is the structural analogue of semantic search, and it needs no embeddings.
4. Tools return Markdown, not JSON, under a token budget
The consumer is a context window. A 40-symbol JSON array spends thousands of tokens on braces and repeated keys, and the model reformats it anyway. Every view here is compact Markdown with a hard token budget.
Critically, every truncation is announced. An agent handed 20 of 87 callers with no marker will confidently conclude the other 67 do not exist, and then delete something.
5. Traversal runs in SQLite, not Python
who_calls at depth 4 is a recursive CTE, so the whole traversal stays inside SQLite's C loop. On Django's 252k-edge graph that is ~5ms. Pulling the edge table into Python to walk it would not be.
Benchmarks
Real repositories, M-series laptop, single process. Cold = full index from scratch; warm = no-op reindex.
Repo | Files | KLOC | Symbols | Edges | Cold | Warm | DB | Internal resolution |
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% |
Query latency (median of 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 |
Reproduce with scripts/bench.py.
Architecture
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))Module | Responsibility |
| File discovery — defers to |
| One adapter per language: extensions, queries, docstrings, module keys, import resolution |
| AST → symbols/references/imports, language-agnostic |
| tree-sitter capture patterns — the per-language knowledge, as data |
| The graph: |
| The confidence cascade |
| PageRank, personalized PageRank, iterative Tarjan SCC, layering |
| Recursive-CTE traversal, ranked lookup, aggregates |
| One facade so the CLI and MCP server cannot drift |
| Token-budgeted Markdown |
Scoping without combinatorial queries
The trick that keeps queries/*.scm small: scope is never encoded in the query. Every captured definition is indexed by its tree-sitter node id, and a reference's enclosing symbol is found by walking its parent chain until it hits one. That is O(tree depth) per reference and handles closures, methods, inner classes, and arrow functions for free — no per-shape patterns.
Adding a language
Subclass LanguageAdapter (~40 lines) and drop in a .scm file. GoAdapter is the shortest complete example. tests/test_queries.py then automatically compiles your queries against the grammar and asserts they capture something.
Development
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 # strictCI runs the suite on Python 3.11/3.12/3.13 (plus macOS), then dogfoods: it indexes this repo, fails on import cycles, asserts a no-op reindex reparses nothing, and drives the MCP server over real stdio. It also installs the built wheel into a clean venv and indexes with it, because packaged .scm files are easy to leave out of a wheel and impossible to notice locally.
The cycle gate has already earned its keep — it caught a store → resolver → store cycle that I introduced in this repo, which was fixed by moving the offending helper rather than by relaxing the gate.
Notable tests
tests/test_queries.py— every.scmcompiles against every grammar that loads it, and captures something. A pattern valid in JavaScript ((class_heritage (identifier))) is an Impossible pattern in TypeScript, which wraps supertypes inextends_clause. That one line silently produced zero TypeScript symbols.tests/test_incremental.py— no stale edges after edits, deletions, or a symbol moving between files.tests/test_resolver.py— every rule fires, and none over-claims its confidence.tests/test_cli.py— a reader and an indexer can hold the database at once.tests/test_docs.py— the generated demo page is well-formed HTML with balanced tags, which is how the Markdown renderer's crossed-tag bug onmin_confidencewas caught.
Limitations
Stated plainly, because a code-intelligence tool that oversells its precision is worse than useless:
No type inference.
self.conn.execute(...)cannot be resolved to a repo symbol without knowingconn's type. Those land inunresolved, and they are the bulk of what remains at ~85% internal resolution.Dynamic dispatch is invisible.
getattr(obj, name)(), decorator registries, and DI containers do not appear as edges.Cross-language edges are not tracked. A TypeScript frontend calling a Python endpoint is two disconnected subgraphs.
Definitions only, not every reference. A symbol used as a value (passed as a callback) is weaker in the graph than one that is called.
Roadmap: Rust and Java adapters, optional LSP enrichment for exact resolution where a language server is available, and a --changed-since <ref> mode for PR-scoped blast radius.
Why this exists
I wanted to know whether a coding agent's biggest weakness on large repos — no structural model of the code — could be fixed with static analysis and a well-shaped tool surface rather than a bigger model or a vector database. Mostly, it can.
License
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