github/dotnet-mcp-builder
Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 2.x NuGet packages. Helps with cases the model gets wrong without guidance — stale versions (0.x preview or 1.x-era defaults), the v2 stateless-by-default HTTP flip, the 2026-07-28 spec deprecations (roots/sampling/logging), MCP Apps and Tasks extension packages, elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging MapMcp / STDIO / Streamable-HTTP errors. Also covers STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, completions, and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages.
npx skills add https://github.com/github/awesome-copilot --skill dotnet-mcp-builder
This skill helps you write production-quality MCP servers and basic clients in C#/.NET against the official ModelContextProtocol NuGet packages, maintained by Microsoft and the MCP project. It targets the stable 2.x line and the current spec (2026-07-28).
The .NET MCP SDK had years of preview packages (0.x-preview) before reaching 1.0, and v2 flipped several defaults. Without help, the model tends to:
Stateless defaults to true).[Obsolete], warning MCP9005).input_required, discovery-first negotiation, MCP Apps/Tasks extension packages, elicitation URL mode, structured content blocks).If the task is one of those, *load the matching reference* and follow it. If it's truly trivial (e.g. "rename this tool method"), you don't need to read everything — the cardinal rules below are the minimum.
A .NET MCP server is an ordinary Microsoft.Extensions.Hosting (or WebApplication) app that wires an MCP server through DI:
builder.Services
.AddMcpServer()
.WithStdioServerTransport() // OR .WithHttpTransport(...)
.WithToolsFromAssembly() // discover [McpServerToolType] classes
.WithPrompts<MyPrompts>() // optional
.WithResources<MyResources>(); // optional
Primitives are plain C# methods on classes marked with attributes ([McpServerToolType] + [McpServerTool], [McpServerPromptType] + [McpServerPrompt], [McpServerResourceType] + [McpServerResource]). Parameters bind from JSON-RPC; the SDK builds the JSON Schema from the signature plus [Description] attributes.
Server-to-client features (elicitation, progress notifications, and the now-deprecated sampling/roots/log notifications) are methods on the injected IMcpServer.
Always load references/packages.md if you're creating a new project or unsure of the current package version.
| Task | Load |
|---|---|
| New STDIO server | references/transport-stdio.md |
| New HTTP (Streamable) server | references/transport-http.md |
| Add/modify a tool | references/tool-primitive.md |
| Add/modify a prompt | references/prompt-primitive.md |
| Add/modify a resource | references/resource-primitive.md |
| Ask the user a question mid-tool | references/elicitation.md |
| Call the client's LLM from a tool (deprecated in 2026-07-28) | references/sampling.md |
| Read the user's project roots (deprecated in 2026-07-28) | references/roots.md |
| Return an interactive UI | references/mcp-apps.md |
| Argument completions, log/progress notifications, filters, server instructions | references/server-features.md |
| Write a .NET program that consumes an MCP server | references/client.md |
| MCP Inspector, in-memory tests, mocks, CI | references/testing.md |
For multi-primitive tasks, load several at once. For trivial edits in an existing file, you usually don't need any.
ModelContextProtocol / ModelContextProtocol.AspNetCore / ModelContextProtocol.Core at the latest 2.x. If you find yourself writing 0.3-preview or 0.4-preview, stop and check NuGet — preview APIs have breaking differences. 1.x still works but predates the 2026-07-28 spec.LogToStandardErrorThreshold = LogLevel.Trace before anything else and never Console.WriteLine from a tool.Stateless = false makes the server refuse that revision and serve clients via the legacy initialize fallback. For "ask the user something mid-tool" on current-protocol HTTP, use the multi-round-trip InputRequiredException pattern; reserve stateful HTTP (or STDIO) for the legacy ElicitAsync/sampling/roots paths and pushed notifications.EnableLegacySse = true) for an old client you must support, and call it out.[Obsolete] (warning MCP9005). They still work against down-level clients, but for new designs prefer the multi-round-trip input_required pattern and ILogger logging. Suppress MCP9005 only as a documented transition measure.[Description] tools and parameters. This is what the LLM sees when picking and shaping calls. Vague descriptions are the #1 reason tools don't get used.[McpServerPromptType] class without .WithPrompts<...>() (or .WithPromptsFromAssembly()) is invisible.ModelContextProtocol.Extensions.Tasks, ModelContextProtocol.Extensions.Apps) — check their docs before writing code against them.dotnet build. Catches missing usings, attribute typos, and TFM mismatches before the user sees them.Walk this checklist before guessing:
Console.WriteLine, library banner).app.MapMcp() is root, app.MapMcp("/mcp") puts it under /mcp.[McpServerToolType] on the class, or no .WithToolsFromAssembly() / .WithTools<T>() registered.arguments keys; complex types bind via System.Text.Json.InputRequiredException pattern, or (legacy paths only) set Stateless = false, knowing that pins HTTP clients to a down-level initialize revision. Also check the client actually advertises the capability.MCP9005 build warnings after upgrading to 2.x: the code uses deprecated roots/sampling/logging APIs. Plan the migration; suppress only temporarily.Still stuck? Point the user at the EverythingServer sample — it exercises every feature.
Take github/dotnet-mcp-builder from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.