Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Model Context Protocol C# SDK
https://github.com/modelcontextprotocol/csharp-sdk
Admin
The official C# SDK for the Model Context Protocol, enabling .NET applications to implement and
...
Tokens:
44,402
Snippets:
147
Trust Score:
7.8
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
95
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# MCP C# SDK The MCP C# SDK is the official .NET implementation of the Model Context Protocol (MCP), an open protocol that standardizes how applications provide context to Large Language Models (LLMs). This SDK enables .NET applications to implement both MCP clients and servers, facilitating secure integration between LLMs and various data sources and tools. The SDK consists of three NuGet packages: `ModelContextProtocol.Core` for minimal dependencies, `ModelContextProtocol` for hosting and dependency injection, and `ModelContextProtocol.AspNetCore` for HTTP-based servers. The protocol supports three core primitives - Tools, Prompts, and Resources - that allow servers to expose callable functions, reusable prompt templates, and data content to clients. Communication between clients and servers occurs over stdio (for local integrations) or HTTP (Streamable HTTP or legacy SSE) transports. The SDK integrates with Microsoft.Extensions.AI abstractions, enabling seamless use of MCP tools with any `IChatClient` implementation and supporting features like sampling, progress tracking, and logging. ## Creating an MCP Server with stdio Transport The simplest way to create an MCP server is using the hosted service pattern with stdio transport, which communicates over standard input/output streams. Tools, prompts, and resources are discovered automatically using attributes. ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ModelContextProtocol.Server; using System.ComponentModel; var builder = Host.CreateApplicationBuilder(args); // Configure logging to stderr (required for stdio transport) builder.Logging.AddConsole(options => { options.LogToStandardErrorThreshold = LogLevel.Trace; }); // Register MCP server with stdio transport and auto-discover tools builder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); // Discovers all [McpServerToolType] classes await builder.Build().RunAsync(); // Define tools using attributes [McpServerToolType] public static class EchoTool { [McpServerTool, Description("Echoes the message back to the client.")] public static string Echo([Description("The message to echo")] string message) => $"hello {message}"; } ``` ## Creating an HTTP-Based MCP Server with ASP.NET Core For remote server scenarios, use the ASP.NET Core integration which provides both Streamable HTTP and legacy SSE transport support through a single endpoint. ```csharp using ModelContextProtocol.Server; using System.ComponentModel; var builder = WebApplication.CreateBuilder(args); // Register MCP server with HTTP transport builder.Services.AddMcpServer() .WithHttpTransport() .WithToolsFromAssembly(); var app = builder.Build(); // Map MCP endpoint (handles both Streamable HTTP and SSE) app.MapMcp(); // Default route "/" // Or use custom route: app.MapMcp("/mcp"); app.Run("http://localhost:3001"); [McpServerToolType] public static class CalculatorTools { [McpServerTool, Description("Adds two numbers together")] public static double Add( [Description("First number")] double a, [Description("Second number")] double b) => a + b; [McpServerTool, Description("Divides two numbers")] public static double Divide(double a, double b) { if (b == 0) throw new ArgumentException("Cannot divide by zero"); return a / b; } } ``` ## Creating an MCP Client with stdio Transport The MCP client connects to servers and can list/call tools, get prompts, and read resources. The stdio transport launches a server as a child process. ```csharp using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; // Create transport to launch server process var clientTransport = new StdioClientTransport(new StdioClientTransportOptions { Name = "Weather Server", Command = "dotnet", Arguments = ["run", "--project", "path/to/WeatherServer"], ShutdownTimeout = TimeSpan.FromSeconds(10) }); // Create client and connect await using var client = await McpClient.CreateAsync(clientTransport); // List available tools IList<McpClientTool> tools = await client.ListToolsAsync(); foreach (var tool in tools) { Console.WriteLine($"{tool.Name}: {tool.Description}"); } // Call a tool with parameters CallToolResult result = await client.CallToolAsync( "GetForecast", new Dictionary<string, object?> { ["latitude"] = 40.7128, ["longitude"] = -74.0060 }); // Process text result if (result.IsError is true) { Console.WriteLine($"Error: {result.Content.OfType<TextContentBlock>().First().Text}"); } else { Console.WriteLine(result.Content.OfType<TextContentBlock>().First().Text); } ``` ## Creating an MCP Client with HTTP Transport For connecting to remote MCP servers, use the HTTP client transport with automatic transport mode detection. ```csharp using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; // Create HTTP transport (auto-detects Streamable HTTP vs SSE) var transport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("https://my-mcp-server.example.com/mcp"), TransportMode = HttpTransportMode.AutoDetect, // Default ConnectionTimeout = TimeSpan.FromSeconds(30), AdditionalHeaders = new Dictionary<string, string> { ["Authorization"] = "Bearer your-token" } }); await using var client = await McpClient.CreateAsync(transport); // Use tools with any IChatClient (Microsoft.Extensions.AI integration) IList<McpClientTool> tools = await client.ListToolsAsync(); IChatClient chatClient = /* your chat client implementation */; var response = await chatClient.GetResponseAsync( "What's the weather in New York?", new ChatOptions { Tools = [.. tools] }); ``` ## Defining Server Tools with Dependency Injection Tools can accept injected services, the McpServer instance, progress reporting, and cancellation tokens as parameters alongside tool arguments. ```csharp using ModelContextProtocol; using ModelContextProtocol.Server; using System.ComponentModel; using System.Text.Json; [McpServerToolType] public sealed class WeatherTools { // HttpClient injected via DI [McpServerTool, Description("Get weather alerts for a US state.")] public static async Task<string> GetAlerts( HttpClient client, // Injected from DI container [Description("2-letter state abbreviation (e.g. NY)")] string state) { var response = await client.GetAsync($"/alerts/active/area/{state}"); response.EnsureSuccessStatusCode(); using var doc = await JsonDocument.ParseAsync( await response.Content.ReadAsStreamAsync()); var alerts = doc.RootElement.GetProperty("features").EnumerateArray(); if (!alerts.Any()) return "No active alerts for this state."; return string.Join("\n--\n", alerts.Select(alert => { var props = alert.GetProperty("properties"); return $""" Event: {props.GetProperty("event").GetString()} Severity: {props.GetProperty("severity").GetString()} Description: {props.GetProperty("description").GetString()} """; })); } [McpServerTool, Description("Get weather forecast for a location.")] public static async Task<string> GetForecast( HttpClient client, [Description("Latitude")] double latitude, [Description("Longitude")] double longitude) { var pointResponse = await client.GetStringAsync($"/points/{latitude},{longitude}"); using var pointDoc = JsonDocument.Parse(pointResponse); var forecastUrl = pointDoc.RootElement .GetProperty("properties") .GetProperty("forecast") .GetString(); var forecastResponse = await client.GetStringAsync(forecastUrl!); using var forecastDoc = JsonDocument.Parse(forecastResponse); var periods = forecastDoc.RootElement .GetProperty("properties") .GetProperty("periods") .EnumerateArray(); return string.Join("\n---\n", periods.Select(p => $""" {p.GetProperty("name").GetString()} Temperature: {p.GetProperty("temperature").GetInt32()}F Wind: {p.GetProperty("windSpeed").GetString()} Forecast: {p.GetProperty("detailedForecast").GetString()} """)); } } // Register in Program.cs var builder = Host.CreateApplicationBuilder(args); // Register HttpClient for injection builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri("https://api.weather.gov") }); builder.Services.AddMcpServer() .WithStdioServerTransport() .WithTools<WeatherTools>(); await builder.Build().RunAsync(); ``` ## Defining Server Prompts Prompts expose reusable prompt templates that can be parameterized. They return ChatMessage or PromptMessage instances. ```csharp using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; [McpServerPromptType] public class CodePrompts { [McpServerPrompt, Description("A simple greeting prompt")] public static ChatMessage Greeting() => new(ChatRole.User, "Hello! How can you help me today?"); [McpServerPrompt, Description("Generates a code review prompt")] public static IEnumerable<ChatMessage> CodeReview( [Description("The programming language")] string language, [Description("The code to review")] string code) => [ new(ChatRole.User, $"Please review the following {language} code:\n\n```{language}\n{code}\n```"), new(ChatRole.Assistant, "I'll review the code for correctness, style, and potential improvements.") ]; [McpServerPrompt, Description("Prompt with embedded resource")] public static IEnumerable<PromptMessage> ReviewDocument( [Description("The document ID")] string documentId) { string content = LoadDocument(documentId); return [ new PromptMessage { Role = Role.User, Content = new TextContentBlock { Text = "Please review:" } }, new PromptMessage { Role = Role.User, Content = new EmbeddedResourceBlock { Resource = new TextResourceContents { Uri = $"docs://documents/{documentId}", MimeType = "text/plain", Text = content } } } ]; } private static string LoadDocument(string id) => $"Document {id} content"; } // Register prompts builder.Services.AddMcpServer() .WithHttpTransport() .WithPrompts<CodePrompts>(); ``` ## Consuming Prompts on the Client Clients can list available prompts and retrieve them with argument values. ```csharp using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; await using var client = await McpClient.CreateAsync(transport); // List available prompts IList<McpClientPrompt> prompts = await client.ListPromptsAsync(); foreach (var prompt in prompts) { Console.WriteLine($"{prompt.Name}: {prompt.Description}"); // Show arguments if (prompt.ProtocolPrompt.Arguments is { Count: > 0 }) { foreach (var arg in prompt.ProtocolPrompt.Arguments) { var required = arg.Required == true ? " (required)" : ""; Console.WriteLine($" - {arg.Name}: {arg.Description}{required}"); } } } // Get a prompt with arguments GetPromptResult result = await client.GetPromptAsync( "CodeReview", new Dictionary<string, object?> { ["language"] = "csharp", ["code"] = "public static int Add(int a, int b) => a + b;" }); // Process returned messages foreach (var message in result.Messages) { Console.WriteLine($"[{message.Role}]:"); switch (message.Content) { case TextContentBlock text: Console.WriteLine($" {text.Text}"); break; case ImageContentBlock image: Console.WriteLine($" [image] {image.MimeType}"); break; case EmbeddedResourceBlock resource: Console.WriteLine($" Resource: {resource.Resource.Uri}"); break; } } ``` ## Defining Server Resources Resources expose data content via URIs. Direct resources have fixed URIs; template resources use RFC 6570 URI templates with parameters. ```csharp using ModelContextProtocol; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; using System.Text.Json; [McpServerResourceType] public class MyResources { // Direct resource with fixed URI [McpServerResource( UriTemplate = "config://app/settings", Name = "App Settings", MimeType = "application/json")] [Description("Returns application configuration")] public static string GetSettings() => JsonSerializer.Serialize(new { theme = "dark", language = "en" }); // Template resource with parameter [McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")] [Description("Returns an article by ID")] public static ResourceContents GetArticle(string id) { string? content = LoadArticle(id); if (content is null) throw new McpException($"Article not found: {id}"); return new TextResourceContents { Uri = $"docs://articles/{id}", MimeType = "text/markdown", Text = content }; } // Binary resource [McpServerResource(UriTemplate = "images://photos/{id}", Name = "Photo")] [Description("Returns a photo by ID")] public static BlobResourceContents GetPhoto(int id) { byte[] imageData = LoadPhoto(id); return BlobResourceContents.FromBytes( imageData, $"images://photos/{id}", "image/png"); } private static string? LoadArticle(string id) => $"# Article {id}\n\nContent here..."; private static byte[] LoadPhoto(int id) => new byte[0]; } // Register resources builder.Services.AddMcpServer() .WithHttpTransport() .WithResources<MyResources>(); ``` ## Consuming Resources on the Client Clients can list resources and templates, read resource content, and subscribe to updates. ```csharp using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; await using var client = await McpClient.CreateAsync(transport); // List direct resources IList<McpClientResource> resources = await client.ListResourcesAsync(); foreach (var resource in resources) { Console.WriteLine($"{resource.Name} ({resource.Uri}) - {resource.MimeType}"); } // List resource templates IList<McpClientResourceTemplate> templates = await client.ListResourceTemplatesAsync(); foreach (var template in templates) { Console.WriteLine($"{template.Name}: {template.UriTemplate}"); } // Read a direct resource ReadResourceResult result = await client.ReadResourceAsync("config://app/settings"); foreach (var content in result.Contents) { if (content is TextResourceContents text) Console.WriteLine($"[{text.MimeType}] {text.Text}"); else if (content is BlobResourceContents blob) Console.WriteLine($"[{blob.MimeType}] {blob.Blob.Length} bytes"); } // Read a template resource with parameters ReadResourceResult articleResult = await client.ReadResourceAsync( "docs://articles/{id}", new Dictionary<string, object?> { ["id"] = "getting-started" }); // Subscribe to resource updates IAsyncDisposable subscription = await client.SubscribeToResourceAsync( "config://app/settings", async (notification, ct) => { Console.WriteLine($"Resource updated: {notification.Uri}"); var updated = await client.ReadResourceAsync(notification.Uri, cancellationToken: ct); // Process updated content... }); // Unsubscribe when done await subscription.DisposeAsync(); ``` ## Returning Rich Content from Tools Tools can return text, images, audio, embedded resources, or mixed content types. ```csharp using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public class RichContentTools { // Return image content [McpServerTool, Description("Generates and returns an image")] public static ImageContentBlock GenerateImage() { byte[] pngBytes = CreateImage(); return ImageContentBlock.FromBytes(pngBytes, "image/png"); } // Return audio content [McpServerTool, Description("Synthesizes speech from text")] public static AudioContentBlock Synthesize(string text) { byte[] wavBytes = TextToSpeech(text); return AudioContentBlock.FromBytes(wavBytes, "audio/wav"); } // Return embedded resource [McpServerTool, Description("Returns a document as embedded resource")] public static EmbeddedResourceBlock GetDocument(string id) { return new EmbeddedResourceBlock { Resource = new TextResourceContents { Uri = $"docs://{id}", MimeType = "text/plain", Text = "Document content here..." } }; } // Return mixed content [McpServerTool, Description("Returns text and an image")] public static IEnumerable<ContentBlock> DescribeImage() { byte[] imageBytes = GetImage(); return [ new TextContentBlock { Text = "Here is the generated image:" }, ImageContentBlock.FromBytes(imageBytes, "image/png"), new TextContentBlock { Text = "The image shows a landscape.", Annotations = new Annotations { Audience = [Role.Assistant], // Only for LLM Priority = 0.3f } } ]; } private static byte[] CreateImage() => new byte[0]; private static byte[] TextToSpeech(string text) => new byte[0]; private static byte[] GetImage() => new byte[0]; } ``` ## Implementing Sampling (Server Requests LLM from Client) Sampling allows servers to request LLM completions from the client during tool execution. ```csharp // Server-side: Request completion from client using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public class SamplingTools { [McpServerTool(Name = "SummarizeContent")] [Description("Summarizes the given text using the client's LLM")] public static async Task<string> Summarize( McpServer server, // Injected automatically [Description("The text to summarize")] string text, CancellationToken cancellationToken) { ChatMessage[] messages = [ new(ChatRole.User, "Briefly summarize the following content:"), new(ChatRole.User, text), ]; ChatOptions options = new() { MaxOutputTokens = 256, Temperature = 0.3f, }; // Use IChatClient adapter for sampling var chatClient = server.AsSamplingChatClient(); var response = await chatClient.GetResponseAsync(messages, options, cancellationToken); return $"Summary: {response}"; } } // Client-side: Handle sampling requests using ModelContextProtocol.Client; IChatClient chatClient = /* your LLM client */; McpClientOptions options = new() { Handlers = new() { // Use extension method for simple integration SamplingHandler = chatClient.CreateSamplingHandler() } }; await using var client = await McpClient.CreateAsync(transport, options); ``` ## Progress Tracking for Long-Running Tools Tools can report progress updates to clients for long-running operations. ```csharp // Server-side: Report progress using ModelContextProtocol; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public class LongRunningTools { [McpServerTool, Description("Processes items with progress updates")] public static async Task<string> ProcessItems( McpServer server, IProgress<ProgressNotificationValue> progress, // Injected [Description("Number of items to process")] int count, CancellationToken cancellationToken) { for (int i = 0; i < count; i++) { await Task.Delay(500, cancellationToken); // Report progress progress.Report(new ProgressNotificationValue { Progress = i + 1, Total = count, Message = $"Processing item {i + 1} of {count}" }); } return $"Processed {count} items successfully"; } } // Client-side: Receive progress updates using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; await using var client = await McpClient.CreateAsync(transport); // Option 1: Use Progress<T> for specific call var progress = new Progress<ProgressNotificationValue>(p => { Console.WriteLine($"Progress: {p.Progress}/{p.Total} - {p.Message}"); }); CallToolResult result = await client.CallToolAsync( "ProcessItems", new Dictionary<string, object?> { ["count"] = 10 }, progress: progress); // Option 2: Global notification handler await using var handler = client.RegisterNotificationHandler( NotificationMethods.ProgressNotification, (notification, ct) => { var pn = notification.Params?.Deserialize<ProgressNotificationParams>(); Console.WriteLine($"Progress: {pn?.Progress.Progress}/{pn?.Progress.Total}"); return ValueTask.CompletedTask; }); ``` ## Handling Tool List Change Notifications Servers can dynamically modify tools at runtime and notify clients to refresh their tool list. ```csharp // Server-side: Send notification after modifying tools using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; // Inject McpServer and send notification await server.SendNotificationAsync( NotificationMethods.ToolListChangedNotification, new ToolListChangedNotificationParams()); // Client-side: Handle tool list changes using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; await using var client = await McpClient.CreateAsync(transport); client.RegisterNotificationHandler( NotificationMethods.ToolListChangedNotification, async (notification, cancellationToken) => { // Refresh tool list var updatedTools = await client.ListToolsAsync(cancellationToken: cancellationToken); Console.WriteLine($"Tools updated: {updatedTools.Count} tools available"); foreach (var tool in updatedTools) { Console.WriteLine($" - {tool.Name}: {tool.Description}"); } }); ``` ## Configuring Client Logging Clients can configure the logging level and receive log messages from servers. ```csharp using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; await using var client = await McpClient.CreateAsync(transport); // Check if server supports logging if (client.ServerCapabilities?.Logging is not null) { // Set desired logging level await client.SetLoggingLevelAsync(LoggingLevel.Info); // Register handler for log messages client.RegisterNotificationHandler( NotificationMethods.LoggingMessageNotification, (notification, ct) => { var logParams = notification.Params?.Deserialize<LoggingMessageNotificationParams>(); if (logParams is not null) { Console.WriteLine($"[{logParams.Level}] {logParams.Logger}: {logParams.Data}"); } return ValueTask.CompletedTask; }); } ``` ## Session Resumption with Streamable HTTP The Streamable HTTP transport supports session resumption for reconnecting after disconnects. ```csharp using ModelContextProtocol.Client; // Initial connection - save session info var transport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("https://my-mcp-server.example.com/mcp") }); await using var client = await McpClient.CreateAsync(transport); // Save session info for later string sessionId = client.SessionId!; ServerCapabilities serverCapabilities = client.ServerCapabilities!; Implementation serverInfo = client.ServerInfo!; // ... later, after disconnect ... // Resume session var resumeTransport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("https://my-mcp-server.example.com/mcp"), KnownSessionId = sessionId }); await using var resumedClient = await McpClient.ResumeSessionAsync( resumeTransport, new ResumeClientSessionOptions { ServerCapabilities = serverCapabilities, ServerInfo = serverInfo }); ``` ## Summary The MCP C# SDK provides a comprehensive implementation of the Model Context Protocol for .NET applications. The primary use cases include building MCP servers that expose tools (callable functions), prompts (reusable templates), and resources (data content) to LLM-powered applications, as well as building MCP clients that consume these primitives from any MCP-compliant server. The SDK's attribute-based discovery (`[McpServerToolType]`, `[McpServerTool]`, etc.) makes it straightforward to expose functionality with minimal boilerplate. Integration patterns center around the Microsoft.Extensions.AI abstractions, allowing MCP tools to work seamlessly with any `IChatClient` implementation. The SDK supports stdio transport for local integrations (server runs as child process), Streamable HTTP for remote servers (recommended), and legacy SSE for backward compatibility. Advanced features include sampling (servers requesting LLM completions from clients), progress tracking for long-running operations, resource subscriptions for real-time updates, and comprehensive logging support. The ASP.NET Core integration (`ModelContextProtocol.AspNetCore`) enables hosting HTTP-based MCP servers with a single `app.MapMcp()` call, handling both modern Streamable HTTP and legacy SSE clients automatically.