Skip to main content
Glama
Aditya31398

repo-atlas-mcp

by Aditya31398
README.md
# repo-atlas-mcp

An MCP server that gives coding agents a **graph** of your codebase instead of a text search over it.

Grep finds a name. It cannot tell you who calls that function, what breaks if you change it, or whether
any test reaches it. Those are relationship questions, so `repo-atlas-mcp` parses your repository with
tree-sitter, loads the call and import structure into Neo4j, and exposes it to your agent as MCP tools.

```
▸ hotspots
  17 callers  stock_analyzer/base.py:54        is_num
  10 callers  stock_analyzer/data/market.py:144  get_row
   9 callers  stock_analyzer/base.py:12        Finding

▸ impact_of is_num
  21 production caller(s) across 11 file(s).
  ⚠ No test reaches this symbol on any call path — changes here are unguarded.
```

## Why a graph

Reachability is transitive, and transitive questions are exactly what a graph database answers in one
query and a text search cannot answer at all. `impact_of` walks every call path upstream of a symbol,
partitions the callers into production code and tests, and tells you when nothing guards the change.
That is the query that stops an agent from confidently editing shared code.

## Requirements

- Node.js 18+
- A Neo4j instance. [AuraDB Free](https://console.neo4j.io) is enough for repositories in the
  hundreds-of-thousands-of-lines range and costs nothing.

## Setup

Create a free AuraDB instance, then note the connection URI and password it gives you.

Add the server to your MCP client:

```json
{
  "mcpServers": {
    "repo-atlas": {
      "command": "npx",
      "args": ["-y", "repo-atlas-mcp"],
      "env": {
        "NEO4J_URI": "neo4j+s://xxxxxxxx.databases.neo4j.io",
        "NEO4J_USER": "neo4j",
        "NEO4J_PASSWORD": "your-password"
      }
    }
  }
}
```

For Claude Code that goes in `.mcp.json` at your project root, or run:

```bash
claude mcp add repo-atlas -- npx -y repo-atlas-mcp
```

Then ask your agent to index the project once:

> index this repo at /path/to/project

Re-indexing is incremental — unchanged files are skipped by content hash, so running it again after a
few edits takes about as long as parsing the files you touched.

## Tools

| Tool | Question it answers |
| --- | --- |
| `index_repo` | Build or refresh the graph for a repository |
| `find_symbol` | Where is this function/class/method defined? |
| `who_calls` | What reaches this symbol, transitively? |
| `what_it_calls` | What does this symbol depend on, transitively? |
| `impact_of` | What breaks if I change this — and do any tests cover it? |
| `path_between` | How does control flow from A to B? |
| `hotspots` | Which symbols have the most distinct callers? |
| `module_map` | What does the top-level package structure depend on? |
| `list_repos` | What is indexed? |
| `drop_repo` | Remove a repository from the graph |

## Languages

Python, TypeScript, TSX, and JavaScript, via tree-sitter WASM grammars — no native compilation, so
`npx` works on a clean machine without build tools.

## How call resolution works, and where it is wrong

Resolving a call to a definition properly requires full type inference. This does something cheaper and
tells you when it is guessing. What the call looks like decides how it is resolved:

**`self.foo()` / `this.foo()`** — resolves to `foo` on the enclosing class, else to a definition in the
same file. This is the one method form that resolves precisely. Tier: `local`.

**`foo()`** — a bare call, very likely a repo-level function. Resolved against a definition in the same
file (`local`), then in a file this one imports (`imported`), then repo-wide if three or fewer
candidates exist (`global`).

**`something.foo()`** — the receiver's type is unknown. Accepted only when `foo` is defined exactly once
in the whole repository *and* is not a well-known builtin name. Tier: `receiver`.

That last rule matters more than it looks. Without it, one repo method named `get` absorbs every
`dict.get()` and `response.get()` in the codebase; on a real 26-file project it produced a phantom
symbol with 59 callers, ranked as the most-called code in the project. The denylist in
`COMMON_METHOD_NAMES` (`get`, `add`, `update`, `join`, `map`, `read`, …) is a heuristic, and it is
consulted *only* for this weakest case — a bare `get()` still resolves normally.

Every `CALLS` edge stores which tier produced it, and the tools surface anything above `local`.

Known limitations, stated plainly:

- **Dynamic dispatch is invisible.** `getattr(obj, name)()`, reflection, and dependency-injection
  wiring produce no edges.
- **Polymorphism is not modelled.** Two classes with a `run()` method cannot be told apart, so calls on
  a non-`self` receiver to a name defined more than once are dropped rather than guessed.
- **Calls into third-party packages are dropped.** Only symbols defined inside the repository become
  nodes.
- **Top-level code is attributed to a synthetic `<module>` symbol** per file, so route registration and
  CLI wiring still appear in the graph rather than being discarded.
- **Repeated calls between the same pair collapse to one edge**, which keeps the last line number seen.

The practical effect: the graph under-reports rather than over-reports. `impact_of` can miss a caller
that goes through dynamic dispatch, so treat its output as a strong lead, not a proof — but a symbol it
names really is a caller.

## Graph model

```
(:Repo)-[:HAS_FILE]->(:File)-[:DEFINES]->(:Symbol)
(:File)-[:IMPORTS]->(:File)
(:Symbol)-[:CALLS {line, confidence}]->(:Symbol)
```

`Symbol.isTest` is set from the defining file's path, which is what lets `impact_of` separate covering
tests from production callers.

Ranking in `hotspots` uses caller in-degree rather than PageRank, so it runs on AuraDB Free, which does
not include the Graph Data Science library.

## Development

```bash
npm install
npm run build
node scripts/smoke.mjs                 # parser check, no database needed
node scripts/bulk-parse.mjs /some/repo  # parse a real repo and report totals
```

## License

MIT

TDQS

A3.5/5.0

Scored across 10 tools

Disambiguation3/5

Tools like who_calls, impact_of, and hotspots all concern callers of a symbol, which could cause an agent to pick the wrong one. However, their descriptions clarify distinctions (nearest callers vs. full upstream impact vs. ranked by caller count), reducing ambiguity.

Naming Consistency3/5

Names are all snake_case and readable, but they mix verb_noun patterns (list_repos, index_repo, find_symbol) with question-style phrases (who_calls, what_it_calls) and noun phrases (hotspots, module_map, path_between). The inconsistency is noticeable but not chaotic.

Tool Count5/5

With 10 tools, the server is well-scoped for its purpose of code graph analysis and repository management. Each tool addresses a distinct need, and the count falls comfortably within the ideal range.

Completeness4/5

The tool surface covers repository lifecycle (list, index, drop) and key graph queries (find, callers, callees, impact, paths, hotspots). Minor gaps exist, such as lacking a dedicated 'get single repo' tool, but the coverage is sufficient for core workflows.

Maintenance

ActivityStale
ResponsivenessNo issues