Generated banner for Dotnet MCP Builder
Generated logo for Dotnet MCP Builder

Dotnet MCP Builder

GitHubOfficialFirst committed by AClerboisDistributed by GitHub

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

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.
— the description by AClerbois

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides guidance for developing production-ready MCP servers and clients in C#/.NET. It focuses on the official ModelContextProtocol NuGet packages, specifically targeting the stable 2.x line and the 2026-07-28 specification. It addresses common pitfalls such as stale preview versions, v2 stateless HTTP defaults, deprecated capabilities like sampling and roots, and proper transport configuration for STDIO and Streamable HTTP.

What it does

  • Provides setup patterns for MCP servers using Microsoft.Extensions.Hosting.
  • Explains how to implement tools, prompts, and resources using C# attributes.
  • Guides the configuration of STDIO and Streamable HTTP transports.
  • Offers solutions for debugging common errors like HTTP 404s, parameter binding issues, and STDIO stdout conflicts.
  • Details the multi-round-trip pattern for eliciting user input mid-tool execution.
  • Assists in building .NET-based MCP clients to consume and test servers.

When to use it

  • When starting a new MCP project in C# or upgrading an existing one to the 2.x SDK.
  • When you need to implement specific MCP primitives like tools, prompts, or resources.
  • When troubleshooting transport-level issues in STDIO or HTTP implementations.
  • When migrating away from deprecated features like sampling or roots to the current multi-round-trip pattern.
  • When you need to integrate an MCP client into a .NET application.

Advantages

  • Prevents the use of stale or incompatible preview packages.
  • Ensures adherence to current protocol specifications and deprecation warnings.
  • Provides clear patterns for handling complex interactions like user elicitation.
  • Helps avoid common runtime errors related to transport and DI configuration.
  • Promotes consistent project structure using standard .NET hosting patterns.

Usage examples

“How do I set up a basic MCP server in .NET?”
The agent provides a code snippet using AddMcpServer, WithStdioServerTransport, and tool registration via DI.
“Why is my MCP tool not appearing in the client?”
The agent checks for missing [McpServerToolType] attributes and verifies that the tool class is registered via .WithToolsFromAssembly().
“How do I ask the user for input during a tool call in v2?”
The agent explains the multi-round-trip InputRequiredException pattern for modern stateless HTTP.
“How do I connect to an MCP server from a .NET client?”
The agent provides a configuration example for StdioClientTransport or HttpClientTransport.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/github/dotnet-mcp-builder/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip dotnet-mcp-builder.zip -d .agents/skills

Documentation by AClerbois

Building MCP servers in .NET

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).

When this skill earns its keep

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:

  • Pin a stale preview version that won't compile against current samples.

  • Apply 1.x-era defaults that v2 reversed (HTTP was stateful by default; in 2.x Stateless defaults to true).

  • Recommend capabilities the 2026-07-28 spec deprecates (roots, sampling, MCP-channel logging — now [Obsolete], warning MCP9005).

  • Miss recent spec features (multi-round-trip input_required, discovery-first negotiation, MCP Apps/Tasks extension packages, elicitation URL mode, structured content blocks).

  • Get HTTP transport details wrong (stateful/stateless, proxy buffering, OAuth wiring).

  • Forget the STDIO stdout/stderr trap.

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.

Mental model in 30 seconds

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.

Decision tree → which references to load

Always load references/packages.md if you're creating a new project or unsure of the current package version.

TaskLoad
New STDIO serverreferences/transport-stdio.md
New HTTP (Streamable) serverreferences/transport-http.md
Add/modify a toolreferences/tool-primitive.md
Add/modify a promptreferences/prompt-primitive.md
Add/modify a resourcereferences/resource-primitive.md
Ask the user a question mid-toolreferences/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 UIreferences/mcp-apps.md
Argument completions, log/progress notifications, filters, server instructionsreferences/server-features.md
Write a .NET program that consumes an MCP serverreferences/client.md
MCP Inspector, in-memory tests, mocks, CIreferences/testing.md

