DEV Community

Prashant Patil
Prashant Patil

Posted on

Six Problems. Six Decisions. How sharp-mcp Actually Works Under the Hood.

Two months ago I published my first article about sharp-mcp — a local MCP server that gives Claude real Roslyn-powered access to your C# codebase. That article introduced the what. This one is the why behind every decision.

I want to be direct about something first: I've been using sharp-mcp as my primary coding workflow for two months on real production work. Not demos, not toy projects — actual backend .NET development at my job. It has changed how I work completely. Every architectural decision I'm about to describe came from either hitting a real wall or refusing to accept a tradeoff I knew was wrong.

GitHub: https://github.com/patilprashant6792-official/sharp-mcp

💬 Follow the build and share feedback in GitHub Discussions

The starting point: focus

Claude Code, GitHub Copilot, Cursor — they're all built for every language, every ecosystem. That generality has a cost. When you're doing C# backend work, you're paying token overhead for abstractions built for Python, JavaScript, Ruby. I wanted something that only knows C#, only knows .NET, and uses that focus to do the job better than anything general-purpose ever could.

That decision — narrow focus over broad compatibility — is what every other decision flows from.


Problem 1: Direct file reading was the obvious first move — and it was broken

I started where everyone starts: read the file, pass it to the model. It worked. It was also impractically slow for any real codebase. Search latency was high, large files burned context fast, and it touched the real filesystem on every single request.

A 500-line service class costs ~2,000 tokens raw. Load ten files across a microservices solution and you've burned your entire context budget before writing a single line. That's not a tool, that's a handicap.

Direct file reading wasn't viable at scale — and that failure made the solution obvious: parse the structure once, cache it, and serve from memory.


Problem 2: Roslyn was the right answer, but it needed a home

Roslyn gives you a full AST — classes, methods, line spans, dependencies, everything structured. Instead of handing Claude a 500-line file, you hand it the API surface: class name, constructor dependencies, method signatures with exact line numbers. The model gets exactly what it needs to reason and nothing it doesn't.

But parsing on every request defeats the purpose entirely. The data needed to live somewhere fast.

Redis was the obvious choice. If you're a .NET backend developer, you already have Redis. It's not an exotic dependency — it's infrastructure you trust in production. The decision was: parse once with Roslyn on startup, serialize the AST metadata into Redis, serve every subsequent call in milliseconds with zero disk I/O.

Second call for any file: a Redis read. That's it.


Problem 3: NuGet hallucinations — the one nobody else is solving

This was the most painful problem to hit and the most interesting one to solve.

AI models generate correct-looking C# that doesn't compile because they don't know what's actually inside the NuGet packages you're using. System.Text.Json changed nullable handling between 6.0 and 8.0. EF Core changed DbContext configuration between 7.0 and 8.0. IgnoreNullValues was deprecated mid-lifecycle. The model confidently generates code against APIs that no longer exist in your version.

Documentation is incomplete. Training data is stale. The model guesses — and guesses wrong.

The fix: stop guessing entirely. Decode the binary.

MetadataLoadContext downloads the real .nupkg, resolves transitive dependencies, loads the DLL into an isolated context, reads the actual IL, and returns real signatures — real method names, real parameter types, real generics. No docs. No training data. The model gets the same ground truth the compiler uses.

Frontier LLMs can web search documentation for behavioral context whenever they need it. What they cannot do is know the exact method signatures in your specific package version. That's the gap. That's what this closes.

Token cost difference in practice:

What you're doing Raw docs dump sharp-mcp
Explore a namespace ~6,000 tokens ~250 tokens
Explore a class ~2,000 tokens ~400 tokens
Fetch one method ~2,000 tokens ~120 tokens

Over a full implementation session this compounds into hours of additional useful context.


Problem 4: The cache needs to stay warm

A cached Roslyn analysis is only useful if it reflects the current state of the file. This is the consistency problem that kills most caching approaches — you get speed, but you trade accuracy.

The solution: a FileSystemWatcher architecture that listens for file changes and automatically invalidates or updates the relevant cache entries. Every .cs file edit you make in Visual Studio propagates to the cache within 300ms — a debounce window that handles the burst of events VS fires on a single save.

