Local code navigation for AI agents

Give your coding agent a map.

PhoenixCodeNav helps Claude Code, Codex, and other MCP clients navigate large C# workspaces—locally, efficiently, and with every answer labeled by confidence.

MCP, in plain English

It is the connection that lets your coding agent use local tools. Phoenix runs behind the agent—it is not another editor or chat app.

Your agent asks

“What breaks if I change InvoiceService?”

Phoenix maps a route
  1. Find the definitionexact
  2. Trace its callersexact
  3. Suggest likely test leadsheuristic
Billing.Api InvoiceService.cs Billing.Tests Payments.Core
An illustrative map shows a coding agent question traveling from a C# symbol definition through exact callers to heuristic test leads.
Start with the basics
Local toolNo Phoenix cloud service
Tracked source unchangedLocal indexes live under .codenav
Legacy + modern C#Plus bounded F# support
Agent-agnosticAny compatible MCP client

One symbol, two very different journeys

Grep finds the word.
Phoenix understands the symbol.

Text search is great when you need text. But a coding agent also needs definitions, dependency direction, callers, and the smallest useful source span.

Broad text searchrg InvoiceService
Many matchesYou decide what matters
PSymbol navigationdefinition → callers → related_tests
One agent-orchestrated routeEvery step says how much to trust it

Keep both. Phoenix complements your editor and rg; it gives agents the structured path those tools do not.

Start with what you want to know

Ask the repo a
better question.

You do not need to memorize the tool names. Teach your agent the intent; Phoenix supplies a small, line-addressable answer.

01

“Where is this defined?”

Jump to the owning declaration and the exact source span instead of opening five possible files.

definition
02

“Who uses it?”

Follow references across projects with compiler-checked results whenever the semantic layer is available.

references
03

“What could this change break?”

Summarize dependent projects, change surface, and coupling risks before the edit becomes a surprise.

impact
04

“Which tests matter?”

Surface related test leads beside the symbol and its project neighborhood.

related_tests
05

“Which project owns this file?”

See which project compiles the file instead of guessing ownership from folder names.

projects_containing
06

“Help me review this change.”

Turn changed symbols into a compact review map with context, risks, and test leads.

review_pack

Trust is part of the response

Three depths.
No false certainty.

Phoenix uses the cheapest layer that can answer the question, then labels the result so your agent knows whether it has a fact or a lead.

01
Indexed

Find the words

Fast ranked text search over the persisted local index. Best for config keys, routes, errors, and literal text.

SQLite FTS · ranked · budgeted
02
Indexed

See the structure

Roslyn parses types, members, signatures, and spans so the agent can inspect an outline before reading source.

syntax map · file spans · no compile
03
Exact

Prove the connection

Lazy Roslyn compilations resolve definitions, references, implementations, and call graphs with compiler semantics.

compiler-verified · bounded coverage
Heuristic

A heuristic result is a lead to verify—not a fact in disguise. Partial, stale, or bounded coverage is reported too.

From syntax to meaning

How an agent turns
var into a fact.

