MCP · stdio · read-only

The map an agent cannot build for itself.

Agents have a specific economics: every token of context costs, and the global view never fits. An agent that re-reads five files to work out what calls a function has spent its budget rebuilding something a graph already knows. nao serves that graph as compact ranked text — capped, explicitly truncated, filtered of noise.

The loop

Eight steps, each one tool call.

This is the shape of a task that goes well. Every step replaces something the agent would otherwise do by reading files and inferring.

  1. 01

    Ground overview

    Which Feature does this task belong to, and which files does it claim? Skipped when the project has no .elv layer.

  2. 02

    Orient map

    Get the folder’s shape instead of reading ten files to reconstruct it.

  3. 03

    Locate similar

    Check the helper does not already exist under another name.

  4. 04

    Prepare context

    Pull the edit target plus its neighbours’ signatures in one call.

  5. 05

    Check reach impact · trace

    Who breaks if this changes, and how control arrives here.

  6. 06

    Edit

    Ordinary edits, with LSP for the precise pointwise work. nao does not replace that layer.

  7. 07

    Verify tests_for

    Run the tests that actually reach the change, not the whole suite.

  8. 08

    Self-review assess_change

    Metric deltas and smell churn on the real diff, before presenting it.

The surface

Ten tools, all read-only.

ToolAnswers
overviewThe Elevator domain map, or a focused bundle for one entity. The grounding step — which capability does this task belong to.
mapFiles and entities of a folder with complexity and coupling annotations, at three depth levels. One call instead of ten file reads.
similarSimilarity-ranked search before you write something that already exists.
contextA minimal edit pack: the target’s source plus the signatures of everything it uses and everything using it.
impactBlast radius — what it uses, direct dependents, then transitive dependents level by level.
traceShortest dependency path(s) from A to B, with the relationship kind of every hop.
qualitySmells, top offenders by composite refactor pressure, dependency cycles.
hotspotsGit churn × complexity. The ranking complexity alone cannot give you.
tests_forWhich tests reach an entity, directly or transitively — including Rust inline mod tests.
assess_changeWorking tree versus a git ref: per-entity metric deltas, added and removed entities, smell churn.

Why the output is trustworthy

Two properties make the loop worth running.

Deterministic analysis

Identical trees produce byte-identical graphs. That is what makes assess_change usable: every delta it reports is a real change in the code, not parser jitter between two runs.

Exact type edges where it counts

UsesType edges come straight out of signatures and fields in Rust, TypeScript, Svelte, Java, Kotlin and Python — so type-level impact is exact rather than inferred in those five.

Measured call edges

93.7% recall at 98.6% precision against a rust-analyzer oracle. Good enough to act on — not good enough to treat an empty result as proof.

Warm cache

Graphs are cached per path with watcher-driven invalidation. The first call after a source change re-analyses; the rest are milliseconds.

The full limitations, including what the parsers still miss →

Setup

One binary, one stdio server.

nao mcp speaks MCP over stdio. Drop it in your client's server config — .mcp.json at the project root for Claude Code, the equivalent block for any other MCP client — and the tools appear.

There is no daemon to run, no port to pick and nothing to authenticate against. The server reads the checkout it was pointed at and nothing else.

Install first →
.mcp.json
{
  "mcpServers": {
    "nao": {
      "command": "nao",
      "args": ["mcp"]
    }
  }
}

A good first prompt

“Use nao's overview and map before you read any files. Then use impact on the function you plan to change, and assess_change before you show me the diff.”