Skip to main content
Glama

Slnmap

Slnmap (sln-map) — a semantic map of your .sln for AI coding agents.

Open source under the MIT license.

CI NuGet License: MIT

Your AI agent can't refactor .NET code it can't see. Ask an agent "what breaks if I change this interface?" and it guesses from the files in its context — missing callers in other projects and files it never opened. Slnmap gives the agent a precise, compiler-accurate map of your whole solution, so it answers correctly: every caller, every implementation, across every project. Fewer broken changes, no hallucinated dependencies. It runs locally and serves the map to your agent or editor over MCP.

Quickstart (3 steps)

1. Install the global tool (requires the .NET SDK 9.0+):

dotnet tool install --global Slnmap

If this is the first .NET global tool ever installed on the machine, the tools directory (~/.dotnet/tools) may not be on your PATH yet — open a new terminal before running slnmap.

2. Analyze your solution (or a single .csproj) — this builds slnmap.db in the current folder:

slnmap analyze path/to/YourSolution.sln

3. Connect your MCP client. For Claude Code, add this to .mcp.json in your project. Use an absolute path to the slnmap.db you just built — an MCP client's working directory is usually not your project folder, so a relative path can silently resolve to the wrong (or a missing) file:

{
  "mcpServers": {
    "slnmap": {
      "command": "slnmap",
      "args": ["serve", "--db", "C:/path/to/your/project/slnmap.db"]
    }
  }
}

On macOS/Linux, use a POSIX absolute path instead, e.g. /home/you/project/slnmap.db.

Or register it from the command line:

claude mcp add slnmap -- slnmap serve --db C:/path/to/your/project/slnmap.db

Restart your MCP client after registering. Fully quit and relaunch it — starting a new conversation or reconnecting mid-session is not enough; a running session will not see the new tools until the client process restarts.

That's it. Ask your agent an architecture question and it will call Slnmap. (Run slnmap doctor first if anything looks off — see Troubleshooting.)

Related MCP server: sharplens-mcp

What you can ask

The server exposes fifteen read-only tools. Give them fully qualified names; results are capped and counts-first. (A note the tools also carry: an FQN does not reveal whether a member is an explicit interface implementation.)

Tool

Example question

find_symbol

"Find the IBasketService interface."

get_dependencies

"What does CartController.Index depend on?"

impact_analysis

"What breaks if I change IBasketService?"

get_architecture_overview

"Show me the projects and how they depend on each other."

find_usages

"Where is BasketService.GetBasket used?"

find_implementations

"Who implements IBasketService / overrides this virtual member?"

get_type_hierarchy

"Show the base and derived type tree for BaseEntity."

find_tests_for_symbol

"Which tests exercise BasketService.AddItemToBasket?"

get_project_dependencies

"How do the projects reference each other, and where is the coupling worst?"

find_circular_dependencies

"Are there dependency cycles between projects or namespaces?"

get_symbol_source

"Show me the actual source of IBasketService."

list_endpoints

"List every HTTP endpoint, or just the POSTs under /api/basket."

find_endpoint

"Which endpoint serves /api/basket/42/items, and which method handles it?"

find_orphan_calls

"Which frontend API calls don't hit any real endpoint?" (after slnmap link)

list_frontend_callsites

"List every frontend HTTP call site and what it links to." (after slnmap link)

For an interface (or interface member), impact_analysis follows both the interface's callers and its concrete implementations/overrides — so the answer includes code that only touches the interface, across projects, in files nobody has open.