For multi-primitive tasks, load several at once. For trivial edits in an existing file, you usually don't need any.

Cardinal rules (apply always; these prevent the highest-frequency breakages)

  1. Pin the current stable package, not a preview. Use 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.

  2. STDIO servers must not write to stdout. Stdout is the JSON-RPC channel. Configure LogToStandardErrorThreshold = LogLevel.Trace before anything else and never Console.WriteLine from a tool.

  3. HTTP defaults to stateless in 2.x (v1.x defaulted to stateful — the single most impactful v2 breaking change). The 2026-07-28 revision has no HTTP sessions at all: setting 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.

  4. SSE-only is deprecated. Use Streamable HTTP. Only enable legacy SSE (EnableLegacySse = true) for an old client you must support, and call it out.

  5. Don't design new servers around deprecated capabilities. The 2026-07-28 spec deprecates roots, sampling, and MCP-channel logging; the SDK marks them [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.

  6. Always [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.

  7. Show the registration line every time you add a primitive. A new [McpServerPromptType] class without .WithPrompts<...>() (or .WithPromptsFromAssembly()) is invisible.

  8. Don't invent APIs. If you're unsure a method exists, say so and check the API reference — wrong method names cause silent failures. This applies doubly to the new v2 extension packages (ModelContextProtocol.Extensions.Tasks, ModelContextProtocol.Extensions.Apps) — check their docs before writing code against them.

Working style

  • Make minimal, additive changes. Add a method to the existing tool class rather than restructuring the project.

  • For non-trivial setups, run dotnet build. Catches missing usings, attribute typos, and TFM mismatches before the user sees them.

  • Confirm transport + .NET version + primitives before scaffolding if context doesn't already make them obvious. Default to .NET 10 for new projects.

When the user is stuck

Walk this checklist before guessing:

  1. STDIO: something is writing to stdout (logger sink, Console.WriteLine, library banner).

  2. HTTP 404: path mismatch — app.MapMcp() is root, app.MapMcp("/mcp") puts it under /mcp.

  3. Tool not appearing: missing [McpServerToolType] on the class, or no .WithToolsFromAssembly() / .WithTools<T>() registered.

  4. Args not bound: parameter names must match the JSON-RPC arguments keys; complex types bind via System.Text.Json.

  5. Sampling/elicitation/roots failing: these legacy server-to-client calls can't run on current-protocol HTTP — migrate to the multi-round-trip 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.

  6. 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.

Files

references/mcp-apps.md

MCP Apps (interactive UI)

MCP Apps is the official extension that lets a tool return an interactive UI rendered in a sandboxed iframe inside the host (Claude, Claude Desktop, VS Code Copilot, Goose, Postman, MCPJam). Typical use cases: charts, dashboards, multi-step forms, 3D viewers, real-time monitors, PDF/video viewers.

Important: SDK 2.x ships a dedicated extension package, ModelContextProtocol.Extensions.Apps, with typed MCP Apps support: register with .WithMcpApps() and annotate tools with [McpAppUi(ResourceUri = "ui://...")]. It replaces the hand-rolled _meta wiring, not the ui:// resource — you still register and serve the UI resource. The APIs are marked experimental (suppress diagnostic MCPEXP003); check the package page and SDK API reference for the current surface rather than guessing beyond those names. The manual pattern below is what you need on 1.x, which has no typed layer (was tracked in csharp-sdk#1431): serve a ui:// resource and emit the right _meta on the tool.

How it works (short version)

  1. You register a resource at a ui:// URI returning an HTML bundle.

  2. You register a tool whose definition includes _meta.ui.resourceUri pointing to that URI.

  3. When the LLM calls the tool, the host fetches the UI resource and renders it in a sandboxed iframe in the chat.

  4. The HTML talks to the host over postMessage JSON-RPC (use @modelcontextprotocol/ext-apps from the bundle, or hand-roll it).

  5. The app can call back into your MCP server (any tool), update the model context, etc.

The full protocol spec is at @modelcontextprotocol/ext-apps.

Step 1: Serve the UI resource

Bundle your HTML/JS/CSS into a single string (or load from wwwroot). Serve it at a ui:// URI.

using System.ComponentModel;
using System.IO;
using System.Reflection;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

[McpServerResourceType]
public static class ChartUiResource
{
    [McpServerResource(
        UriTemplate = "ui://charts/interactive",
        Name = "Interactive chart",
        MimeType = "text/html;profile=mcp-app")]   // see "MIME type" note below
    [Description("UI bundle for the interactive chart MCP App.")]
    public static TextResourceContents GetUi()
    {
        // Load a bundled HTML/JS file from embedded resources or wwwroot.
        var html = LoadEmbeddedString("MyMcpServer.AppUi.chart.html");

        return new TextResourceContents
        {
            Uri = "ui://charts/interactive",
            MimeType = "text/html;profile=mcp-app",
            Text = html
        };
    }

    private static string LoadEmbeddedString(string resourceName)
    {
        var asm = Assembly.GetExecutingAssembly();
        using var stream = asm.GetManifestResourceStream(resourceName)
            ?? throw new InvalidOperationException($"Missing embedded resource {resourceName}");
        using var reader = new StreamReader(stream);
        return reader.ReadToEnd();
    }
}

MIME type note: the current Apps spec (2026-01-26) uses text/html;profile=mcp-app for app HTML so hosts can distinguish UI bundles from regular text/html previews. Earlier drafts used text/html+skybridge — treat that as legacy; some older hosts may still expect it.

Step 2: Emit _meta on the tool

The C# SDK's [McpServerTool] doesn't expose _meta in the attribute today, so set it via the lower-level Tool definition. Do this once at startup:

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Text.Json;
using System.Text.Json.Nodes;

builder.Services.Configure<McpServerOptions>(options =>
{
    options.Capabilities ??= new();
    options.Capabilities.Tools ??= new();

    // Define the tool manually so we can attach _meta.
    var visualizeTool = new Tool
    {
        Name = "visualize_data",
        Description = "Visualize the user's data as an interactive chart.",
        InputSchema = JsonDocument.Parse("""
            {
              "type": "object",
              "properties": {
                "datasetId": { "type": "string", "description": "Dataset to visualize." }
              },
              "required": ["datasetId"]
            }
            """).RootElement,
        Meta = new JsonObject
        {
            ["ui"] = new JsonObject
            {
                ["resourceUri"] = "ui://charts/interactive"
                // Optionally:
                // ["csp"] = new JsonObject { ["default-src"] = "'self' https://cdn.example.com" },
                // ["permissions"] = new JsonArray("clipboard-write")
            }
        }
    };

    // Implement the call handler that returns the data the UI will render.
    options.Capabilities.Tools.ToolCollection ??= new();
    options.Capabilities.Tools.ToolCollection.Add(McpServerTool.Create(
        async (CallToolRequestParams req, CancellationToken ct) =>
        {
            var args = req.Arguments ?? new();
            var datasetId = args["datasetId"]!.GetValue<string>();
            var data = await LoadDataset(datasetId, ct);
            return new CallToolResult
            {
                Content = [new TextContentBlock { Text = JsonSerializer.Serialize(data) }],
                StructuredContent = JsonSerializer.SerializeToNode(data)
            };
        },
        visualizeTool));
});

If you don't need full structured content, the tool can return just JSON in a text block — the UI fetches it via app.callServerTool(...) after rendering.

Backwards compatibility key

Some older hosts expect _meta["ui/resourceUri"] instead of _meta.ui.resourceUri. Set both for safety:

Meta = new JsonObject
{
    ["ui"] = new JsonObject { ["resourceUri"] = "ui://charts/interactive" },
    ["ui/resourceUri"] = "ui://charts/interactive"   // legacy
}

Step 3: The HTML bundle

A minimum viable bundle: vanilla JS using @modelcontextprotocol/ext-apps. The simplest build is a single self-contained HTML file.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Chart</title>
    <style>body { font-family: system-ui; margin: 0; }</style>
  </head>
  <body>
    <div id="root">Loading…</div>
    <script type="module">
      import { App } from "https://esm.sh/@modelcontextprotocol/[email protected]";

      const app = new App();
      await app.connect();

      // Fetch the data we need from the server.
      const resp = await app.callServerTool({
        name: "visualize_data",
        arguments: { datasetId: "default" }
      });

      const data = JSON.parse(resp.content[0].text);
      document.getElementById("root").textContent =
        `Loaded ${data.points.length} data points.`;

      // Tell the model what just happened (becomes part of its context).
      await app.updateModelContext({
        content: [{ type: "text", text: "User opened the chart UI." }]
      });
    </script>
  </body>
</html>

Tip: for non-trivial UIs, build with Vite (React/Vue/Svelte/Solid — any of the official starter templates) and have the build emit a single inlined HTML you embed as a project resource.

Project layout

A pragmatic layout for an MCP App in .NET:

MyMcpServer/
├── Program.cs
├── Tools/
│   └── VisualizeDataTool.cs       # (or registered via Configure as above)
├── Resources/
│   └── ChartUiResource.cs         # serves the ui:// resource
├── AppUi/
│   ├── chart.html                 # bundled UI (Embedded Resource)
│   └── package.json + src/...     # if you build with Vite, output to chart.html
└── MyMcpServer.csproj

In the csproj:

<ItemGroup>
  <EmbeddedResource Include="AppUi\chart.html" />
</ItemGroup>

Read it via Assembly.GetManifestResourceStream("MyMcpServer.AppUi.chart.html").

Testing locally

  1. Run your MCP server (STDIO or HTTP).

  2. Use a host that supports MCP Apps — Claude Desktop or VS Code Copilot Chat are the easiest.

  3. Trigger the tool via the LLM. The UI renders inline.

For pure-UI iteration, MCP Inspector shows resource contents but does not fully render apps; for that, point Claude Desktop at your dev server.

Pitfalls

  • Wrong MIME type. Use text/html;profile=mcp-app (current spec; text/html+skybridge is a legacy draft value). Plain text/html may still work on lenient hosts but isn't future-proof.

  • CSP too tight or too loose. If your UI loads from a CDN, declare it in Meta["ui"]["csp"] on the Tool definition (this serialises to _meta.ui.csp on the wire). Otherwise the iframe sandbox blocks it.

  • Forgetting Tool.Meta on the tool. Without the Meta property containing the ui.resourceUri entry, the host treats your tool as a regular text-returning tool. The UI never appears.

  • Trying to use browser APIs outside the sandbox. No cookies, no localStorage from the parent. Use app.updateModelContext and tool calls for state.

Migrating from the manual pattern

On 2.x, ModelContextProtocol.Extensions.Apps replaces the manual Configure block: .WithMcpApps() plus [McpAppUi(ResourceUri = "ui://...")] on the tool, with the extension handling the Apps capability negotiation. You still serve the ui:// resource (with the current text/html;profile=mcp-app MIME type) and keep your HTML bundle — keep the UI HTML as embedded resources so the migration is mechanical. The APIs are experimental (MCPEXP003); consult the package docs for anything beyond this surface rather than inventing it.

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

First committed by
GitHub avatar of AClerboisAClerbois
Publisher
GitHub (Official)
Distributed by
GitHub
Source
github/awesome-copilot · skills/dotnet-mcp-builder · commit 797337f
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Contributors
AClerbois, jeanpaulhassane-225

AutoGineer is not affiliated with or endorsed by AClerbois or GitHub.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download dotnet-mcp-builder.zip