Loading...
Loading...
Create MCP servers using the C# SDK and .NET project templates. Covers scaffolding, tool/prompt/resource implementation, and transport configuration for stdio and HTTP. USE FOR: creating new MCP server projects, scaffolding with dotnet new mcpserver, adding MCP tools/prompts/resources, choosing stdio vs HTTP transport, configuring MCP hosting in Program.cs, setting up ASP.NET Core MCP endpoints with MapMcp. DO NOT USE FOR: debugging or running existing servers (use mcp-csharp-debug), writing tests (use mcp-csharp-test), publishing or deploying (use mcp-csharp-publish), building MCP clients, non-.NET MCP servers.
npx skill4agent add dotnet/skills mcp-csharp-createModelContextProtocoldotnet new mcpserver--transport local--transport remotemcp-csharp-debugmcp-csharp-testmcp-csharp-publish| Input | Required | Description |
|---|---|---|
| Transport type | Yes | |
| Project name | Yes | PascalCase name for the project (e.g., |
| .NET SDK version | Recommended | .NET 10.0+ required. Check with |
| Service/API to wrap | Recommended | External API or service the tools will interact with |
Commit strategy: Commit after completing each step so scaffolding and implementation are separately reviewable.
dotnet --versiondotnet new list mcpserverdotnet new install Microsoft.McpServer.ProjectTemplates| Choose stdio if… | Choose HTTP if… |
|---|---|
| Local CLI tool or IDE plugin | Cloud/web service deployment |
| Single user at a time | Multiple simultaneous clients |
| Running as subprocess (VS Code, GitHub Copilot) | Cross-network access needed |
| Simpler setup, no network config | Containerized deployment (Docker/Azure) |
dotnet new mcpserver -n <ProjectName>dotnet new console -n <ProjectName>dotnet add package ModelContextProtocoldotnet new web -n <ProjectName>
cd <ProjectName>
dotnet add package ModelContextProtocol.AspNetCoredotnet new mcpserver -n <ProjectName> --transport remotedotnet new web--transport local--transport remote--aot--self-contained[McpServerToolType][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}";
}
}[Description][Description]CancellationToken[McpServerTool(Name = "custom_name")][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);
}
}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();[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}");
}[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);
}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();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();MapMcp()/mcpASPNETCORE_URLS=http://+:8080EXPOSE 8080HttpContextAccessorcd <ProjectName>
dotnet build
dotnet rundotnet build[McpServerToolType][McpServerTool][Description][Description]app.MapMcp()dotnet run| Pitfall | Solution |
|---|---|
| stdio server outputs garbage or hangs | Logging to stdout corrupts JSON-RPC protocol. Set |
| Tool not discovered by LLM clients | Missing |
| LLM doesn't understand when to use a tool | Add clear |
| Reflection-based discovery is incompatible with Native AOT. Use |
| Parameters not appearing in tool schema | |
| HTTP server returns 404 | |
mcp-csharp-debugmcp-csharp-testmcp-csharp-publishHttpContextAccessor