Skip to content

Repository files navigation

dotnet-navigator

A proof-of-concept tool that gives AI coding assistants semantic understanding of .NET solutions via Roslyn. It provides twelve semantic analysis commands, two workspace-free discovery commands, plus an optional MCP stdio server that keeps one refreshable workspace warm across repeated calls.

This is a prototype, not a production framework. See docs/prototype.md for scope, non-goals, and known limitations.

Commands

Command Purpose
find-symbol <name> Where a type/method/property is defined.
find-references <name> All usages of a symbol.
find-implementations <name> Types implementing an interface / deriving a base class.
find-callers <method> Methods that call a method.
find-callees <method> Methods and constructors called by a method.
find-overrides <method> Overrides of a virtual/abstract method.
get-type-hierarchy <type> Base types, interfaces, derived types.
get-public-api <type> Public members of a type.
get-symbol-detail <name> Signature, parameters, return type, XML doc.
get-symbol-context <name> Bounded detail, references, and relationships for one symbol.
get-project-graph Solution project dependency tree.
get-diagnostics Compiler diagnostics for a file/project/solution.
list-commands Machine-readable command catalog and global option schema.
describe-command <command> Argument and option schema for one CLI or MCP command.

list-commands and describe-command do not discover or load a solution. Use them when an agent needs the exact command contract rather than parsing help text.

Target and candidate payloads include dnv2 symbolId values built from assembly identity, documentation ID, kind, fully qualified signature, and a deterministic metadata/source location fallback. Pass them back with --symbol-id to resolve same-named symbols exactly across source projects and metadata assemblies.

MCP server

Run dotnet-navigator mcp to start a stdio MCP server. By default the server starts immediately, exposes tools/list, and loads the Roslyn workspace in the background. Semantic tools return schema-valid status: "loading" payloads until the first generation is ready. The coordinator tracks workspace states not-started, loading, ready, refreshing, stale, failed, and stopping, plus active generation, timings, failures, and retry metadata.

Workspace refreshes build a fresh candidate workspace with empty caches and then publish it as a new generation. In atomic swap mode, calls already using an old generation finish safely and calls during refresh continue using the previous generation. In blocking swap mode, semantic calls during refresh return structured loading. A failed candidate load leaves the active generation in place and reports stale/failed status instead of crashing MCP startup.

# Small default surface with common navigation, context, graph, status, and refresh.
dotnet-navigator mcp --solution ./MyApp.slnx

# Every tool except the expensive compiler-diagnostics tool.
dotnet-navigator mcp --profile heavy

# Start from one or more categories, add tools, then remove tools last.
dotnet-navigator mcp \
  --categories navigation \
  --include-tools get-project-graph \
  --exclude-tools find-overrides

# Old startup behavior for deterministic local checks.
dotnet-navigator mcp --workspace-load blocking --refresh-strategy manual
Profile Exposed tools
light (default) find-symbol, find-references, get-symbol-detail, get-symbol-context, get-project-graph, refresh-workspace, get-workspace-status
heavy All navigation/context tools, get-project-graph, refresh-workspace, and get-workspace-status; excludes expensive diagnostics.
full All twelve analysis tools plus refresh-workspace and get-workspace-status.

Categories are navigation (symbol/type/call/context tools) and analysis (get-project-graph, get-diagnostics); both include refresh-workspace and get-workspace-status. --categories replaces the profile baseline. --include-tools adds named tools, then --exclude-tools removes named tools and therefore wins conflicts. Lists are comma-separated; unknown profiles, categories, or tool names are usage errors.

MCP lifecycle options are --workspace-load <background|blocking> (default background), --refresh-strategy <polling|manual> (default polling), --refresh-interval <seconds> (default 30), and --refresh-swap <atomic|blocking> (default atomic). Polling fingerprints solution/project/source inputs, SDK-globbed source/project files, common ancestor MSBuild/NuGet/editorconfig/global markers (Directory.Build.*, Directory.Packages.props, NuGet.config, global.json, .editorconfig) from project folders up to the nearest repository boundary or filesystem root, and does nothing when unchanged. Explicit custom MSBuild imports outside those bounded roots are not exhaustively discovered; put shared imports under the repo root or run a forced refresh after changing them.

refresh-workspace accepts force (default false), wait (default false), and optional timeoutMilliseconds. Its status is one of accepted, already-running, unchanged, completed, failed, or cooldown. wait: true joins an existing refresh; refreshes are coalesced so only one load runs at a time.

The MCP tool is annotated read-only because it mutates only dotnet-navigator's in-process Roslyn workspace cache. It does not write files, change projects, call external services, or mutate user/application state.

Global options

