creating-dotnet-mcp-servers
Skill199 starsChanged 5 days agoApache-2.0View on GitHub
Use when building Model Context Protocol (MCP) servers in .NET, configuring tools, transports (SSE/stdio), JSON serialization for AOT, or testing MCP endpoints
In this repository5 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- generate-microcks-openapi-samplesSkill
- migrating-prompts-to-skillsSkill
- setup-husky-dotnetSkill
- skill-creatorSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 24
Use this skill when:
- Very specific: line 79
Critical: Add ALL tool return types to [JsonSerializable] attributes.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Creating .NET MCP Servers
Build production-ready Model Context Protocol (MCP) servers in .NET with AOT compatibility.
Core principle: MCP servers expose tools via standardized protocol using attribute-based registration and explicit JSON serialization contexts for AOT.
When to Use
graph TD
A[Need to build server?] -->|Yes| B[Using .NET?]
B -->|Yes| C[MCP Protocol?]
C -->|Yes| D[✅ Use this skill]
C -->|No| E[❌ Use REST/gRPC guide]
B -->|No| F[❌ See Python/TS MCP docs]
A -->|No| G[❌ Skip]Use this skill when:
- Building new MCP server in .NET
- Configuring SSE or stdio transport
- Enabling AOT compilation
- Need testing or error handling patterns
Not for: Non-.NET MCP, client-side integration, generic APIs
Quick Reference
| Task | Implementation |
| Install packages | dotnet add package ModelContextProtocol + ModelContextProtocol.AspNetCore |
| Mark tool class | [McpServerToolType] |
| Mark tool method | [McpServerTool(Name = "ToolName"), Description("...")] |
| Parameter description | [Description("param description")] string param |
| Register server | services.AddMcpServer().WithHttpTransport().WithTools<T>() |
| Map endpoints | app.MapMcp() (exposes /sse) |
| AOT serialization | Create JsonSerializerContext with [JsonSerializable] |
| Test transport | Use SseClientTransport + McpClientFactory |
Basic Setup
1. Install Packages
dotnet add package ModelContextProtocol --version 0.3.0-preview.1
dotnet add package ModelContextProtocol.AspNetCore --version 0.3.0-preview.1
# For testing (optional)
dotnet add package ModelContextProtocol.Client --version 0.3.0-preview.12. Enable AOT
Project configuration:
<PropertyGroup>
<PublishAot>true</PublishAot>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>JSON serialization context:
[JsonSerializable(typeof(PackageMetadata))]
[JsonSerializable(typeof(IEnumerable<PackageMetadata>))]
public partial class McpJsonContext : JsonSerializerContext { }Register in Program.cs:
builder.Services.AddMcpServer()
.WithTools<PackageTools>(serializerOptions: McpJsonContext.Default.Options);Critical: Add ALL tool return types to [JsonSerializable] attributes.
Tool Implementation
Minimal example:
[McpServerToolType]
public sealed class PackageTools
{
public const string SearchPackages = "SearchPackages";
[McpServerTool(Name = SearchPackages)]
[Description("Search for NuGet packages by name or keyword")]
public async Task<IEnumerable<PackageMetadata>> SearchAsync(
[Description("The package name or search keyword")] string query,
[Description("Maximum number of results (default: 10)")] int limit = 10,
CancellationToken cancellationToken = default)
{
// Implementation
}
}Key patterns: Sealed classes, const tool names, [Description] on method and parameters, always accept CancellationToken.
Transport Configuration
SSE (HTTP):
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);
builder.Services.AddMcpServer().WithHttpTransport().WithTools<PackageTools>(...);
app.MapMcp(); // Exposes /sseStdio (CLI):
builder.Services.AddMcpServer().WithStdioTransport().WithTools<PackageTools>(...);
// No MapMcp() neededTesting and Error Handling
📖 See references:
- Testing MCP servers - Integration tests, fixtures, Testcontainers
- Error handling patterns - Structured errors, error codes, logging
Common Mistakes
| Mistake | Fix |
Forgot [JsonSerializable] for return type | Add all tool return types to JsonSerializerContext |
| Not logging to stderr | Set LogToStandardErrorThreshold = LogLevel.Trace |
Missing CancellationToken | Always add CancellationToken parameter |
| Throwing exceptions in tools | Return ToolResult<T> with structured errors |
| Magic strings for tool names | Use const string in tool class |
No [Description] on parameters | Add [Description] to all parameters |
Forgetting MapMcp() | Required for HTTP/SSE transport |
References
- 📖 Testing - WebApplicationFactory, fixtures, Testcontainers
- 📖 Error Handling - Structured errors, error codes
- 📖 Advanced Topics - Clean Architecture, deployment, monitoring
- MCP Specification
- ModelContextProtocol NuGet
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

