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.
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.
Copy a source-pinned command for your client. You run it yourself.
Destination: .claude/skills/dotnet-mcp-builder · pinned to the source commit
# Run from your project root
git clone https://github.com/github/awesome-copilot.git .skillboard-tmp
git -C .skillboard-tmp checkout f11a4e441c5ff061b4f8ae37952be8c602e4034e
mkdir -p ".claude/skills"
cp -r ".skillboard-tmp/skills/dotnet-mcp-builder" ".claude/skills/"
rm -rf .skillboard-tmpReview the source before running. This copies files into your project; it is not a one-click install and does not verify runtime safety.
sudo apt update && sudo apt install -y gitnpm install -g @anthropic-ai/claude-code# Run from your project root
git clone https://github.com/github/awesome-copilot.git .skillboard-tmp
git -C .skillboard-tmp checkout f11a4e441c5ff061b4f8ae37952be8c602e4034e
mkdir -p ".claude/skills"
cp -r ".skillboard-tmp/skills/dotnet-mcp-builder" ".claude/skills/"
rm -rf .skillboard-tmpDestination: .claude/skills/dotnet-mcp-builder
Scanner static-checks@0.1.0 · commit f11a4e441c5f. Static checks cannot prove runtime safety – review the source and the exact diff before installing. How checks work.
No static rules matched. This is not a safety guarantee.