← Back to list

Part 4 — The Compliance MCP Server: Deterministic Rules, Zero LLM

Building Multi-Agent Systems with .NET 10 Blog Series

Fuji Nguyen in Scrum and Coke · 2026-05-17 11:14 · 26 claps · 4.5 min read paywalled
#artificial-intelligence #dotnet #programming #software-engineering #technology
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents AI · AI · General 💻 · Programming

Part 4 — The Compliance MCP Server: Deterministic Rules, Zero LLM

*Building Multi-Agent Systems with .NET 10 Blog Series*

Part 3 built Hr.Jobs.Mcp — nine tools that agents use to search positions, generate job descriptions, and list hiring organizations. Tools that create content are only useful if that content meets regulatory standards. This part adds the second server, Hr.Compliance.Mcp — a rule engine that checks OPM compliance in deterministic C# with zero LLM calls.

The most important architectural decision in the system is not which model to use or how to structure the agents. It is deciding where the LLM should not be involved at all.

The OPM compliance server (Hr.Compliance.Mcp) has zero LLM calls. Every compliance decision — pass, warning, fail — is made by deterministic C# code. The language model appears only in the orchestrator layer, where the OPMCompliance specialist agent reads the compliance results and explains them to the user in plain language.

Why Deterministic Rules, Not LLM Judgment

Consider the alternative: you ask an LLM “does this position’s pay grade comply with OPM standards?” The model might say “the grade range appears reasonable for an IT Management position.” It might also say “GS-16 seems high but could be justified by the role’s scope.” Both answers sound plausible. Neither is correct — there is no GS-16, and the model has no reliable knowledge of current OPM qualification standards.

OPM compliance is binary. Either the grade is in the allowed range for the series or it is not. Either the announcement period is at least 5 business days or it is not. Either the qualifications text references the advertised grade level or it does not. There is no “could be justified” — these are regulatory requirements.

The rule of thumb: if a lawyer or auditor could evaluate it from a checklist, make it deterministic C# code. Reserve the LLM for tasks that require language understanding, synthesis, or judgment — explaining what is wrong and how to fix it.

The OpmRuleEngine: 7 Rules, Zero Dependencies

OpmRuleEngine has one dependency: OpmStandardsRepository. No IChatClient. No HTTP client. No EF Core. The rule engine receives a Position object and evaluates it.

// src/Hr.Compliance.Mcp/Rules/OpmRuleEngine.cs
public sealed class OpmRuleEngine(OpmStandardsRepository standards)
{
    public ComplianceReport RunAll(Position position)
    {
        var results = new List<ComplianceResult>
        {
            CheckRequiredFields(position),
            CheckPayGrade(position),
            CheckPayGradeAlignment(position),
            CheckApplicationPeriod(position),
            CheckQualificationsText(position),
            CheckSecurityClearanceDisclosure(position),
            CheckWhoMayApply(position),
        };
        var overall = results.Any(r => r.Status == ComplianceStatus.Fail) ? ComplianceStatus.Fail
            : results.Any(r => r.Status == ComplianceStatus.Warning)      ? ComplianceStatus.Warning
            : ComplianceStatus.Pass;
        return new ComplianceReport(position.Id, position.Title,
            position.OccupationalSeries, overall, results);
    }
}

The overall status is the worst result across all rules. One failure makes the report a failure.

The 7 Rules Explained

Rule 1 — RequiredFields

Eight mandatory fields must be non-empty: Title, OccupationalSeries, PayGradeMin, PayGradeMax, DutyLocation, WhoMayApply, Duties, Qualifications.

Rule 2 — PayGradeRange

Both grades must parse as PLAN-NN format (e.g., GS-12) and PayGradeMin must be ≤ PayGradeMax.

Rule 3 — PayGradeAlignment

Calls OpmStandardsRepository.GetBySeries() to retrieve the OPM qualification standard for the position’s series, then checks that both grades fall within the allowed range. An IT Specialist (series 2210) posted at GS-16 fails because the series allows only GS-05 through GS-15. The failure message includes the OPM standard URL.

if (invalidGrades.Count > 0)
    return ComplianceResult.Fail("PayGradeAlignment",
        $"Grade(s) {string.Join(", ", invalidGrades)} are outside the allowed range " +
        $"for series {p.OccupationalSeries} ({standard.SeriesTitle}). " +
        $"Allowed: GS-{standard.AllowedGradeNumbers.First():D2} " +
        $"to GS-{standard.AllowedGradeNumbers.Last():D2}. " +
        $"Standard: {standard.QualificationStandardUrl}");

