All skills
github avatar

/dotnet-mcp-builder

@aa01464 official
by githubgithub/awesome-copilot40k stars
5,040

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.

Use this Skill: https://skilld.dev/gh/github/awesome-copilot/dotnet-mcp-builder

This session only. Nothing lands on disk.

referencestesting.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Testing and local debugging

Three workflows: interactive testing with MCP Inspector, in-process integration tests, and CI-friendly unit tests.

MCP Inspector (interactive)

MCP Inspector is the go-to tool for trying out a server by hand. It launches your server, connects via STDIO or HTTP, and gives you a UI to list/call tools, view resources, fire elicitations, see logs, and inspect raw JSON-RPC frames.

STDIO

npx @modelcontextprotocol/inspector dotnet run --project ./MyMcpServer

Pass env vars or args after --:

npx @modelcontextprotocol/inspector \
  dotnet run --project ./MyMcpServer -- \
  --some-flag value

HTTP

Start the server normally (dotnet run), then in Inspector pick "Streamable HTTP" and enter the URL (e.g. http://localhost:3001).

Use it for

  • Verifying tool descriptions are clear (Inspector renders them like the LLM would consume them).
  • Walking through elicitation flows without a real LLM.
  • Capturing the exact JSON-RPC payloads when filing bug reports.

In-process integration tests (recommended)

The cleanest test setup uses InMemoryTransport (or the lower-level StreamServerTransport / StreamClientTransport) to wire a real server and a real client together in the same process. No subprocesses, no network.

using System.IO.Pipelines;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Xunit;

public class WeatherToolsTests
{
    [Fact]
    public async Task GetWeather_returns_text()
    {
        var clientToServer = new Pipe();
        var serverToClient = new Pipe();

        await using var server = McpServer.Create(
            new StreamServerTransport(
                clientToServer.Reader.AsStream(),
                serverToClient.Writer.AsStream()),
            new McpServerOptions
            {
                ToolCollection =
                [
                    McpServerTool.Create(
                        (string city) => $"{city}: 18°C",
                        new() { Name = "GetWeather" })
                ]
            });

        var serverTask = server.RunAsync();

        await using var client = await McpClient.CreateAsync(
            new StreamClientTransport(
                clientToServer.Writer.AsStream(),
                serverToClient.Reader.AsStream()));

        var tools = await client.ListToolsAsync();
        var tool = tools.Single(t => t.Name == "GetWeather");

        var result = await tool.CallAsync(new Dictionary<string, object?>
        {
            ["city"] = "Brussels"
        });

        Assert.False(result.IsError);
        var text = result.Content.OfType<TextContentBlock>().Single().Text;
        Assert.Equal("Brussels: 18°C", text);
    }
}

This style lets you assert on the exposed behaviour (what a real client sees), not internal details.

Testing tools that use sampling/elicitation/roots

(Sampling and roots are deprecated on 2.x — expect MCP9005 warnings in test projects that exercise them; suppress in the test csproj if you're deliberately covering legacy paths.)

Inject the MCP server, but supply mock client capabilities. With the in-memory pattern above, register handlers on the client:

await using var client = await McpClient.CreateAsync(clientTransport, new McpClientOptions
{
    Capabilities = new()
    {
        Sampling = new()
        {
            SamplingHandler = (req, progress, ct) =>
                Task.FromResult(new CreateMessageResult
                {
                    Content = [new TextContentBlock { Text = "MOCK SUMMARY" }]
                })
        },
        Elicitation = new()
        {
            ElicitationHandler = (req, ct) =>
                Task.FromResult(new ElicitResult
                {
                    Action = "accept",
                    Content = JsonSerializer.SerializeToNode(new { confirm = true })
                                .AsObject().ToDictionary(kv => kv.Key, kv => JsonDocument.Parse(kv.Value!.ToJsonString()).RootElement)
                })
        }
    }
});

Now your tool's server.SampleAsync / server.ElicitAsync calls hit deterministic mocks.

Unit tests at the DI layer

For pure logic with no MCP-specific behaviour, just test the class:

[Fact]
public void Echo_prepends_hello()
{
    Assert.Equal("hello world", EchoTool.Echo("world"));
}

The [McpServerTool] attribute doesn't affect runtime behaviour outside MCP wiring — your methods are just methods.

Running it from Claude Desktop / VS Code during development

For end-to-end "feels-like-the-real-thing" testing:

  1. Run dotnet publish -c Release (or just dotnet build and use dotnet run).
  2. Point Claude Desktop / VS Code at the binary or dotnet run --project .... See transport-stdio.md for the config snippets.
  3. Restart the host.
  4. Trigger the tool from chat.

When iterating, set up dotnet watch run --project ... so the server restarts on edit; the host typically reconnects on the next tool call.

CI

A typical CI pipeline:

- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet test --no-build --logger "trx;LogFileName=test-results.trx"

Nothing MCP-specific. The in-memory transport tests run anywhere dotnet test runs — no Node, no Docker.

Common diagnostic tricks

  • "Tool isn't showing up": call client.ListToolsAsync() in a quick test and dump the names. If your tool isn't there, the registration is wrong.
  • "LLM keeps misusing the tool": open Inspector and look at the schema/description as the LLM sees it. Most "the model is dumb" issues are actually missing [Description].
  • "Sampling/elicitation throws 'method not supported'": the client doesn't advertise the capability. Either you're testing against a host that doesn't support it (Inspector supports both), or your in-memory client is missing the handler.
  • "HTTP returns 404 for /": check app.MapMcp() is called and you're hitting the right path. MapMcp("/mcp") means the URL is http://host/mcp, not http://host/.

Source: SKILL.md on GitHub

No alerts15d3 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The skill provides technical guidance and code references for building Model Context Protocol (MCP) servers and clients using the official C#/.NET SDK. It includes security best practices for resource access and follows standard development patterns.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

Signed by skilld at aa01464. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 20 hours ago.

Activeupdated 2 months ago
  • MCP
  • dotnet
  • csharp
  • model-context-protocol
  • stdio
  • http
  • tools
  • prompts
  • resources
  • streaming

README badge

README badge for github/awesome-copilot/dotnet-mcp-builder

Builds Model Context Protocol servers in C# / .NET against the stable 1.x SDK, covering STDIO and HTTP transports, tools, prompts, resources, sampling, elicitation, MCP Apps, and debugging. Targets the specific pitfalls that cause breakage: stale preview package versions, stdout pollution in STDIO mode, stateless HTTP misconfiguration, and missing primitive registration in DI.

Generated from the current SKILL.md.

Does this skill work with preview versions of the ModelContextProtocol NuGet packages?
No. The skill targets stable 1.x packages only. Preview versions (0.3, 0.4) have breaking differences and won't compile against current samples. Always pin the latest 1.x release.
Can I use stateless HTTP transport with sampling, elicitation, or server-initiated notifications?
No. Stateless HTTP breaks those features at runtime because it cannot maintain bidirectional communication. Use stateful HTTP or STDIO if you need server-to-client capabilities.
What should I do if my STDIO server isn't working?
First check that nothing is writing to stdout — configure `LogToStandardErrorThreshold = LogLevel.Trace` and remove any `Console.WriteLine` calls, since stdout is the JSON-RPC channel. Also verify tools and prompts are registered with `.WithToolsFromAssembly()` or equivalent.
Does this skill cover building MCP servers in other languages like Python or TypeScript?
No. This skill is C#/.NET only. It skips MCP work in other languages.
Can I write a .NET program that consumes an MCP server instead of building one?
Yes. Load `references/client.md` for guidance on writing a basic .NET MCP client that calls an existing server.

Generated from the current SKILL.md. These answers refresh after source changes.