This is a real example from Phoenix’s pinned Roslyn test repository. The agent starts with var newCompilation, resolves the exact method call in a semantic snapshot, and separately reads bounded live source context. From those labeled pieces of evidence — plus the C# rule for await — it can explain the variable’s type without guessing from text.

  1. 01

    The question in the code

    var hides the answer from plain text.

    The call site says what happens, but not which overload the compiler chose or what newCompilation means. A text search for GetCompilationAsync returns declarations and calls across the repository; the agent still needs the identity attached to this exact token.

    Roslyn · real source, shortened
    39 | public RegularCompilationTracker UnderlyingTracker { get; }
       |
    123| var newCompilation = await
       |     UnderlyingTracker.GetCompilationAsync(
       |         compilationState, cancellationToken)
       |     .ConfigureAwait(false);
       |
    136| newCompilation = newCompilation.ReplaceSyntaxTree(
       |     existingSyntaxTree, replacementSyntaxTree);
  2. 02

    Address the token, not the spelling

    The agent asks about one call in one snapshot.

    definition accepts the real workspace-relative path, 1-based line, and 1-based column. The optional name is an additional guard on a line that also declares a local variable. mode: "semantic" forbids a silent indexed guess, while includeBody returns the live declaration and its written return type.

    MCP · compiler-semantic definition request
    definition(
        name: "GetCompilationAsync",
        path: "src/Workspaces/Core/Portable/Workspace/Solution/" +
              "SolutionCompilationState.WithFrozenSourceGeneratedDocumentsCompilationTracker.cs",
        line: 123,
        column: 58,
        mode: "semantic",
        includeBody: true,
        timeoutMs: 30000
    )
    Exact positionCompiler binding for one source token
  3. 03

    The evidence chain

    Small facts compose into code understanding.

    Compile ownership comes from the index; a bounded live source read supplies the declared receiver type. Roslyn then binds the call to one symbol: RegularCompilationTracker.GetCompilationAsync. Its declaration returns Task<Compilation>; C# await unwraps that task, so the agent can state that newCompilation is a Compilation and explain why.

    Agent reasoning · every arrow has evidence
    UnderlyingTracker
    └─ declared type → RegularCompilationTracker
       └─ exact call → GetCompilationAsync(...)
          └─ return type → Task<Compilation>
             └─ await unwraps → Compilation
                └─ therefore → newCompilation : Compilation
    
    Follow-on check:
    newCompilation.ReplaceSyntaxTree(...)
    └─ receiver remains → Compilation
  4. 04

    The compiler-backed result

    The response carries identity, source, and provenance.

    The real integration case separately asserts the compiler-bound containing type, declaration path, Task<Compilation> signature, exact confidence, semantic navigation layer, and the live freshness of each source read. The representative envelope below uses the production field names and is parsed by the website gate so this explanation cannot drift into a fictional API.

    definition · representative response
    {
      "name": "GetCompilationAsync",
      "symbol": {
        "display": "Microsoft.CodeAnalysis.SolutionCompilationState.RegularCompilationTracker.GetCompilationAsync(Microsoft.CodeAnalysis.SolutionCompilationState, CancellationToken)",
        "kind": "method",
        "containingType": "RegularCompilationTracker",
        "ns": "Microsoft.CodeAnalysis",
        "assembly": "Microsoft.CodeAnalysis.Workspaces"
      },
      "declarations": [
        {
          "path": "src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs",
          "startLine": 216,
          "endLine": 233
        }
      ],
      "body": {
        "path": "src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs",
        "startLine": 216,
        "endLine": 233,
        "source": "  216| public async Task<Compilation> GetCompilationAsync(...)",
        "truncated": false,
        "freshness": "live"
      },
      "timing": {
        "deadlineMs": 30000,
        "elapsedMs": 54
      },
      "meta": {
        "indexStatus": "ready",
        "indexVersion": "7f4c2a9d18e64b3a92c01d5f6e8a7b40",
        "pendingChanges": 0,
        "confidence": "exact",
        "navigationLayer": "semantic",
        "build": "0.12.55+7b0b8112028b",
        "indexSchema": "28",
        "indexMode": "daemon"
      }
    }
    Compiler identityNot name matching dressed up as certainty
  5. 05

    The honest boundary

    Understanding is a proof trail, not a magic label.

    Phoenix does not claim that definition directly performs full expression inference for arbitrary C#. The agent composes a compiler-bound member identity, a separately read live declaration body, receiver context from another bounded live read, and a language rule. If the project cannot be loaded or the token cannot be resolved, the tool returns explicit semantic_unavailable or partial evidence instead of inventing the type.

    Agent conclusion · explainable and bounded
    What I can say:
      newCompilation is Compilation.
    
    Why:
      receiver declaration  → RegularCompilationTracker
      compiler-bound method → Task<Compilation>
      C# await rule         → Compilation
    
    What I cannot say:
      “Phoenix inferred every arbitrary expression type.”
    
    Evidence:
      indexed ownership
      text · live receiver context
      exact · semantic call binding
      separately read · live declaration body
    The useful answerA conclusion with receipts

Fast feedback without frozen promises

Progress you can see.
Confidence you can use.

Repository shape and hardware decide timings. Phoenix reports live progress, freshness, and bounded coverage instead of pretending one benchmark fits every workspace.

Cold index progress LIVE

phase · files · throughput

Measured on your machine, for your repository
Incremental updates DELTA

edits, additions, and deletes update the index

Freshness metadata HEAD

compare indexed state with the repository commit

Lazy semantics LAZY

compile only the project clusters an agent needs

Response budgets BOUND

compact, line-addressable context stays focused

Shared daemon ONE

every agent on a workspace joins one shared runtime — one index writer, one watcher, one warm semantic estate — transparently, with nothing to configure

Operations portal LOCAL

early-access loopback view of observed instances across up to eight configured workspaces, with index health and available live build progress — setup guide ↗

From source to first answer

Three steps.
One new superpower.

