DEV Community

MCP server for C# development with real NuGet reflection

Prashant Patil on April 13, 2026

sharp-mcp: Roslyn-Powered C# Analysis, Real NuGet DLL Reflection, and Safe Live File Editing for Claude, On Your Machine via MC...
Collapse
 
ticktockbent profile image
Wes • Edited

The MetadataLoadContext approach for inspecting real binaries instead of relying on training data is a clean solution to the stale-signature problem. Pulling actual type info from the exact package version in a .csproj eliminates a whole class of errors.

But how much of the hallucination problem does this actually cover? In my experience, the more painful failures are not "the model invented a method that doesn't exist" but "the model called a real method with the wrong assumptions about its behavior." EF Core's SaveChangesAsync exists in every version, but the implicit transaction semantics, change tracker behavior, and concurrency token handling have all shifted in ways that correct signatures alone won't reveal. Reflection tells you the shape of an API but not the contract behind it. XML doc comments help, but they tend to describe parameters, not gotchas.

Do you see a path toward surfacing behavioral documentation or version-specific usage patterns alongside the signatures, or does that fall outside what reflection can reasonably provide?

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Hey Wes — this is a sharp and fair observation, and the EF Core example you picked is exactly
the right stress test.

The honest answer: reflection gives you the shape of an API, not the contract behind it. For
SaveChangesAsync, that means two clean signatures — but nothing about implicit transaction
wrapping, what acceptAllChangesOnSuccess actually does to tracked entity state, the MARS/savepoint
incompatibility, or how DbUpdateConcurrencyException behaves across versions. You're right that
those are the painful failures in practice.

What sharp-mcp is really doing is solving the prerequisite problem. An LLM with web search can
reason well about EF Core's behavioral nuances — Microsoft's docs are rich and current. But if
it's calling a signature that was removed or changed between 6.0 and 8.0, the correct behavioral
reasoning doesn't matter because the code won't compile. Real signatures first, then behavioral
context from training data or live search.

Your point about XML doc comments is noted as a concrete near-term improvement — the DLL
metadata does include them for most well-maintained packages, and surfacing them alongside
signatures would close part of the gap without needing a separate source. Beyond that, pairing
reflection output with a targeted web search against official docs is probably the more complete
path, and that's something I'm actively thinking about for the next iteration.

Good callout. This is exactly the kind of feedback that improves it.

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Hey Wes — quick update: I actually shipped get_member_xml_doc since your comment. It pulls the full XML docs for any member — summary, params, returns, remarks, exceptions — parsed directly from the XML file inside the .nupkg at load time, zero extra network cost. Closes part of the gap you described. The behavioral contract problem you raised is still real, but at least the documented gotchas are now surfaced.

Collapse
 
automate-archit profile image
Archit Mittal

This is a great use of reflection — hallucinated NuGet APIs are easily the #1 reason I lose trust in AI-generated C# code. One thing worth considering: caching the reflection output keyed by (package, version) so repeat queries don't pay the assembly-loading cost every time. Package assemblies rarely change within a version, so a simple on-disk JSON cache can 10x response time for repeat lookups.

Also curious whether you surface XML doc comments alongside the member signatures — those carry a lot of the "intended usage" context that raw reflection misses.

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Hey Archit — both points are already handled, and the caching is more aggressive than an on-disk JSON approach.
On caching: The entire reflection pipeline is Redis-backed with double-checked locking. The key format is {packageId}@{version}@{targetFramework} — e.g. Microsoft.EntityFrameworkCore@8.0.0@net10.0 — so hits are exact to your pinned version . The flow on every call:

Check Redis → hit → return immediately, zero assembly loading
Miss → acquire a per-key SemaphoreSlim, check Redis again (prevents thundering herd on cold start), then drop into MetadataLoadContext
Reflection completes → serialize to JSON → StringSet with a 7-day TTL

Assembly loading cost is paid exactly once per (package, version, framework) triple, ever. Every subsequent call is a Redis string read.
On XML doc comments: The .xml doc file ships inside the .nupkg alongside the DLL and is available after the extraction step — so technically surfacing , , and is within reach. It's intentionally excluded for now though. Those comment blocks can be verbose, and the priority here is keeping token cost per tool call low — the whole NuGet exploration workflow is designed to be progressive and narrow, not a dump. The tradeoff is signatures without behavioral prose, which is a real gap, but the alternative is ballooning every get_type_surface response significantly. Something to revisit once there's a clean way to make it opt-in.

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Hey Archit — quick update: I actually shipped get_member_xml_doc . It pulls the full XML docs for any member — summary, params, returns, remarks, exceptions.

Collapse
 
vishal_kulkarni_1994 profile image
Vishal Kulkarni • Edited

Been using this daily for a few weeks now. No more pasting entire files into Claude just to ask about one method it pulls exactly what it needs on its own. The answers are actually informative too, you can understand what the code is doing instead of getting generic guesses. And the edit tool has been superb, handles big files without any issues which has been a huge time saver.

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Thanks, Vishal — means a lot!

Collapse
 
itskondrat profile image
Mykola Kondratiuk

write access over live files is where I would push back on safety. local just means you cannot blame a vendor when it goes wrong. had to add explicit audit logging before I trusted mine anywhere near production code.

Collapse
 
prashant_patil_9e62d3fa8a profile image
Prashant Patil

Hey Mykola — the safety isn't just "trust the user" — it's structural.
Every write goes through ResolveAndGuard first: path traversal blocked at the resolver level, then every path segment checked against a hardcoded BlockedDirectories set (.git, bin, obj, .vs, node_modules, .ssh, backups, logs), then the filename matched against BlockedPatterns (secret, password, token, credential, etc.). There's also an extension allowlist — only source, project, doc, and safe config file types can be touched. Concurrent writes to the same file are serialized via per-path SemaphoreSlim. None of this is configurable — it's enforced at the service layer.
That said, the primary audit trail I'm leaning on intentionally is git. Every change Claude makes is a diff — visible immediately, revertible instantly. That's a mechanism that's already universally trusted, and I'd rather build on it than duplicate it with a parallel logging system.
Audit logging as an explicit opt-in is on the list as the write surface matures. Writes are functional but deliberately conservative — Roslyn analysis and NuGet reflection are the stable core, and I'm building write tooling out carefully. And when all of it comes together — real signatures from your actual binaries, surgical Roslyn-aware file edits, and live build feedback in the same loop — it makes Claude a genuinely capable C# assistant rather than a best-guess code generator.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

ok yeah, hardcoded blocklist at the resolver beats anything configurable. was pushing back on the concept not the impl - this is the right pattern.