mcpField
M
mcp-csharp-create

mcp-csharp-create

github.com/dotnetDevelopment5.8k722 installsUpdated 1d ago
Repository

About mcp-csharp-create

A skill for creating C# MCP servers with the official SDK and .NET templates. It guides project setup, transport selection, and implementation of tools, prompts, and resources.

Key features

  1. 01Scaffold C# MCP server projects
  2. 02Choose stdio or HTTP transport
  3. 03Implement tools, prompts, and resources
  4. 04Configure ASP.NET Core hosting
  5. 05Validate builds and server startup

Use cases

  1. 01Start a new MCP server
  2. 02Add capabilities to an existing server
  3. 03Wrap an API as MCP tools

MCP support

Guides creation of MCP servers that expose tools, prompts, and resources to MCP clients over stdio or HTTP.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dotnet/skills --skill mcp-csharp-create

After installing, type/mcp-csharp-createin Claude Code to confirm it loaded.

Bundled filesreferences/api-patterns.mdreferences/transport-config.md

Details

From the project README

C# MCP Server Creation

Create Model Context Protocol servers using the official C# SDK (ModelContextProtocol NuGet package) and the dotnet new mcpserver project template. Servers expose tools, prompts, and resources that LLMs can discover and invoke via the MCP protocol.

When to Use

  • Starting a new MCP server project from scratch
  • Adding tools, prompts, or resources to an existing MCP server
  • Choosing between stdio (--transport local) and HTTP (--transport remote) transport
  • Setting up ASP.NET Core hosting for an HTTP MCP server
  • Wrapping an external API or service as MCP tools

Stop Signals

  • Need tests or evaluations? → Use mcp-csharp-test
  • Ready to publish? → Use mcp-csharp-publish
  • Building an MCP client, not a server → This skill is server-side only

Inputs

InputRequiredDescription
Transport typeYesstdio (local/CLI) or http (remote/web). Ask user if not specified — default to stdio
Project nameYesPascalCase name for the project (e.g., WeatherMcpServer)
.NET SDK versionRecommended.NET 10.0+ required. Check with dotnet --version
Service/API to wrapRecommendedExternal API or service the tools will interact with

Workflow

Commit strategy: Commit after completing each step so scaffolding and implementation are separately reviewable.

