The mistake most people make when building AI agents is starting with the agent. You end up with a prompt that calls nothing, proves nothing, and impresses nobody. For AgentReview (my ongoing series proving that Python-default AI patterns translate cleanly into C#/.NET) I flipped that order and started with the tools for the AI agents. Phase 1 is entirely about building the MCP server, planting real bugs, and verifying the tools catch them before any agent logic enters the picture.
That constraint was intentional because a tool-less agent is just a prompt with ambition but a MCP server that drops into Claude Code is demoable on day one. I wanted something I could point at and say "this works" before I wrote a single line of orchestration.
What Got Built and Why the Transport Choice Matters
The server uses the official MCP C# SDK over stdio transport. That choice sounds boring until you hit the one rule that makes or breaks it which is that the logs go to stderr, never stdout. The MCP protocol rides on stdout, so a single stray Console.WriteLine in the wrong place corrupts the stream and your client sees garbage. I moved all diagnostic output to stderr early and didn't touch it again.
Two tools got implemented in Phase 1. analyze_csharp runs Roslyn compiler diagnostics alongside NetAnalyzers CA rules against a provided code snippet. run_semgrep runs polyglot security patterns using Semgrep's registry rulesets. Both tools accept a code string, do their analysis, and return structured findings. This has a simple surface area, which is the point but the complexity lives in what the scanners catch and where they fall short.
To prove the tools work, I planted four specific bugs and called each one live from inside Claude Code. They were an unused variable triggering CS0219, unreachable code triggering CS0162, an IDisposable leak for CA2000, and a SQL injection built by string concatenation for Semgrep's csharp-sqli rule. All four issues were flagged with 12 passing unit tests green. Then I quickly recorded a Demo GIF for the project readme. When setting up the readme I set a list of requirements for each phase and set the standard that no checkbox in the README gets ticked until the feature works end-to-end.
Where the Scanners Actually Break Down
The limitations are what motivate building an agent on top of the tools rather than just shipping the tools alone.
CA2000 only catches abandoned locals. So if you allocate an IDisposable, never assign it to anything, and let it drift, CA2000 fires, but if that object escapes through a return statement or gets thrown in an exception path, the rule goes quiet. I had to shape my eval fixture carefully so it hit the detectable pattern. That's not a knock on Roslyn but it's a precise description of what the rule covers. Any agent that treats CA2000 as a catch-all disposable-leak detector will miss real bugs in production code.
Semgrep won't find a hardcoded password in a connection string. No standard registry pack, including p/secrets, flagged a hardcoded password sitting inside a connection string literal which is a real blind spot. The p/secrets pack looks for patterns like API key assignments and bearer tokens, not credentials embedded in ADO.NET or Entity Framework connection strings. If your security posture relies solely on Semgrep to catch hardcoded credentials, you're in trouble.
This is exactly why the eventual security agent in AgentReview layers LLM reasoning on top of the scanners rather than treating scanner output as ground truth. The scanners are fast, deterministic, and cheap to run which makes them the right first pass, but the LLM needs to look at what the scanners didn't flag and reason about it.
Homebrew hit a /usr/local permissions wall installing Semgrep. I had to find an official alternative which was a pinned pip user install. Not interesting on its own, but I'm calling it out because these are the things that eat an hour if you haven't hit them before. Going to my terminal, typing in pip install --user semgrep==1.x.x, and moving on was the right call. This is a known friction point with Homebrew-managed Python environments, and it didn't deserve more time than that.
The Build Log as Proof
One pattern I want to establish across the whole AgentReview series is that the build log doubles as the proof. Every claim I make in these build logs (CA2000 fires on this shape of leak, Semgrep misses this shape of credential) was verified by running the tool against a real fixture and reading the actual output.
That matters more than it might sound because a lot of AI tooling content right now is aspirational. Someone describes what an agent could do, what a pipeline should handle. I want AgentReview to be a different kind of reference that says here's what I built, here's the exact bug I planted, here's the output the tool returned, and here's what it missed and why.
The scanner blind spots I found in Phase 1 are in the architecture requirement for Phase 2. The security agent is an agent because scanners and LLM reasoning solve different parts of the problem. Phase 1 just gave me the receipts to prove that clearly.
What's Next
Phase 2 introduces the agent layer on top of these tools which is an orchestration loop that calls analyze_csharp and run_semgrep, reads their output, and then applies LLM reasoning to the gaps. The CA2000 escape-path leak and the connection string credential are both on the fixture list for Phase 2, specifically because Phase 1 proved the scanners won't catch them unassisted.
If you're building something similar in .NET and want to compare notes on the MCP SDK setup or the Roslyn integration, shoot me an email, especially about the stdio transport hygiene piece. It's important to get that right early and it saves you a frustrating debugging session later.