Rule 4 — ApplicationPeriod

Open positions must have at least 5 business days between open date and close date (excluding weekends).

Rule 5 — QualificationsGradeReference

The qualifications text must explicitly mention the advertised grade level (e.g., “GS-12”). OPM qualification standards are written grade-by-grade; if the text does not reference a specific grade, it cannot be OPM-compliant by definition.

Rule 6 — SecurityClearanceDisclosure

If a clearance is required (anything other than NotRequired), the duties or qualifications text must mention it. A position that requires Secret clearance but does not disclose this in the announcement creates legal exposure.

Rule 7 — WhoMayApply

The applicant pool must match one of the recognized federal categories: “US Citizens”, “Current Federal Employees”, “Status Candidates”, “Merit Promotion”, “All Sources”, “Open to the Public”, or “Veterans”. A substring match is used so “Open to all US Citizens” passes even though it is not an exact match.

OpmStandardsRepository: Static Reference Data for 8 Series

The repository is a dictionary of OpmStandard value objects, one per series. Seven of the eight series allow GS-05 through GS-15. Safety (0018) allows only GS-05 through GS-12. The GetBySeries lookup normalizes the series code so "201", "0201", and " 0201 " all resolve to the HR Management series.

ComplianceResult: A Value Object Pipeline

Each rule returns a ComplianceResult — an immutable value object:

public record ComplianceResult(string RuleName, ComplianceStatus Status, string Message)
{
    public static ComplianceResult Pass(string rule) =>
        new(rule, ComplianceStatus.Pass, "Passed.");
    public static ComplianceResult Warn(string rule, string message) =>
        new(rule, ComplianceStatus.Warning, message);
    public static ComplianceResult Fail(string rule, string message) =>
        new(rule, ComplianceStatus.Fail, message);
}

The MCP tool serializes the report to a formatted string so the LLM specialist agent can read and explain it:

OPM Compliance Report
Position: IT Specialist (ID: 42)
Series: 2210 | Overall: FAIL
RequiredFields         PASS    Passed.
PayGradeRange          PASS    Passed.
PayGradeAlignment      FAIL    Grade GS-16 is outside the allowed range for series 2210
                               Allowed: GS-05 to GS-15.
ApplicationPeriod      PASS    Passed.
QualificationsGrade    WARN    Qualifications text does not reference the advertised grade.
SecurityClearance      PASS    Passed.
WhoMayApply            PASS    Passed.

Testing with MCP Inspector

Start the compliance server:

dotnet run --project src/Hr.Compliance.Mcp

Open MCP Inspector:

npx @modelcontextprotocol/inspector http://localhost:5200/compliance

1. List known series — call ListOPMSeries. You get all 8 series with their allowed grade ranges and OPM standard URLs.

2. Check a specific series — call GetOPMStandard with series: "2210". You get the IT Management standard including allowed grades GS-05 to GS-15.

3. Full compliance check — call RunFullComplianceCheck with positionId: 1. You get the full 7-rule report. A position seeded from real USAJobs data will typically pass most rules but may warn on QualificationsGradeReference.

4. Validate a pay grade independently — call ValidatePayGrade with series: "2210", minGrade: "GS-07", maxGrade: "GS-09". A targeted grade alignment check without fetching a position from the database.

Testing at this level confirms the rule logic is correct before the compliance agent ever sees a result.

What Comes Next

Both MCP servers are running and tested. But WriteJobDescription returns a string that disappears when the conversation ends. Part 5 introduces the JobAnnouncement entity and lifecycle — Draft, CompliancePassed, ComplianceFailed, Published — so every generated draft persists across sessions with a full audit trail.

Part 3 — Building the HR Data MCP Server | Part 5 — Persisting AI Artifacts: The JobAnnouncement Lifecycle

View the repository


메타데이터
post_id
6fa051cb3f8d
slug
part-4-the-compliance-mcp-server-deterministic-rules-zero-llm-6fa051cb3f8d
url
https://medium.com/scrum-and-coke/part-4-the-compliance-mcp-server-deterministic-rules-zero-llm-6fa051cb3f8d
canonical_url
https://medium.com/scrum-and-coke/part-4-the-compliance-mcp-server-deterministic-rules-zero-llm-6fa051cb3f8d
author_url
https://medium.com/@fuji-nguyen
status
ok
fetched_at
2026-06-12 18:14:10