Step 1: Verify prerequisites
  1. Confirm .NET 10+ SDK: dotnet --version (install from https://dotnet.microsoft.com if < 10.0)

  2. Check if the MCP server template is already installed:

    dotnet new list mcpserver
    

    If "No templates found" → install: dotnet new install Microsoft.McpServer.ProjectTemplates

Step 2: Choose transport
Choose stdio if…Choose HTTP if…
Local CLI tool or IDE pluginCloud/web service deployment
Single user at a timeMultiple simultaneous clients
Running as subprocess (VS Code, GitHub Copilot)Cross-network access needed
Simpler setup, no network configContainerized deployment (Docker/Azure)

Default: stdio — simpler, works for most local development. Users can add HTTP later.

Step 3: Scaffold the project

stdio server:

dotnet new mcpserver -n <ProjectName>

If the template times out or is unavailable, use dotnet new console -n <ProjectName> and add dotnet add package ModelContextProtocol.

HTTP server:

dotnet new web -n <ProjectName>
cd <ProjectName>
dotnet add package ModelContextProtocol.AspNetCore

This is the recommended approach — faster and more reliable than the template. The template also supports HTTP via dotnet new mcpserver -n <ProjectName> --transport remote, but dotnet new web gives you more control over the project structure.

Template flags reference: --transport local (stdio, default), --transport remote (ASP.NET Core HTTP), --aot, --self-contained.

Step 4: Implement tools

Tools are the primary way MCP servers expose functionality. Add a class with [McpServerToolType] and methods with [McpServerTool]:

using ModelContextProtocol.Server;
using System.ComponentModel;

[McpServerToolType]
public static class MyTools
{
    [McpServerTool, Description("Brief description of what the tool does.")]
    public static async Task<string> DoSomething(
        [Description("What this parameter controls")] string input,
        CancellationToken cancellationToken = default)
    {
        // Implementation
        return $"Result: {input}";
    }
}

Critical rules:

  • Every tool method must have a [Description] attribute — LLMs use this to decide when to call the tool
  • Every parameter must have a [Description] attribute
  • Accept CancellationToken in all async tools
  • Use [McpServerTool(Name = "custom_name")] only if the default method name is unclear

DI injection patterns — the SDK supports two styles:

  1. Method parameter injection (static class): DI services appear as method parameters. The SDK resolves them automatically — they do not appear in the tool schema.

  2. Constructor injection (non-static class): Use when tools need shared state or multiple services:

[McpServerToolType]
public class ApiTools(HttpClient httpClient, ILogger<ApiTools> logger)
{
    [McpServerTool, Description("Fetch a resource by ID.")]
    public async Task<string> FetchResource(
        [Description("Resource identifier")] string id,
        CancellationToken cancellationToken = default)
    {
        logger.LogInformation("Fetching {Id}", id);
        return await httpClient.GetStringAsync($"/api/{id}", cancellationToken);
    }
}

Register services in Program.cs:

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(options =>
    options.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services.AddHttpClient();        // registers IHttpClientFactory + HttpClient
// ILogger<T> is registered by default — no extra setup needed.

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();            // discovers non-static [McpServerToolType] classes

await builder.Build().RunAsync();

For the full attribute reference, return types, DI injection, and builder API patterns, see references/api-patterns.md.

Step 5: Add prompts and resources (optional)

Prompts — reusable LLM interaction templates:

[McpServerPromptType]
public static class MyPrompts
{
    [McpServerPrompt, Description("Summarize content into one sentence.")]
    public static ChatMessage Summarize(
        [Description("Content to summarize")] string content) =>
        new(ChatRole.User, $"Summarize this into one sentence: {content}");
}

Resources — data the LLM can read:

[McpServerResourceType]
public static class MyResources
{
    [McpServerResource(UriTemplate = "config://app", Name = "App Config",
        MimeType = "application/json"), Description("Application configuration")]
    public static string GetConfig() => JsonSerializer.Serialize(AppConfig.Current);
}
Step 6: Configure Program.cs

stdio transport:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(options =>
    options.LogToStandardErrorThreshold = LogLevel.Trace); // CRITICAL: stderr only

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

await builder.Build().RunAsync();

HTTP transport:

using ModelContextProtocol.Server;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

// Register services your tools need via DI
// builder.Services.AddHttpClient();
// builder.Services.AddSingleton<IMyService, MyService>();

var app = builder.Build();
app.MapMcp();                     // exposes MCP endpoint at /mcp (Streamable HTTP)
app.MapGet("/health", () => "ok"); // health check for container orchestrators
app.Run();

Key HTTP details: MapMcp() defaults to /mcp path. For containers, set ASPNETCORE_URLS=http://+:8080 and EXPOSE 8080. The MCP HTTP protocol uses Streamable HTTP — no special client config needed beyond the URL.

For transport configuration details (stateless mode, auth, path prefix, HttpContextAccessor), see references/transport-config.md.

Step 7: Verify the server starts
cd <ProjectName>
dotnet build
dotnet run

For stdio: the process starts and waits for JSON-RPC input on stdin. For HTTP: the server listens on the configured port.

Validation

  • Project builds with no errors (dotnet build)
  • All tool classes have [McpServerToolType] attribute
  • All tool methods have [McpServerTool] and [Description] attributes
  • All parameters have [Description] attributes
  • stdio: logging directed to stderr, not stdout
  • HTTP: app.MapMcp() is called in Program.cs
  • Server starts successfully with dotnet run

Common Pitfalls

PitfallSolution
stdio server outputs garbage or hangsLogging to stdout corrupts JSON-RPC protocol. Set LogToStandardErrorThreshold = LogLevel.Trace
Tool not discovered by LLM clientsMissing [McpServerToolType] on the class or [McpServerTool] on the method. Verify .WithToolsFromAssembly() in Program.cs
LLM doesn't understand when to use a toolAdd clear [Description] attributes on both the method and all parameters
WithToolsFromAssembly() fails in AOTReflection-based discovery is incompatible with Native AOT. Use .WithTools<MyTools>() instead
Parameters not appearing in tool schemaCancellationToken, IMcpServer, and DI services are injected automatically — they do not appear in the schema. Only parameters with [Description] are exposed
HTTP server returns 404app.MapMcp() must be called. Check the request path matches the configured route

Related Skills

  • mcp-csharp-test — Unit tests, integration tests, evaluations
  • mcp-csharp-publish — NuGet, Docker, Azure deployment

Reference Files

  • references/api-patterns.md — Complete attribute reference, return types, DI injection, builder API, dynamic tools, experimental APIs. Load when: implementing tools, prompts, or resources beyond the basic patterns shown above.
  • references/transport-config.md — Detailed transport configuration: stateless HTTP mode, OAuth/auth, custom path prefix, HttpContextAccessor, OpenTelemetry observability. Load when: configuring advanced transport options or authentication.

More Info

Comments

Markdown supported