MCP Server Architecture & Source Generators
In traditional monolithic or microservice architectures, exposing operational endpoints to Large Language Models (LLMs) requires extensive glue code: maintaining custom JSON Schemas, writing JSON-RPC deserializers, manually extracting documentation, and wiring custom model validation.
BitzOrcas.Modern introduces BitzOrcas.Mcp.SourceGenerator, a Roslyn-based analyzer that inspects CQRS messages annotated with [GenerateMcpTool] at build time. It extracts XML documentation comments and primary constructor parameter types to emit standard IMcpToolPartition registration code. The entire workflow eliminates runtime reflection, supporting Native AOT compilation, sub-millisecond execution, and low-memory startups.
1. Compile-Time Declaration: [GenerateMcpTool]
Developers annotate application-level Mediator Command or Query records with [GenerateMcpTool] and write standard C# XML documentation comments:
using BitzOrcas.Application.Commands;using BitzOrcas.Domain.Results;using BitzOrcas.Mcp.Attributes;
namespace BitzOrcas.Modules.Litigation.Application.Commands.Cases;
/// <summary>/// Creates a new litigation case file and initializes the matter tracking number./// </summary>/// <param name="CaseTitle">The full legal title of the litigation case.</param>/// <param name="CaseType">The case classification token, such as CivilLitigation or CommercialArbitration.</param>/// <param name="ClaimAmount">The monetary claim amount in standard currency, which must be strictly greater than zero.</param>/// <param name="DefendantName">The legal name of the primary defendant or responding party.</param>[GenerateMcpTool("create_litigation_case", "Creates a new litigation case file, generates the matter number, and binds lead attorneys.")]public sealed record CreateLitigationCaseCommand( string CaseTitle, string CaseType, decimal ClaimAmount, string DefendantName): ICommand<Result<string>>;Physical Rules for Parameter Derivation
- Primary Constructor Parameters as Inputs: The generator only exposes primary constructor parameters to the LLM. Get-only calculated properties, delegated services, and internal metadata inside the record body remain completely hidden;
- XML Doc Comments Mapped to Descriptions: Parameter tags (
<param name="parameterName">) become propertydescriptionfields in the generated JSON Schema, providing the prompt context guiding LLM parameter selection; - Type Constraints and Nullability: Non-nullable types are automatically registered in the JSON Schema
requiredarray; numeric types receive strictnumberorintegerformatting; - Stable Tool Naming: Tool names follow lowercase snake_case conventions (e.g.,
create_litigation_case), ensuring compatibility across OpenAI, Anthropic, and open-weight model tooling routers.
2. Emitted Source Output (Roslyn Emit)
The Roslyn generator emits a strongly typed tool partition class and dependency injection extension for each module during compilation:
// <auto-generated/>#nullable enable
namespace BitzOrcas.Modules.Litigation.Generated;
/// <summary>/// Compile-time generated MCP tool partition for the Litigation module./// </summary>public sealed class LitigationMcpToolPartition : global::BitzOrcas.Mcp.Abstractions.IMcpToolPartition{ public string ModuleName => "Litigation";
public global::System.Collections.Generic.IReadOnlyList<global::BitzOrcas.Mcp.Abstractions.McpToolDefinition> GetTools() { return new global::BitzOrcas.Mcp.Abstractions.McpToolDefinition[] { new global::BitzOrcas.Mcp.Abstractions.McpToolDefinition( Name: "create_litigation_case", Description: "Creates a new litigation case file, generates the matter number, and binds lead attorneys.", InputSchemaJson: """ { "type": "object", "properties": { "CaseTitle": { "type": "string", "description": "The full legal title of the litigation case." }, "CaseType": { "type": "string", "description": "The case classification token, such as CivilLitigation or CommercialArbitration." }, "ClaimAmount": { "type": "number", "description": "The monetary claim amount in standard currency, which must be strictly greater than zero." }, "DefendantName": { "type": "string", "description": "The legal name of the primary defendant or responding party." } }, "required": ["CaseTitle", "CaseType", "ClaimAmount", "DefendantName"] } """, ExecuteAsync: async (jsonElement, serviceProvider, cancellationToken) => { var mediator = global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions .GetRequiredService<global::BitzOrcas.Application.Mediator.IMediator>(serviceProvider);
var command = global::System.Text.Json.JsonSerializer.Deserialize<global::BitzOrcas.Modules.Litigation.Application.Commands.Cases.CreateLitigationCaseCommand>( jsonElement.GetRawText(), new global::System.Text.Json.JsonSerializerOptions(global::System.Text.Json.JsonSerializerDefaults.Web));
if (command is null) { return global::BitzOrcas.Mcp.Abstractions.McpToolResult.Fail("Payload JSON deserialization failed."); }
var result = await mediator.SendAsync(command, cancellationToken); if (result.IsSuccess) { return global::BitzOrcas.Mcp.Abstractions.McpToolResult.Success(result.Value); }
return global::BitzOrcas.Mcp.Abstractions.McpToolResult.Fail(result.Error.Message); } ) }; }}3. Host Composition & Endpoint Exposure
Registering the MCP protocol layer within src/Hosts/BitzOrcas.Api requires standard minimal service configuration:
3.1 Service Registration (Program.cs)
using BitzOrcas.Infrastructure.Mcp;
var builder = WebApplication.CreateBuilder(args);
// 1. Register core MCP transport and partition aggregatorbuilder.Services.AddBitzOrcasMcpProtocol();
// 2. Register generated tool partitions from business modulesbuilder.Services.AddLitigationMcpTools();
var app = builder.Build();3.2 Endpoint Mapping
// Map MCP standard endpoint (defaults to /mcp)app.MapBitzOrcasMcp(McpProtocolDependencyInjection.DefaultRoutePattern);
app.Run();BitzOrcas.Api listens on /mcp using the Streamable HTTP / Server-Sent Events (SSE) transport protocol. Clients querying tools/list immediately receive the live, strongly typed catalog.
4. Related Architecture Decisions & Deep Dives
- Practical Guide: Cursor and Claude Desktop Integration Guide
- Security Context: Agent Governance, Tenancy, and Human-in-the-Loop Approval
- Architecture ADR: ADR 0103: Source Generators Replacing Reflection