HTTP endpoints are first-class graph nodes — from ASP.NET Core Minimal APIs (v0.7.0) and attribute-routed controllers (v0.8.0): each MapGet/MapPost/… registration and each [Route]/[HttpGet("…")] action appears as VERB /route/template linked to its handler method, so impact_analysis and find_usages on a handler surface the actual routes that break. Route templates are resolved statically — MapGroup prefixes, const patterns, the common CleanArchitecture registration conventions, class-level [Route] (including inherited ones and [controller]/[action] tokens), and controller base classes reached through packages (Ardalis.ApiEndpoints works out of the box). Anything that can't be resolved statically is counted and reported, never guessed — controllers routed conventionally (MapControllerRoute, no route attributes) are detected and disclosed rather than silently absent, and so are Razor Pages (PageModel-derived classes with OnGet/OnPost/… handlers — they route by file location, a different routing system analyze counts and notes rather than modeling). Blazor .razor markup is not analyzedanalyze detects and reports how many .razor files exist in the solution rather than silently excluding them from the document count; component-usage edges aren't modeled yet (#30 tracks that).

MCP tools reference

The exact parameter names, for clients that call the tools directly. Most tools take fqn — the symbol's fully qualified name — not symbol, name, or type; a wrong parameter name fails the call.

Tool

Parameters

Description

find_symbol

query (required), kind (optional)

Search symbols by name or FQN, case-insensitive substring; returns kind, FQN, and file for up to 20 matches.

get_architecture_overview

(none)

Projects, project-to-project dependencies, node/edge counts by kind, and top-level namespaces.

get_symbol_source

fqn (required), context_lines (optional, 0–20, default 5)

Print a symbol's source, read from its file at the declaration span.

find_usages

fqn (required)

Where a symbol is called or referenced — containing member, file, and line, up to 50.

get_dependencies

fqn (required), direction (optional: outgoing/incoming, default outgoing), depth (optional, 1–3, default 1)

A symbol's dependencies grouped by relationship kind (Calls, Implements, Inherits, References).

find_implementations

fqn (required)

Concrete types implementing an interface / deriving from a base, or members overriding a virtual/interface member.

get_type_hierarchy

fqn (required), direction (optional: up/down/both, default both), depth (optional, 1–10, default 5)

Base and/or derived type tree as an indented text tree.

get_project_dependencies

project (optional, default all)

Project-to-project reference map with cross-project reference counts and a hotspot line.

impact_analysis

fqn (required)

Every symbol that transitively depends on the given one (depth 5) — counts first, then nearest-first.

find_tests_for_symbol

fqn (required)

Test members that transitively exercise a symbol, grouped by project with file:line.

find_circular_dependencies

scope (optional: project/namespace, default project)

Dependency cycles reported as path chains, worst offenders first.

list_endpoints

verb (optional: GET/POST/PUT/DELETE/PATCH), prefix (optional route prefix, e.g. /api/vendors)

HTTP endpoints (Minimal APIs + attribute-routed controllers) grouped by project: VERB /route → handler — file:line; unresolved registrations and conventionally-routed controllers disclosed in trailing notes.

find_endpoint

route (required: a template or a concrete path), verb (optional)

Endpoints matching a route — case-insensitive, {param} holes bind concrete segments; a miss suggests near matches. After slnmap link, also lists its frontend callers.

find_orphan_calls

category (optional: no-match/verb-mismatch/verb-unknown)

Frontend call sites with no matching endpoint, grouped by exact reason — computed live, always current even if slnmap link hasn't run since the last change.

list_frontend_callsites

verb (optional), prefix (optional)

Every frontend HTTP call site with its live linking status — the endpoint(s) it hits, or why it doesn't.

A malformed call never returns an opaque error: failures come back as a normal result carrying a small JSON payload — status/code/message/hint plus the offending parameter and the valid parameter list — so an agent can self-correct (invalid_parameter/missing_parameter → fix the call; internal_error → retry). "Not found" is a normal prose answer with suggestions, never an error. Stack traces and file paths never appear in a payload.

CLI

slnmap analyze <solution>        # build or update the code graph (incremental on re-run)
slnmap analyze-ts <frontend>     # add TypeScript/React frontend HTTP call sites to the same graph
slnmap link                      # join frontend call sites to the C# endpoints they hit
slnmap watch <solution>          # analyze once, then keep a warm workspace and re-analyze on save
slnmap serve                     # serve the graph to MCP clients over stdio
slnmap status                    # show node/edge counts and when it was last analyzed
slnmap viz                       # export the graph as a self-contained interactive HTML file
slnmap doctor                    # check the environment can run Slnmap

These eight verbs are the whole CLI. Symbol, usage, and impact querying is MCP-only — there is no find/usages/impact command; connect an MCP client to slnmap serve to query the graph.

--db <path> selects the database file (default slnmap.db). -v/--verbose prints per-document progress on its own line per update — useful in an interactive terminal, but it floods piped or redirected output (logs, CI), so omit it there.

Frontend call sites (analyze-ts)

Requires Node.js 18+ — checked explicitly, with a clean error if missing, the same way the .NET SDK is checked for analyze. Nothing else to install by hand: the extractor itself (slnmap-ts on npm) is fetched automatically via npx the first time you run the verb.

slnmap analyze-ts path/to/your-frontend --db slnmap.db
Frontend:  187 call sites resolved, 9 unresolved (95.4% coverage)
Saved:     slnmap.db

Point it at a TypeScript/React project root (add --tsconfig if it isn't at the project root) and the same database analyze writes to gets two new node kinds: FrontendCallSite for every HTTP call the extractor could resolve to a route template — including through const-through- barrel re-exports and template literals with a mix of folded and runtime-only segments — and UnresolvedCallSite for every one it honestly can't, each labeled with one of seven specific reasons (including string-concatenation, for a +-built URL argument with a non-constant part, such as axios.get(base + '/users') — disclosed, never silently dropped) rather than silently dropped or guessed at. A fluent method chain (two HTTP-verb-named calls registered on one statement, e.g. an Express app.use(...).get(A).get(B) shape) is disambiguated per link, never collapsed. Ask your agent to find a frontend call site by route the same way it already finds C# symbols.

Known limits: a fully-constant +-built URL (every operand a literal or const, e.g. '/api' + '/users') still folds to a real route template, same as always — but the moment any operand isn't statically constant (a parameter, a mutable binding, a computed value), the whole call site is disclosed as string-concatenation rather than partially folded the way a template literal's holes are. Deliberate, not a gap: partial concatenation folding would need the same per-segment machinery template-literal holes already have, and this hasn't been built yet.

Cross-stack linking (link)

Joins every frontend call site to the C# Endpoint it actually hits — the reason both halves of the graph exist. Run it after analyze and analyze-ts:

slnmap analyze YourSolution.sln --db slnmap.db
slnmap analyze-ts path/to/your-frontend --db slnmap.db
slnmap link --db slnmap.db
Linked:    121/128 call sites (98 unique, 14 via precedence, 9 set-edge)
Disclosed: 7 (5 no match, 2 verb mismatch)

Matching is deterministic: exact verb equality (an honestly-unresolvable verb is disclosed, never guessed), and route-precedence tie-breaking only where it's actually knowable — a literal call site beats a parameterized sibling endpoint, the same way ASP.NET's own router would. A call site that can genuinely reach more than one endpoint (real runtime fan-out, or an ambiguity nothing can resolve statically) gets a truthful edge to every one of them, never a guessed single link. Everything that doesn't link is individually disclosed by exact reason — nothing is silently dropped.

Known limits: a BFF-style proxy call site whose own template starts with a dynamic hole rather than a literal path segment — e.g. a Next.js catch-all API route forwarder such as app/api/[...path]/route.ts producing a call-site template like {*}/Authentication/refresh — can't be correctly aligned by the linker's base-path-prefix concatenation, and shows up as a disclosed no-match rather than a link. This is infrastructure (a proxy forwarding whatever path it's given), not a real frontend→backend call site, so it's a correctly disclosed non-link, not a bug to fix.

Once linked, impact_analysis and find_usages continue straight through a C# handler into its frontend callers, with no separate query:

impact_analysis("BasketController.UpdateQuantities"):
  [Endpoint] PUT /api/basket/{id}/items @depth 1
  [FrontendCallSite] PUT src/services/basketService.ts:42:10 @depth 2

Change the handler, see the exact React call sites that break — one graph, zero guessing. Re-run slnmap link after analyze or analyze-ts changes the graph — a one-line note appears on both when the stored links may be stale.

Visualizing the graph

slnmap viz --output graph.html      # export the whole graph
slnmap viz --project YourProject    # export one project's subtree; others render as collapsed stubs

Opens as a single HTML file — double-click it, no server or internet connection required. It starts collapsed to one node per project; click a project, namespace, or class to drill into it. Like the rest of Slnmap, the export is self-contained: the graph library is embedded in the file, so nothing is fetched from a CDN and it works fully offline.

Updating

.NET tools do not update themselves, and Slnmap makes no network calls — so it will never nag you about (or check for) new versions. To update:

dotnet tool update -g Slnmap

To hear about releases, watch the GitHub repo (Watch → Custom → Releases); each release ships with notes in the changelog. After a major-version update, re-run slnmap analyze if the tool asks for it — release notes call out when a graph rebuild is needed.

Build from source

Slnmap is a standard .NET solution — clone, build, and test it with the SDK:

git clone https://github.com/EMahmoudNabil/slnmap.git
cd slnmap
dotnet build -c Release
dotnet test  -c Release

To run the CLI without installing the global tool:

dotnet run --project src/Slnmap.Cli -- analyze path/to/YourSolution.sln

Compatibility

Analyzes C# solutions targeting .NET 8 and .NET 9 (earlier targets are untested — feedback welcome); runs on Windows, macOS, and Linux; works with any MCP client (tested with Claude Code). analyze-ts additionally requires Node.js 18+ and analyzes TypeScript/React frontends (single, flat tsconfig.json setups field-verified; monorepo/project-references tsconfigs are not yet).

Privacy

100% local — and now you can verify it. Slnmap runs on your machine, reads your source with Roslyn, and writes a single local SQLite file. The MCP server reads only that local file. There is no telemetry, no cloud service, and analyze/watch/serve/viz/status/doctor make no network calls at all — analysis works fully offline. Now that the CLI and MCP server are open source, that claim is auditable: read the code, or watch the process — nothing leaves your machine.

One documented exception: analyze-ts fetches the slnmap-ts extractor from the public npm registry via npx (a local or globally-installed copy is used instead if one already exists — no network needed once you've installed it that way). No source code is ever sent anywhere; the only network activity is npm's own package download. Skip analyze-ts entirely to keep the tool fully offline.

Performance

Measured on eShopOnWeb (10 projects, net8.0), .NET 9 SDK, on a 2-core laptop. Each timing is the median of 3 runs; full methodology, machine spec, and pinned commit are in BENCHMARKS.md.

Metric

Result

Graph size

1,332 nodes / 3,014 edges

Cold analyze (10 projects)

~20.9 s (median of 3)

Re-analyze after a one-file change

~18.7 s (median of 3 — see note)

impact_analysis on IBasketService (29 dependents, last measured v0.5.0)

~240–290 ms (end-to-end MCP round-trip)

Numbers are for v0.6.0: fully-qualified type references (no using shortcut) now produce edges, and events are modeled as graph nodes (see the changelog) — the fully-qualified- reference fix accounts for nearly all of this release's edge growth (89 of 92 new edges) versus v0.5.0 (1,311 / 2,922 edges). Timings are flat within normal run-to-run noise; the analyzer's per-document work is otherwise unchanged. Full before/after detail, including the v0.5.0 and v0.3.0 baselines, is in BENCHMARKS.md.

To estimate your own solution's cold analyze time, scale by size rather than anchoring on any single number above: field measurements on real-world solutions (antivirus real-time protection on, no exclusions) come out at roughly 55–60 seconds per 1,000 analyzed documents. Treat it as approximate — hardware and antivirus overhead move it either way.

Incremental re-analysis. Re-analysis re-walks only the changed file and its dependents. A run-and-exit slnmap analyze still pays a full workspace load each time — so a re-run is about as fast as a cold run. slnmap watch (new in 0.10.0) pays that load once and then applies file saves to the warm workspace: on this same eShopOnWeb setup, a one-file semantic change re-analyzed in 0.93 s and saved in 0.17 s — about a second end to end, versus the ~19 s re-run above. On very large graphs (100k+ edges) the database save dominates instead (~7 s measured), so a change lands in a few seconds rather than minutes. Run slnmap serve beside it — the server reads the same file and survives the atomic swap mid-query, so your agent's answers stay fresh while you type. Note: the resident workspace holds compilations in memory — expect hundreds of MB on very large solutions.

Troubleshooting

Run slnmap doctor first — it checks the three things that actually block analysis and prints a fix for each:

$ slnmap doctor
[ok] .NET SDK: 1 SDK(s) installed; newest: 9.0.314 …
[ok] MSBuild workspace: Roslyn MSBuild workspace initialized …
[ok] Graph directory: Writable: /path/to/cwd
  • "No .NET SDKs are installed" / MSBuild fails to load projects. Slnmap analyzes via MSBuildWorkspace, which runs design-time builds using your installed .NET SDK. Install the SDK (not just the runtime) from https://dotnet.microsoft.com/download. On Windows, if projects still fail to load, install the Visual Studio Build Tools (or Visual Studio) so MSBuild and the targeting packs resolve.

  • Analysis reports warnings but finishes. That is expected and safe: a project that can't be loaded (e.g. a missing SDK or targeting pack) is reported as a warning and skipped — Slnmap indexes everything that did load rather than failing the whole run (a partial load). By default these are condensed into a single Warnings: N (M unique) summary line; run slnmap analyze --verbose for the full, grouped detail.

  • The first analysis of a large solution takes a while. Cold analysis compiles every project once; as a rough guide from field measurements, expect around 55–60 seconds per 1,000 analyzed documents (approximate). Re-runs are faster on graph work but still reload the workspace — see the performance note above. This is normal; the graph is cached in slnmap.db between runs.

  • Windows Defender (or other antivirus) slows analysis. Real-time protection scans every file Roslyn reads while compiling your solution. Adding an exclusion for your repository folder can speed analysis up, but changing exclusions requires local admin rights — corporate users without them may need an IT ticket. No exclusion is required for correctness: analysis completes fine without one, and the ~55–60 s per 1,000 documents guide above was measured with real-time protection on and no exclusions in place.

  • slnmap: command not found after install. Ensure the .NET global tools directory (~/.dotnet/tools) is on your PATH, then open a new shell.

How it works

Slnmap uses the Roslyn compiler platform to build a precise semantic graph of your solution — every type and member, and the relationships between them (calls, implementations, inheritance, references). The graph is stored locally and served to your AI agent or editor over MCP. Updates are incremental and crash-safe: an interrupted run never corrupts your existing graph.

License & support

Slnmap is open source under the MIT license.

The CLI and MCP server are MIT-licensed and will stay that way. Future hosted or team-oriented features may be commercial.

For questions or to report an issue, open a GitHub issue or contact hello@slnmap.dev. Contributions are welcome — see CONTRIBUTING.md.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    CodeMap is a Roslyn-powered MCP server that lets AI agents navigate C# codebases by symbol, call graph, and architectural fact, instead of brute-force reading thousands of lines of source code. One tool call. Precise answer. No context flood.
    18
    -
  • A
    license
    A
    quality
    A
    maintenance
    A Model Context Protocol (MCP) server providing 62 AI-optimized tools for .NET/C# semantic code analysis, navigation, refactoring, and code generation using Microsoft Roslyn. Built for AI coding agents - provides compiler-accurate code understanding that AI cannot infer from reading source files alone.
    62
    32
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    A local .NET MCP server for coding agents, providing persistent semantic indexing, typed-graph DI wiring resolution, reduced context, and compiler-backed verification for .NET projects.
    34
    Apache 2.0

Latest Blog Posts

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/EMahmoudNabil/slnmap'

If you have feedback or need assistance with the MCP directory API, please join our Discord server