Option Meaning
--solution <sln|slnx|dir> Solution file or directory (default: discover from CWD, 3 levels deep).
--project <name> Restrict analysis to a project by exact name. Restricts returned locations (references, callers, implementations, overrides, derived types, diagnostics) to that project; target symbols declared elsewhere are still resolved. Unknown project => exit 2.
--format <json|text> Output format (default: json).
--max-results <1..1000> Cap list-producing commands, including get-type-hierarchy derived types and get-public-api members (default 50).
--brief Omit nonessential fields from symbol, reference, public API, symbol-detail, and project-graph results.

Command options are validated against a per-command allowlist: an unknown option, or an option that is only valid on a different command (e.g. --kind on get-project-graph), is a usage error (exit 2). | --help, -h | Show help. Follow with a command name for command help. | | --version | Show version. |

Output contract

  • stdout carries exactly one result document (a single-line JSON object) or human text for one-shot commands. In MCP mode stdout is reserved exclusively for the JSON-RPC transport.
  • JSON result contracts are versioned with schemaVersion. Symbol-targeting commands include status, message, target, candidates, and workspace metadata where applicable. Status values distinguish resolved, resolved-no-results, ambiguous, not-found, dependency-not-indexed, loading, failed, and stale.
  • Ambiguous target resolution is never silently satisfied by the first match. Retry with supported disambiguators returned by the contract: --symbol-id, --file/--line, --type, --signature, --arity, --kind, or --metadata true.
  • All logs, workspace warnings, and errors go to stderr. In normal mode there are no success logs.
  • List results are bounded deterministically; responses that support it include a totalFound field reporting the unbounded count. get-type-hierarchy reports derivedTypesCount/derivedTypesTotalFound for its bounded derivedTypes; get-public-api reports count/totalFound for its bounded members.
  • Brief results preserve identities, solution-relative locations, counts, and totals. Full output remains the default. MCP exposes the same optional brief parameter on tools whose payload changes.
  • File paths are solution-relative and forward-slashed. --file/--path matching is separator-anchored, so Order.cs does not match PriorityOrder.cs.
  • find-symbol --kind type matches any named type; --kind class is class-only.
  • Symbol matching normalizes attribute suffixes, generic arity/query syntax, records, constructors, extension/reduced methods, operators, indexers, and partial declarations where Roslyn exposes a stable symbol identity.
  • References/call relationships carry target identity and can resolve source symbols plus referenced-assembly metadata symbols when Roslyn binds them from the loaded project compilations. Source candidates win ordinary unqualified lookup; broad metadata candidates are added only when source has no exact candidate or when the query uses a qualified name, --symbol-id, or --metadata true. Metadata results expose origin and assembly; transitive NuGet package provenance is not reconstructed.
  • MCP analysis calls lease the current workspace generation; freshness checks are handled by polling or explicit refresh, not by every semantic call. Added or deleted SDK-globbed source files and common project inputs trigger polling refresh when --refresh-strategy polling is active. If refresh fails, calls keep using the last valid generation and report workspace.status: "stale" with lastRefreshFailure.
  • get-workspace-status reports cache and timing metrics: cached compilations, indexed metadata assemblies, latest refresh-check milliseconds, refresh milliseconds, and metadata-index milliseconds. These are direct instrumentation fields used by the test harness rather than a future placeholder.
  • get-project-graph includes project references plus package/framework dependency metadata from restored project.assets.json; when assets are unavailable, the project reports dependency-not-indexed instead of silently returning empty dependency lists.
  • get-diagnostics omits generated/intermediate outputs (obj/, *.g.cs, AssemblyInfo.cs, *.GlobalUsings.g.cs, …) so source diagnostics dominate.

Exit codes

Code Meaning
0 Success, including empty result sets.
1 Internal failure.
2 Usage error: unknown command, invalid option, or unknown --project.
3 Solution discovery or workspace load failure.
130 Cancelled (Ctrl+C).

Evals

Repository-local evals live under evals/.

  • make eval-scripted runs deterministic black-box MCP protocol and semantic cases with no model access.
  • make eval-agent keeps the legacy OpenAI-compatible Chat Completions loop for comparison.
  • make eval-pi runs the Pi SDK-backed agent eval. It builds evals/pi-runner, starts a fresh dotnet-navigator MCP subprocess per case, exposes only MCP-discovered tools to a fresh in-memory Pi session, and scores the same strict final JSON contract as agent mode.

Pi evals use existing Pi auth/subscription state. They do not accept API keys on argv and do not install or load Pi extensions, skills, context files, or built-in file/shell tools. Default model is EVAL_PI_MODEL=openai-codex/gpt-5.4-mini, which is OpenAI Codex through Pi, not OpenCode, and avoids GitHub Copilot credits. Users can override it with EVAL_PI_MODEL; optional knobs are EVAL_PI_PROVIDER, EVAL_PI_THINKING, EVAL_PI_REPEATS, EVAL_PI_MAX_CALLS, and --case-timeout-seconds.

