First, let’s address the obvious question.
Why am I calling this 14.2 instead of just Part 15?
Well, teaching how to build a production-grade MCP server definitely isn’t going to fit into a single article, is it?
So instead of cramming everything into one massive post, we’re turning this into a 4-part mini-series.
Yes, I know how that sounds.
A series… inside another series?
Umm. Yeah.
Anyway, here’s how this is going to work:
14.3 ReviewMyCode MCP Server: Rules & Extensibility
14.4 ReviewMyCode MCP Server: Deployment, Docker, and Testing
Schrödinger's AI is your invitation to look inside. Right now, AI feels like a mystery , wired like a brain, yet running on pure math.
Each article is a new layer of the box. We start with the first spark of an idea and move all the way to the models reshaping everything we thought we knew .
Explore the entire series Schrodingers-AI
I’d suggest cloning the code from my repository: review-my-code-mcp
It’ll make it easier to follow along with the project as we build it. That said, it’s not strictly required since we’ll be building everything step by step throughout the series.

Part 14.3: ReviewMyCode MCP Server: Rules & Extensibility
By now, you understand:
How MCP connects clients to your server
How
ReviewAnalyzerruns rules and collects findingsHow
ReviewScorercalculates quality scores
But you haven't seen how rules are actually built. Let's do that in this article.
A "rule" in this system is a simple check: given source code, does it violate some pattern? If yes, return a finding. If no, return null.
We'll cover:
ICodeRule: The interface all rules implement
IRuleGroupProvider: How to organize rules by category
Concrete rule types: Token detection, regex, custom delegates
Building a provider: Grouping related rules
All eight categories: What each checks
Extending the system: How to add new rules without touching existing code
The Rule Interface: ICodeRule
Every rule implements one simple interface.
Create Rules/Abstractions/ICodeRule.cs:
using McpCodeReviewServer.Models;
namespace McpCodeReviewServer.Rules.Abstractions;
public interface ICodeRule
{
ReviewIssue? Evaluate(RuleContext context);
}Single responsibility: One rule checks one thing
Immutable: Takes a
RuleContext, returns a finding or nullOne finding per rule: Each rule creates only one finding. If a rule matches many times, only the first match is reported.
The Provider Interface: IRuleGroupProvider
Rules are organized into groups (categories). Each group has a provider.
Create Rules/Abstractions/IRuleGroupProvider.cs:
namespace McpCodeReviewServer.Rules.Abstractions;
public interface IRuleGroupProvider
{
string Category { get; }
IReadOnlyCollection<ICodeRule> BuildRules();
}Category: is name, stable identifier for grouping (e.g., "async correctness", "security")
BuildRules: Factory method that returns configured rule instances
Called once at startup: Providers build their rules during DI registration, not on every analysis
Concrete Rule Type 1: ContainsTokenRule
The simplest rule type: check if a specific token appears in code.
Create Rules/Abstractions/ContainsTokenRule.cs:
using McpCodeReviewServer.Models;
namespace McpCodeReviewServer.Rules.Abstractions;
public sealed class ContainsTokenRule : ICodeRule
{
private readonly string _token;
private readonly string _severity;
private readonly string _category;
private readonly string _description;
private readonly string _fix;
public ContainsTokenRule(
string token,
string severity,
string category,
string description,
string fix)
{
_token = token;
_severity = severity;
_category = category;
_description = description;
_fix = fix;
}
public ReviewIssue? Evaluate(RuleContext context)
{
// Search for token in each line
var line = FindFirstLine(context.Lines, _token);
if (line is null)
{
// Token not found
return null;
}
// Token found, emit issue with line number
return new ReviewIssue(
_severity,
_category,
line, // Line number (1-indexed)
_description,
_fix
);
}
private static int? FindFirstLine(IReadOnlyList<string> lines, string token)
{
for (var i = 0; i < lines.Count; i++)
{
if (lines[i].Contains(token, StringComparison.Ordinal))
{
return i + 1; // Line numbers are 1-indexed for humans
}
}
return null; // Not found
}
}
Example Usage
// Create a rule to detect async void methods
var asyncVoidRule = new ContainsTokenRule(
token: "async void ",
severity: "critical",
category: "async correctness",
description: "Avoid async void methods; exceptions can be unobserved.",
fix: "Return Task instead. Example: public async Task MyMethodAsync() { }"
);
// Evaluate it
var context = new RuleContext("public async void BadMethod() { }", ...);
var issue = asyncVoidRule.Evaluate(context);
// Result: ReviewIssue with severity="critical", line=1, description=..., fix=...When to use ContainsTokenRule:
Simple substring detection
Tokens that don't need complex pattern matching
Examples: "async void ", ".Result", ".Wait("
Concrete Rule Type 2: RegexRule
For more complex patterns, use regex.
Create Rules/Abstractions/RegexRule.cs:
using McpCodeReviewServer.Models;
using System.Text.RegularExpressions;
namespace McpCodeReviewServer.Rules.Abstractions;
public sealed class RegexRule : ICodeRule
{
private readonly Regex _regex;
private readonly string _severity;
private readonly string _category;
private readonly string _description;
private readonly string _fix;
public RegexRule(
Regex regex,
string severity,
string category,
string description,
string fix)
{
_regex = regex;
_severity = severity;
_category = category;
_description = description;
_fix = fix;
}
public ReviewIssue? Evaluate(RuleContext context)
{
// Search for pattern in each line
for (var i = 0; i < context.Lines.Count; i++)
{
if (_regex.IsMatch(context.Lines[i]))
{
// Pattern matched on this line
return new ReviewIssue(
_severity,
_category,
i + 1, // Line number (1-indexed)
_description,
_fix
);
}
}
return null; // No match
}
}Example Usage
// Create a rule to detect hardcoded API keys (basic example)
var apiKeyRule = new RegexRule(
regex: new Regex(@"['\"]sk_[a-zA-Z0-9_]{20,}['\"]", RegexOptions.IgnoreCase),
severity: "critical",
category: "security",
description: "Hardcoded API key detected.",
fix: "Remove hardcoded keys. Use environment variables or secure configuration."
);
var context = new RuleContext(@"var token = ""sk_prod_abc123def456""; // Bad!", ...);
var issue = apiKeyRule.Evaluate(context);
// Result: ReviewIssue with severity="critical", line=1, description=...
Join the conversation! Your thoughts help the community grow.