Serena MCP is mandatory for C# code. First call
mcp__serena__initial_instructionsto load the Serena tool manual, then use the Serena tools for ALL.csreading / searching / navigation / creation / editing — prefer symbol navigation (get_symbols_overview/find_symbol/find_referencing_symbols) over whole-file reads. NativeEdit/Writeon.csis hook-blocked (the TS/React frontend uses the native tools).
Building MCP servers in C# ({{ProductName}})
For exposing tools/capabilities over the Model Context Protocol from the .NET backend (your agentic pipeline
on Microsoft Agent Framework is the natural consumer/producer). C# (.cs) edits via Serena.
⚠ Approval required.
ModelContextProtocol(core) andModelContextProtocol.AspNetCore(HTTP) plus theMicrosoft.McpServer.ProjectTemplatestemplate are NuGet dependencies — need Dan's explicit approval.
Create
- Prereqs:
dotnet --version(need .NET 10+);dotnet new install Microsoft.McpServer.ProjectTemplates. - Transport: stdio (local CLI/IDE plugins, default) vs HTTP (cloud/web, multiple clients, containers).
- Scaffold: stdio →
dotnet new mcpserver -n <Name>; HTTP →dotnet new web -n <Name>+dotnet add package ModelContextProtocol.AspNetCore. - Tools —
[McpServerToolType]on the class,[McpServerTool]+[Description]on the method, a[Description]on EVERY parameter, async tools take aCancellationToken:
For DI/non-static tools, use an explicit constructor assigning[McpServerToolType] public static class ProjectTools { [McpServerTool, Description("Summarize a project.")] public static async Task<string> Summarize( [Description("The project id.")] Guid projectId, CancellationToken cancellationToken = default) => /* ... */; }readonlyfields — NOT a primary constructor (the upstream Microsoft sample usesclass ApiTools(HttpClient http, ILogger log); we don't). - Prompts/Resources:
[McpServerPromptType]/[McpServerPrompt](returnChatMessage);[McpServerResourceType]/[McpServerResource(UriTemplate="config://app", MimeType="application/json")]. - Program.cs:
- stdio:
Host.CreateApplicationBuilder(args);builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);— logging to stdout corrupts JSON-RPC (the #1 error);AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly(); - HTTP:
WebApplication.CreateBuilder(args);AddMcpServer().WithHttpTransport().WithToolsFromAssembly();app.MapMcp(); app.MapGet("/health", () => "ok");
- stdio:
- Verify:
dotnet build/dotnet run.
Debug / Test / Publish (lifecycle)
- Debug: run locally, validate via the MCP Inspector UI and Copilot Agent Mode; remember stdio logs go to stderr.
- Test: unit-test tools + integration-test via the MCP client SDK. (The upstream skill uses Moq + FluentAssertions — both are libraries needing approval, and must match whatever test stack Dan approves for
apps/api/tests.) - Publish: NuGet packaging for stdio servers; Docker containerization for HTTP servers → deploy to our (EU) host's container runtime (NOT Azure Container Apps / App Service — strip those). MCP Registry publish is optional.
Ours
- Explicit constructors, no primary ctors;
.csvia Serena; async +CancellationTokenthroughout. - Tools that touch tenant data must carry the tenancy invariant; failures should flow through our
Result<T>/error model where the tool contract allows. - Pair with the
otel-instrumentationskill —transport-configsupports OpenTelemetry; trace tool calls. - No library/template added without Dan's approval.