The Pi SDK host uses exact local npm package pins at @earendil-works/pi-coding-agent@0.82.1; it does not execute a globally installed pi CLI, so a local CLI at 0.80.6 can coexist without changing eval runtime behavior.

Install from a local package

Build the package and install it into an isolated tool path:

dotnet tool restore
dotnet pack -c Release -o artifacts/packages

# Install into a local tool manifest (recommended) ...
dotnet new tool-manifest        # if you do not have one
dotnet tool install DotNetNavigator.Tool \
  --version 0.1.0-alpha.2 \
  --add-source ./artifacts/packages

# ... or into an isolated global tool path:
dotnet tool install DotNetNavigator.Tool \
  --version 0.1.0-alpha.2 \
  --add-source ./artifacts/packages \
  --tool-path /tmp/dnnav-tools

/tmp/dnnav-tools/dotnet-navigator --version

Usage examples

# Discover the solution from the current directory and list projects (JSON).
dotnet-navigator get-project-graph

# Point at a specific solution and find implementations of an interface.
dotnet-navigator find-implementations IOrderRepository --solution ./MyApp.slnx

# Human-readable output.
dotnet-navigator find-symbol OrderService --format text

# Bound a large result set and read the true total from `totalFound`.
dotnet-navigator find-references GetByIdAsync --max-results 20

# Omit snippets for a smaller reference payload.
dotnet-navigator find-references GetByIdAsync --brief --max-results 20

# Scope analysis to a single project.
dotnet-navigator get-public-api Order --project MyApp.Domain

# Symbol detail without opening the file.
dotnet-navigator get-symbol-detail CancelOrderAsync --type OrderService

# Reuse an ambiguity candidate exactly.
dotnet-navigator get-symbol-detail Append --symbol-id '<candidate symbolId>' --metadata true

# Resolve a framework metadata type by qualified name.
dotnet-navigator get-symbol-detail System.Text.StringBuilder

# Project graph dependency lists come from restored NuGet assets; missing assets
# are reported per project as dependency-not-indexed.
dotnet-navigator get-project-graph

# One bounded call for detail, references, callers/callees, and relationship metadata.
dotnet-navigator get-symbol-context CancelOrderAsync --type OrderService --max-results 10

# Compiler diagnostics for one project.
dotnet-navigator get-diagnostics --scope project --path MyApp.Domain --severity warning

Build, test, format, pack

make help     # list targets
make build    # dotnet build -c Release
make test     # dotnet test -c Release
make format   # csharpier format .
make check    # csharpier check .
make pack     # dotnet pack -c Release -o artifacts/packages

MCP evals

The repository includes a black-box MCP eval harness in evals/DotNetNavigator.Evals. It launches the built tool as a stdio MCP subprocess, performs real initialize/tool discovery/tool calls through the official MCP client, scores curated fixture oracles from tests/DotNetNavigator.Tests/TestData/SampleSolution, and writes JSON reports under artifacts/evals/. The eval project is not referenced by the NuGet tool package.

make eval-scripted

# Agent mode uses a fresh OpenAI-compatible Chat Completions conversation per case.
EVAL_BASE_URL=https://example.test \
EVAL_API_KEY=... \
EVAL_MODEL=... \
make eval-agent

# Inspect the MCP server manually with the official inspector.
npx --yes @modelcontextprotocol/inspector --cli \
  dotnet src/DotNetNavigator/bin/Release/net10.0/DotNetNavigator.dll \
  mcp --solution tests/DotNetNavigator.Tests/TestData/SampleSolution/SampleSolution.slnx \
  --profile full --workspace-load blocking

Scripted evals require no model access and are intended for CI. Agent evals may spend money; configure repeats with EVAL_REPEATS and deterministic sampling with EVAL_TEMPERATURE=0 when the provider supports it. Add cases by editing evals/DotNetNavigator.Evals/cases.json; derive expected facts manually from the fixture source, not from dotnet-navigator output.

Agent eval API keys are accepted only through EVAL_API_KEY, not CLI arguments. EVAL_BASE_URL must use HTTPS except for localhost/loopback development endpoints.

Or directly:

dotnet tool restore
dotnet restore
dotnet build -c Release
dotnet test -c Release
dotnet format --verify-no-changes
dotnet csharpier check .
dotnet pack -c Release -o artifacts/packages

CI also runs dotnet list DotNetNavigator.slnx package --vulnerable --include-transitive after restore to surface vulnerable direct or transitive packages without changing the repository NuGet audit policy.

About

A CLI and MCP server to help agents navigate .NET code effeciently

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages