Architecture
This solution is loosely based on Clean Architecture. It’s by no means perfect, so we prefer to call it Lean Mean Clean Architecture. The inspiration is Jason Taylor’s Clean Architecture template, with some ideas taken further and others scaled back.
Dependencies point inwards
Section titled “Dependencies point inwards”Application sits in the middle and depends on nothing. Presentation and Infrastructure sit around the outside, and both depend on Application. The outside can change without the middle noticing.
- Presentation exposes the Application layer as an MCP server. Each tool, resource and prompt sends a command or query through Mediator and maps the result into what the protocol expects, and two filters turn Application errors into tool errors and protocol errors.
- Application owns the domain and the business logic. Commands and queries are validated and handled here, and each domain type has its own repository interface. Application doesn’t care what fulfils those contracts, as long as something does.
- Infrastructure owns the data. It implements the repository interfaces and maps its own data models onto the Application entities, so persistence details don’t leak upwards.
There’s a little CQRS in the style of the commands and queries, but reads and writes use the same database underneath. CQRS-curious, if you like.
The one rule we bend
Section titled “The one rule we bend”In textbook Clean Architecture, Presentation never references Infrastructure. Here it does, so it can wire up dependency injection without a fourth project whose only job is to call two methods.
To keep that from turning into a free-for-all, every class in Infrastructure is internal. Presentation can call AddInfrastructure and nothing else. If you try to new up a repository from a tool, the compiler will politely decline.
What happens on a tool call
Section titled “What happens on a tool call”- The MCP C# SDK reads the JSON-RPC request, finds the tool by name, binds its arguments from the JSON and calls the static method.
- It sends a command or query through Mediator, with the request’s cancellation token.
- The
ValidationBehaviourruns every FluentValidation validator for the request. Any failure stops here with aValidationException. - The handler calls a repository interface.
- The repository in Infrastructure runs the query against SQLite with Entity Framework Core and maps the results back to Application entities.
- The tool maps the entities to its result record, the SDK serialises it as text and structured content, and the response goes out as a server-sent event.