Build the documented self-contained Windows executable from source, connect it once, and let the index warm in the background.

  1. 01

    Build it

    Create the local server.

    Clone the repository and publish the complete self-contained win-x64 output. Keep the generated directory together: it contains the executable, its adjacent FSharp.Core.dll reference sidecar, and the portal/ companion. Building from source requires the .NET 10 SDK.

    PowerShell
    git clone https://github.com/grlap/PhoenixCodeNav.git
    cd PhoenixCodeNav
    
    dotnet publish src/CodeNav.Mcp -c Release -r win-x64 --self-contained `
      -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true `
      -p:IncludeNativeLibrariesForSelfExtract=true -o artifacts/win-x64
    Copy the complete outputartifacts/win-x64/ including portal/
  2. 02

    Connect it

    Give your agent the key.

    Point your MCP client at the executable you just built and the repository it should map. Put .mcp.json in that target repository's root; there, . means the repository itself.

    .mcp.json
    {
      "mcpServers": {
        "phoenix": {
          "command": "C:\\path\\to\\PhoenixCodeNav\\artifacts\\win-x64\\PhoenixCodeNav.Mcp.exe",
          "args": [
            "--workspace-root",
            "."
          ]
        }
      }
    }
    ~/.codex/config.toml
    [mcp_servers.phoenix]
    command = "C:\\path\\to\\PhoenixCodeNav\\artifacts\\win-x64\\PhoenixCodeNav.Mcp.exe"
    args = [
      "--workspace-root",
      "C:\\path\\to\\repo",
    ]
  3. 03

    Ask it

    Start with orientation.

    Add .codenav/ to your repository’s .gitignore, attach the provided agent instructions, and begin with a simple request.

    You

    Use Phoenix to map this repository before we change anything.

    Agent

    I’ll check repo_overview, index freshness, and project structure first.

    Phoenix connected · index builds in background

Local server, honest boundary

A local server.
An honest boundary.

PhoenixCodeNav runs locally; there is no hosted Phoenix service. Every agent you attach converges on one shared Phoenix daemon per workspace — elected and rejoined automatically, never configured. It reads your C# workspace, serves bounded results to each MCP client, and leaves tracked source files and Git refs unchanged. It does write local indexes under .codenav/.

Like other Git tooling, repository configuration still matters: Git may invoke configured helpers or fetch missing objects in a partial clone.

Important: your attached AI client may transmit returned context under its own data policy. Phoenix cannot control that boundary.

Complement, do not replace

One toolbox.
Three strengths.

.*

Use rg

For logs, generated output, unindexed file types, and raw text outside the indexed workspace. Phoenix also indexes Markdown and SQL for bounded text search.

Use your editor

For human exploration, refactoring UI, diagnostics, debugging, and writing the actual change.

P

Use Phoenix

When an agent needs ranked C#/F#/Markdown/SQL text search, C# structure and ownership, dependencies, or compiler-backed symbol facts.

Good questions before you start

Does Phoenix support every language?

No. PhoenixCodeNav is C#-first, with deliberately bounded F# support: .fs/.fsi declarations support indexed symbol-name search across at most 64 owner-aware contexts per file, compile-owned files get syntax outlines, and a bounded same-project semantic layer answers position lookups and definitions. Truncated indexed contexts and affected owners are disclosed as partial coverage. Markdown and SQL are indexed for text navigation only. Everything beyond those bounds is labeled unsupported instead of returning a misleading empty answer.

Is every answer compiler-exact?

No—and that honesty is a feature. Semantic answers are exact when the required Roslyn cluster and references load. Otherwise Phoenix labels indexed, heuristic, partial, or stale coverage so the agent can verify or widen deliberately.

Does it require MSBuild to load my solution?

No. Phoenix builds lazy Roslyn workspaces directly from parsed project facts. Building Phoenix from source requires the .NET 10 SDK; exact legacy semantics may also need .NET Framework reference assemblies.

What if I run five agents on one repository?

They share one Phoenix daemon per workspace. The first launch elects it; every later agent joins it transparently through a lightweight proxy — no flags, no topology to learn. One index writer, one file watcher, and one warm compiler estate serve every session, so each additional agent costs far less than another full process. And when startup cannot succeed, the tools stay alive and report a typed, actionable cause instead of presenting a dead server.

Will it edit my repository?

It does not edit tracked source files, apply code changes, or change Git refs. It writes local indexes under .codenav/, including inside an existing worktree you explicitly ask it to index; those files should be ignored by Git.

Ready for unfamiliar C# codebases

Stop hunting.
Start navigating.

Follow the source setup, attach PhoenixCodeNav to your coding agent, and give every unfamiliar C# repository a map.