Checks .NET solutions against a fluent C# architecture spec and explains the rules to agents.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
LoadBearing is a .NET tool that renders one C# architecture spec to two targets: enforcement and agent context.
The architecture of a long-lived codebase is real: layers, boundaries, rules. It is also unenforced: it lives in a few heads, no build step checks it, and diagrams drift. Nothing fails when a change crosses a boundary, least of all when a coding agent wrote the change: fast, plausible, and blind to which walls are load-bearing. Architecture-as-code is LoadBearing's answer: the rules become one C# spec, and the spec becomes every surface on this page.
AGENTS.md block, per-directory rule cards, and MCP query tools for coding agents.Write your architecture once. Use it everywhere.
A rule is one statement:
That is the whole rule: an ID, a posture (Enforce), a constraint, a reason, a fix. It is committed in the clean-architecture example, and CI holds check green against the codebase it governs.
LoadBearing is pre-alpha and under construction; Status holds the current inventory.
Each target below consumes the same reified model, and every violation report carries the rule ID, the generated rule sentence, the reason, the fix, and the exact file:line.
| Target | What it is |
|---|---|
loadbearing check | one pass-or-fail verdict for the command line and CI |
check --sarif | that verdict as SARIF 2.1.0, for code scanning |
| xUnit adapter | every rule an individually named test |
loadbearing render | the managed AGENTS.md block and per-directory rule cards |
loadbearing mcp | arch_check, arch_status, arch_explain, arch_context, and arch_graph, plus a derive_spec prompt |
| agent hook | check after each edit; a red rule blocks it, report on stderr |
The adapter's failure text is byte-identical to the CLI's: the two share one renderer, and a product test pins them equal. The managed block plus loadbearing explain are also the generated architecture documentation, written for agents first and readable by people; the gate under The prose it generates keeps it current.
The compiler is the source of truth for your code. LoadBearing is the source of truth for your architecture.
LoadBearing governs itself. Eighteen rules over this repository's real code, across eight declared layers, live in LoadBearingArchSpec.cs, and every fence from here down to This page is tested is that spec, or this solution under it, on one surface after another. Take the rule that keeps the CLI off stdout β host is the layer the CLI project's namespace defines:
Nothing in the build system stops the CLI writing to Console, and the MCP server on the other side of that stdout speaks JSON-RPC over it. This rule is the only thing standing between those two facts.
loadbearing render derives the rule sentence from the constraint, carries the Because across verbatim, and writes the result into the managed block of this repository's committed AGENTS.md, the convention file Claude Code, Codex, Cursor, and Copilot read:
Nobody wrote that sentence, and nobody can let it go stale: SelfSpecTests.AgentsMd_IsCurrent composes the block in process and asserts the committed file already equals it. Its sibling ScopedCards_AreCurrent holds the whole class the same way, every per-directory card this repository commits, and also fails on a card that no rule placement produced, so one orphaned by a spec change cannot stay behind being read. The prose an agent reads is provably the spec the build enforces. Agents that query rather than read get the same model over MCP (loadbearing mcp).
Suppose an agent adds a progress printer to the CLI so a slow solution load stops looking hung, and reaches for Console.WriteLine. The PostToolUse hook in hooks/ runs check on the edit, the rule goes red, and the wrapper exits 2, which is how a Claude Code hook blocks, with the report on the agent's stderr:
That stanza is one rule's worth of the twenty-rule board the wrapper hands back whole. It carries the four things an agent needs to act without asking a human: the rule ID, the reason, the fix, and the exact file:line of every offending write. The agent routes the output through the command's console instead, the next check is green, and the block clears in the same turn, before the change lands.
The same spec runs inside a test project, where a team already looks. ArchRuleTests<TSpec> from the xUnit adapter turns each rule into an individually named test, and this repository's whole adapter dogfood is one class declaration:
Each test's display name is its rule ID, so a broken rule is named in the run summary rather than buried in an assertion message, and a Migrate rule's grandfathered sites keep their test green while the ratchet holds. CI runs it as a step of its own, "Self-spec as named xUnit tests (one test per rule)", whose log carries one line per rule ID.
check --sarif writes the same verdict as SARIF 2.1.0, which is what GitHub code scanning reads. This repository's one Migrate rule is retiring direct System.Environment reads out of the MCP infrastructure, over a counted baseline:
Its four baselined sites keep the rule green at the command line and still reach code scanning, as note-level results marked suppressed, so the burndown is visible to anyone reviewing without ever failing a build. One result object from a fresh run:
A rule that is genuinely red lands the same shape at error level, with "baselineState": "new" and no suppressions array, so a reviewer can tell house debt from a fresh breach at a glance. Reproduce the file from a checkout:
CI's self-check job runs that check on every push and uploads the SARIF it writes.
The same rule from check --json, the document arch_check returns over MCP, with the ratchet as counters rather than suppressions:
loadbearing graph surveys the codebase a spec is written against: projects and their references, namespaces and their sizes, every external dependency by root. Five of the twenty project lines for this solution:
The references: (none) on the first line is layering/core-no-roslyn seen from the other side: the rule forbids the reified model from reaching for the Roslyn project or the compiler packages behind it, and the survey shows it reaching for no other project in the solution. The lines not shown here are the test project, the rule pack, and the fixture projects the tests check against.
render --diagram <path> draws that same survey as a Mermaid diagram inside a committed file's managed block. Pointed at this repository and scoped to its six shipping projects, it writes ARCHITECTURE.md:
A solid arrow is a reference some type actually makes; a dotted arrow is a project reference that is declared and never exercised, which the text survey leaves you to work out by reading two of its sections against each other. There are no dotted arrows above, which is itself the report: no project here declares a reference it never uses. Nobody drew that diagram, and nobody can let it rot: SelfSpecTests.ArchitectureMd_IsCurrent composes the block in process and asserts the committed file already equals it. A hand-drawn architecture diagram is the artifact that rots first; this one is held to the code the same way the rules are.
That fence is drawn from what the code does. The same block carries a second one, drawn from what the spec forbids:
Nothing in that drawing is a shape somebody chose for it. A bare --x is a reference this spec forbids, the labelled arrows are the verbs that need naming, the dotted one is the single Migrate rule with its existing sites baselined, and the box inside Extraction is the quarantined scope with its sanctioned surface doubled. Model, Checking and Rendering sit inside Core because Core's globs contain theirs. The legend is generated with the rest, one row per construct this particular drawing uses.
The line under the fence is the honest part. A diagram can only draw a rule whose subject and targets are places, and most of this spec's rules are about shapes, names, attributes and members instead. Those rules are listed by ID rather than quietly dropped, so the picture is never mistaken for the whole law.
The excerpts above are under gate. RootReadmeQuoteSyncTests holds each quoted excerpt to the committed file it was cut from, every line in order as a verbatim substring: change the spec and leave this page alone, and the suite goes red. ReadmeAnchorGateTests resolves the file:line anchors inside the quoted reports against the sources they name. The five fences that are captured tool output with no committed counterpart, the hook report and the SARIF object and the check document and the graph survey and the Framework check, are registered as such and held to their place on the page, so an exemption cannot quietly go dead.
The page is the tool's output, and the CI badge at the top is what keeps it that way.
NetArchTest and ArchUnitNET run architecture rules inside your unit tests, and they are good at it. LoadBearing moves the rules out of test code into one spec (architecture-as-code rather than architecture tests) and renders every surface above from it.
| Tool | What you write | Where it runs |
|---|---|---|
| NetArchTest | fluent assertions in test methods | your test runner |
| ArchUnitNET | ArchUnit-style rules in test classes | your test runner |
| LoadBearing | one spec in its own project | every target above |
The grammar comes from surveying that prior art, and GRAMMAR.md records each divergence. Constraints negate in the verb (MustNotReference), following ArchUnitNET. If you know ArchUnit's FreezingArchRule: what freezing does (accept a rule's current violations as a baseline) is Migrate with its counted baseline here. Quarantine contains a scope; it does not accept the scope's violations.
Because is mandatory. A rule without one is an invalid spec: check refuses to run it and reports every spec error in one pass. Even the predicate escape hatch, Must(condition, description:), does not compile without its description. Every reason ships to your agents in the rendered context, and in the Interchange example each of the twelve rules' Because cites the learn.microsoft.com page it enforces. Nine of those twelve come from a shared rule pack, which is an ordinary class library of static methods: the pack owns the citation, the spec picks the posture. That pack ships in this repository as a working example rather than as a package to install, because a pack is a pattern you own rather than a registry you depend on.
Every rule carries one.
| Posture | What it is | What fails |
|---|---|---|
Enforce | the law | every violation, even ones predating the rule |
Migrate | a ratchet over a counted baseline | new violations; baselined sites stay quiet |
Quarantine | containment for a scope | a new reference into the scope |
Enforce failing violations that predate it is what Migrate exists for: loadbearing baseline records a rule's current violations, new ones fail from the next commit, and the baseline only shrinks. At zero, the tool suggests promoting the rule to Enforce. A Quarantine scope also carries a diff-aware tripwire: with check --diff-base <ref>, a change set that touches the scope itself draws a warning.
LoadBearing is built for long-lived, business-critical .NET codebases: systems too important to rewrite, with an architecture that is real but written down nowhere. Start where the code is:
Migrate.loadbearing baseline records every current violation on a counted, committed baseline.Enforce.The first draft is usually an agent's work, not a blank file. loadbearing mcp ships one prompt, derive_spec, the recipe that walks an agent from a solution with no spec to a reviewed proposal: survey the estate with arch_graph, scaffold the spec project, draft every hypothesis as a rule, and let check count the violations that assign each rule its posture. The tool does not infer the architecture and the agent does not ratify it: every proposed rule crosses a curation gate where you accept, edit, or drop it, and recording the baseline stays a human command. The Meridian adoption walkthrough is that recipe replayed on a committed example codebase, one real command at a time.
The tool runs on .NET 10. The codebase it checks does not have to, and neither does the spec that governs it.
A spec project can target net48 and compile at that framework's default language level, C# 7.3. It references the same netstandard2.0 Zphil.LoadBearing package every other spec does, and the CLI loads the built DLL in an isolated load context. A typeof() anchor works from there while the anchored type's own closure stays inside netstandard2.0; past that line, including .NET Framework types with no counterpart on .NET, a namespace pattern is the anchor, and it needs no assembly load at all.
Old project files load too. A non-SDK-style Framework project, the kind in the 2003 MSBuild XML namespace with explicit <Reference> items and a hand-maintained AssemblyInfo.cs, loads through the .NET Framework build host Roslyn ships and reports at file:line like anything else:
And the build server can stay where it is. check --binlog replays a binary log from a real build, including one produced by .NET Framework MSBuild.exe, so the machine that builds needs no .NET 10; only the machine that analyses does. Replaying that log and opening the workspace directly produce byte-identical output, which is what makes the replay a shortcut rather than a lesser reading.
The last two both need Windows, because that is where the Framework build host and MSBuild.exe come from. What the tool looks for is a vswhere-discoverable Visual Studio or Build Tools install carrying MSBuild\Current\Bin\MSBuild.exe; where several are installed it prefers VS 2019 or 2022, and when it has to take something outside that pair it names what it took, on stderr, beside any load failure. Set LOADBEARING_VS_INSTALL_PATH to an install root β the parent of MSBuild\Current\Bin β to choose one yourself. A net48 spec project carries no such requirement and builds anywhere.
Six worked examples in examples/ share one fictional freight-forwarding company. Four are solutions: CI builds each one, holds check green against the committed tree, and re-renders every managed block under examples/ to prove a zero diff. The other two walk a flow with captured output. Three are whole codebases:
Three go deeper on one surface each:
The CLI ships as a .NET global tool. The checker never builds the code it checks, so restore and build the solution first; a stale build gives stale verdicts:
The machine running it needs a .NET 10 SDK: commands that load a solution (check, render, status, graph, baseline, mcp) do so through MSBuildWorkspace via MSBuildLocator, and a runtime-only environment cannot host that load. The codebase under check has no version requirement of its own: LoadBearing never builds or retargets it, and it can target .NET Framework 4.8 or anything newer. The spec project compiles against one package, Zphil.LoadBearing, which is netstandard2.0.
A project that fails to load is treated as a wrong model rather than a smaller one: check, baseline, render, graph and status all exit 2 and say which projects failed, and --allow-workspace-diagnostics opts into the partial model. The xUnit adapter answers the same way in test dress: one named test fails carrying the load failures, every rule case skips, and an AllowWorkspaceDiagnostics override opts in.
The command is loadbearing. Four lockstep-versioned packages make up a release:
| Package | What it is |
|---|---|
Zphil.LoadBearing.Cli | The loadbearing global tool: check, render, explain, status, graph, baseline, and the MCP server (loadbearing mcp). |
Zphil.LoadBearing | The spec contract, zero dependencies; the one package a spec project references. Reference the version loadbearing --version prints. |
Zphil.LoadBearing.Xunit | The xUnit adapter: every rule as an individually named test. |
Zphil.LoadBearing.Roslyn | Extraction/workspace infrastructure; a dependency of the above, not for direct reference. |
An MCP client launches the same tool with the mcp verb; the server speaks stdio. The
.mcp.json shape:
For Claude Code, one command writes that same entry into the project's .mcp.json:
The solution argument is optional, and most repositories should still pass it. Without it the
server reads LOADBEARING_SOLUTION_PATH, and when that is unset too it walks up from its
working directory to the first ancestor holding exactly one .sln, .slnf or .slnx. That
resolves nothing where the solution sits under src/, and refuses as ambiguous where several
sit side by side, which between them covers most real repositories. A solution file passed as
the argument beats both. When it cannot bind, the server starts anyway and every tool call
returns the reason, naming any solution it saw one level down; the failure is readable in the
client rather than arriving as a server that would not start. However the server is launched,
the rule from Installing still applies: restore and build the solution first; the checker
never builds, and a stale build gives stale verdicts.
dnx launches the server straight from nuget.org without the global install, and it ships
with the same .NET 10 SDK the tool already requires; the -- hands everything after it to
the tool:
This is how MCP-registry clients run the server; note the mcp subcommand.
Pre-alpha, under construction. What this page shows is what exists: the reified model, the fluent builder, Roslyn extraction, the CLI verbs, the SARIF writer, the xUnit adapter, the MCP server, and the render pipeline, with all three postures evaluating. The spec excerpts above are quoted from this repository's own committed spec, the tool output is captured from runs against this solution, and CI uploads that check --sarif run to code scanning. The fluent surface can still move; GRAMMAR.md is its spec.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/loadbearing)<a href="https://allmcps.com/mcp/loadbearing"><img src="https://allmcps.com/api/badge/loadbearing?style=directory" alt="Loadbearing on AllMCPs" /></a>