Delete events evict the key. Rename events evict the old key and index the new path. A background indexer runs on startup and every 60 minutes to catch anything the watcher missed.

The result: global code search runs against warm cache data within seconds, without touching the real codebase. Claude always sees your code as it exists on disk right now — never stale. This is what makes long feature implementation sessions practical.


Problem 5: Claude.ai needs a bridge to localhost

An LLM running over the web can't reach your local machine directly. ngrok provides the SSE tunnel — one command, one URL, paste it into Claude.ai Settings → Connectors, and Claude discovers all 23 tools automatically.

It's a real dependency and I won't dress it up. A VS Code extension is on the roadmap to eliminate it. But for now it's one command and the rest of the setup is dotnet run.


Problem 6: The UI that removes the last friction point

One thing I refused to build was a tool that required editing JSON config files or restarting a server every time you added a project.

There's a built-in project management UI at /config.html. You point it at a solution folder. That's it. sharp-mcp indexes everything — classes, methods, dependencies, file sizes — and the LLM immediately has full structural awareness of that codebase.

Configuration UI of sharp-mcp

You point it at a solution folder. That's it. sharp-mcp indexes everything — classes, methods, dependencies, file sizes — and the LLM immediately has full structural awareness of that codebase. Each project is independently enabled, re-indexable on demand, and deletable without touching any config file. And yes — sharp-mcp manages its own source code through the same UI. The tool is its own first user.


The three principles everything was built around

Looking back at all six decisions, they all trace back to three things:

  • Speed — Redis cache + file watcher keeping it warm. No cold starts after the first indexing pass.
  • Token efficiency — every tool description tells the model when not to call it. Batch modes, size hints, "last resort" labels — that's not documentation, it's prompt engineering baked into the architecture. The LLM should get exactly what it needs to know. Nothing more.
  • CorrectnessMetadataLoadContext means the model works with real signatures, not approximations. There is no training cutoff for your NuGet packages.

The system prompt — two months of refinement

Here's the part that doesn't get talked about enough: tools alone don't make an agent. Judgment does.

After two months of daily use on real production code, I've published the exact system prompt I use at prompts/CODING_SYSTEM_PROMPT.md in the repo.

It's split into two halves:

Part A — Engineering philosophy. Eleven principles: clarity before action, solve the unstated problem, atomic decomposition, multi-perspective validation. These aren't LLM-specific instructions. They're the principles any senior engineer applies before touching a keyboard. The prompt makes them explicit so the model doesn't skip them under time pressure.

Part B — Common sense production rules. Thirteen domains: error handling, validation, resource management, logging, concurrency, API design, database patterns. One rule from the third-party libraries section is worth quoting:

Always web search for latest official documentation before using any library. Verify the exact installed version. Validate method signatures and APIs for that specific version. Never assume behavior based on outdated knowledge.

That rule exists because of exactly the NuGet hallucination problem sharp-mcp was built to solve. Even with real signatures from MetadataLoadContext, a model that doesn't verify assumptions before writing code will still find ways to be wrong. The prompt and the tools reinforce each other.

The third section maps the 23 tools to a strict decision tree — which tool to call first, when never to call read_file_content on a large C# file, why you always run execute_dotnet_command before marking anything complete, why you batch all edit_lines patches in a single call.

This prompt is why the experience feels like pair programming with a senior .NET engineer rather than autocomplete. And it works with any MCP-capable LLM — not just Claude.


What this does not replace

This is not inline autocomplete. It doesn't suggest the next line as you type.

What it replaces is the reasoning session — when you open a chat to understand a codebase, plan a refactor, check what breaks if you change a method signature, look up how a dependency actually works, or implement a feature that spans multiple files. Those sessions are where context limits, hallucinated APIs, and cloud data exposure all cause real damage.

That's the scope this was built for. And after two months of using it daily on production .NET backend work, I can say with confidence: it's the most productive I've ever been writing C#.


If you read the first article and thought "sounds interesting but I want to understand how it actually works" — this was written for you. Questions welcome in the comments.

— Prashant

Top comments (0)