Reading a 1,500-line C# file to find one method signature wastes tokens, time, and money. RoslynLens is an open-source MCP server that gives AI coding assistants like Claude Code direct access to Roslyn semantic analysis — so they can query your .NET codebase the way an IDE does, not the way grep does.
One MCP call. 30-150 tokens. No file reading required.
The problem: AI assistants are blind navigators
When Claude Code (or any AI assistant) works with a .NET codebase, it has two options to understand your code:
-
Read entire files — 500 to 2,000+ tokens per
.csfile, most of which is irrelevant noise - Grep and hope — text-based search that can't distinguish a type name from a variable, a declaration from a usage
Neither approach understands your code. They process text. They miss inheritance hierarchies, cross-project references, interface implementations, and semantic relationships.
The result? Wasted context window, missed connections, and wrong assumptions.
The solution: semantic queries via MCP
RoslynLens loads your .NET solution into a Roslyn MSBuildWorkspace and exposes 28 tools through the Model Context Protocol. Instead of reading files, the AI assistant sends focused queries and gets back structured, minimal responses.
Before RoslynLens — finding who calls OrderService.SubmitAsync:
1. Grep for "SubmitAsync" across 200 files -> 47 matches (including comments, strings, other methods)
2. Read 12 files to narrow down -> ~15,000 tokens consumed
3. Still not sure about indirect calls through interfaces
After RoslynLens — same task:
find_callers("OrderService.SubmitAsync")
-> 4 callers with file, line, containing method — 85 tokens
Quick start
Install as a global .NET tool:
dotnet tool install --global RoslynLens
Register with Claude Code:
claude mcp add --scope user --transport stdio roslyn-lens -- roslyn-lens
That's it. RoslynLens auto-discovers the nearest .sln or .slnx file (BFS, max 3 levels), loads it in the background, and starts serving queries over stdio.
You can also configure it via .mcp.json in your project root:
{
"mcpServers": {
"roslyn-lens": {
"type": "stdio",
"command": "roslyn-lens",
"args": []
}
}
}
28 tools at a glance
RoslynLens tools fall into six categories.
Symbol navigation
| Tool | What it does |
|---|---|
find_symbol |
Locate definitions by name — supports glob patterns (*Service, Get*User) |
find_references |
All usages of a symbol across the solution |
find_implementations |
Interface implementors and derived classes |
find_callers |
Direct callers of a method |
find_overrides |
Overrides of virtual/abstract methods |
find_dead_code |
Unused types, methods, properties — with filters for public members, entry points, project/file scope |
Inspection
| Tool | What it does |
|---|---|
get_public_api |
Public surface of a type — no file reading |
get_symbol_detail |
Full signature, parameters, return type, XML docs |
get_type_hierarchy |
Inheritance chain, interfaces, derived types |
get_project_graph |
Solution dependency tree (with projectFilter for large solutions) |
get_dependency_graph |
Method call chain visualization |
get_diagnostics |
Compiler warnings and errors |
get_test_coverage_map |
Heuristic test-to-code mapping |
Analysis
| Tool | What it does |
|---|---|
get_complexity_metrics |
Cyclomatic, cognitive complexity, nesting depth, LOC |
analyze_data_flow |
Variable assignments, reads, writes, captured variables |
analyze_control_flow |
Reachability, return points, exit points |
detect_antipatterns |
18 built-in detectors (see below) |
detect_circular_dependencies |
Project and type-level cycle detection |
detect_duplicates |
Structurally similar code via AST fingerprinting |
Compound tools (one call, multiple analyses)
| Tool | What it does |
|---|---|
analyze_method |
Signature + callers + dependencies + complexity in one call |
get_type_overview |
Public API + hierarchy + implementations + diagnostics |
get_file_overview |
Types + diagnostics + anti-patterns for a file |
Batch operations (multiple symbols, one call)
| Tool | What it does |
|---|---|
find_symbols_batch |
Resolve multiple symbol names at once |
get_public_api_batch |
Public API of multiple types at once |
get_symbol_detail_batch |
Details of multiple symbols at once |
External & specialized
| Tool | What it does |
|---|---|
resolve_external_source |
Resolve NuGet/framework source via SourceLink or decompilation |
get_module_depends_on |
[DependsOn] attribute graph for modular monoliths |
validate_conventions |
Convention violation checker |
18 anti-pattern detectors
RoslynLens doesn't just navigate code — it catches common .NET mistakes at query time. Detectors run on syntax trees with optional semantic model analysis, so they're fast and don't require a full compilation for basic checks.
General .NET
| ID | What it catches |
|---|---|
| AP001 |
async void methods (except event handlers) |
| AP002 | Sync-over-async: .Result, .Wait(), .GetAwaiter().GetResult()
|
| AP003 |
new HttpClient() instead of IHttpClientFactory
|
| AP004 |
DateTime.Now/UtcNow instead of TimeProvider
|
| AP005 |
catch (Exception) without re-throw |
| AP006 | String interpolation in log calls (structured logging violation) |
| AP007 |
#pragma warning disable without matching restore
|
| AP008 | Async methods missing CancellationToken parameter |
| AP009 | EF Core queries without .AsNoTracking()
|
Domain-specific
| ID | What it catches |
|---|---|
| GR-GUID |
Guid.NewGuid() instead of IGuidGenerator
|
| GR-SECRET | Hardcoded passwords, connection strings, API keys |
| GR-SYNC-EF |
SaveChanges() instead of SaveChangesAsync()
|
| GR-BADREQ |
TypedResults.BadRequest<string>() instead of Problem()
|
| GR-REGEX |
new Regex() instead of [GeneratedRegex]
|
| GR-SLEEP |
Thread.Sleep() in production code |
| GR-CONSOLE |
Console.Write/WriteLine instead of ILogger
|
| GR-CFGAWAIT | Missing ConfigureAwait(false) in library code |
| GR-DTO | Classes/records with *Dto suffix (naming convention violation) |
Adding a new detector is straightforward. For simple invocation matches (Foo.Bar()), extend InvocationDetectorBase. For new Foo() patterns, extend ObjectCreationDetectorBase. For complex logic, implement IAntiPatternDetector directly.
Deep dive: key v1.1 features
Fuzzy FQN resolution
AI assistants often send imprecise symbol names. Instead of failing, RoslynLens uses a three-tier fallback strategy:
- Exact match — case-insensitive, highest priority
-
Partial namespace —
MyServiceresolves toMyApp.Services.MyService - Levenshtein distance — typo tolerance (1-2 edits depending on name length)
When multiple candidates match, the response includes a disambiguation list with fully qualified names, file paths, and line numbers — so the AI can pick the right one without another round-trip.
Duplicate code detection via AST fingerprinting
Text-based diff tools miss renamed-but-identical logic. RoslynLens normalizes syntax trees by replacing identifiers with ID, literals with LIT, and types with TYPE, then computes a SHA256 fingerprint of the normalized structure. Methods with identical fingerprints are structurally equivalent — regardless of variable names.
detect_duplicates(projectFilter: "MyProject", minStatements: 5)
-> 3 duplicate groups, each with locations + similarity score
Complexity metrics (SonarSource model)
get_complexity_metrics computes four metrics per method:
-
Cyclomatic complexity — counts decision points (if, while, for, switch, catch,
&&,||,??) - Cognitive complexity — SonarSource model with nesting penalties
- Nesting depth — maximum block nesting level
- Logical LOC — excluding blanks and comments
Supports method, type, and project scope with a configurable threshold to surface only the worst hotspots.
Data flow and control flow analysis
Built on Roslyn's SemanticModel.AnalyzeDataFlow() and AnalyzeControlFlow():
- Data flow: variables declared, read inside, written inside, always assigned, captured by closures
- Control flow: start/end reachability, return statement count, exit points
External source resolution
When the AI needs to understand a NuGet package API, RoslynLens follows a resolution hierarchy:
- SourceLink — check if the symbol has source locations in the solution
- Decompilation — fallback via ICSharpCode.Decompiler (truncated to 60 lines for token efficiency)
No more telling the AI to "just check the NuGet documentation."
Architecture: designed for large solutions
RoslynLens is built to handle real-world .NET solutions with 50+ projects:
-
Background loading — the solution loads asynchronously via a
BackgroundService. Tools return a "loading" status until the workspace is ready, then serve queries instantly. - Lazy compilation with LRU cache — solutions with many projects compile on-demand. A configurable LRU cache (default: 20 compilations) keeps frequently accessed projects hot.
-
File watchers —
.cschanges trigger incremental text updates on the Roslyn workspace..csprojchanges trigger a full reload. No restart needed. - Logs to stderr — stdout is reserved for JSON-RPC (MCP protocol). All diagnostic output goes to stderr.
src/RoslynLens/
├── Program.cs # Host + MCP stdio transport
├── SolutionDiscovery.cs # BFS .sln/.slnx auto-discovery
├── WorkspaceManager.cs # MSBuildWorkspace, LRU compilation cache
├── WorkspaceInitializer.cs # Background solution loading
├── SymbolResolver.cs # Cross-project resolution + fuzzy FQN
├── ComplexityAnalyzer.cs # Cyclomatic/cognitive complexity
├── DuplicateCodeDetector.cs # AST fingerprinting
├── ExternalSourceResolver.cs # SourceLink + decompilation
├── Tools/ # 28 MCP tool implementations
├── Analyzers/ # 18 anti-pattern detectors
└── Responses/ # Token-optimized DTOs
Configuration
Four environment variables for runtime tuning:
| Variable | Default | Purpose |
|---|---|---|
ROSLYN_LENS_TIMEOUT_SECONDS |
30 | Operation timeout |
ROSLYN_LENS_MAX_RESULTS |
100 | Maximum results per query |
ROSLYN_LENS_CACHE_SIZE |
20 | LRU compilation cache size |
ROSLYN_LENS_LOG_LEVEL |
Information | Log verbosity |
Real-world impact: token savings
Here's a comparison on a 35-project solution (~180,000 lines of C#):
| Task | Without RoslynLens | With RoslynLens | Savings |
|---|---|---|---|
Find all implementations of IRepository
|
Read 8 files (~12,000 tokens) |
find_implementations (95 tokens) |
99% |
Understand OrderService API |
Read full file (1,800 tokens) |
get_public_api (120 tokens) |
93% |
| Check for anti-patterns in a module | Read 15 files (~22,000 tokens) |
detect_antipatterns (200 tokens) |
99% |
| Analyze complexity hotspots | Manual review across projects |
get_complexity_metrics (150 tokens) |
N/A (not feasible manually) |
The compound tools push this further. analyze_method replaces what would be 4-5 separate tool calls (or 4-5 file reads) with a single query.
Key takeaways
- RoslynLens replaces file reading with semantic queries — 30-150 tokens instead of 500-2,000+ per file
- 28 tools cover navigation, analysis, batch operations, and compound queries
- 18 anti-pattern detectors catch common .NET mistakes at query time
- Fuzzy FQN resolution handles imprecise AI queries gracefully
- AST-based duplicate detection finds renamed-but-identical logic
- Works on large solutions — lazy compilation, LRU cache, background loading
Get started
# Install
dotnet tool install --global RoslynLens
# Register with Claude Code
claude mcp add --scope user --transport stdio roslyn-lens -- roslyn-lens
# Done. Navigate to any .NET project and Claude Code can query it.
GitHub: jfmeyers/roslyn-lens | NuGet: RoslynLens | License: Apache-2.0
Built by JF Meyers. Contributions welcome.
Top comments (0)