Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/modelcontextprotocol/csharp-sdk/llms.txt

Use this file to discover all available pages before exploring further.

Quick Start

This guide walks you through building a complete MCP client and server. You’ll create a weather server that exposes tools to get forecasts and alerts, then build a client to call those tools.

Prerequisites

  • .NET 8.0 SDK or later
  • A code editor (Visual Studio, VS Code, or Rider)
  • Basic familiarity with C# and .NET

Build an MCP Server

You’ll create a stdio-based MCP server that exposes weather tools. This server will run as a separate process that clients can connect to.

Step 1: Create a New Project

1

Create a console application

dotnet new console -n WeatherServer
cd WeatherServer
2

Add required packages

dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting

Step 2: Configure the Server

Replace the contents of Program.cs with this code:
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using WeatherServer.Tools;
using System.Net.Http.Headers;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<WeatherTools>();

builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

using var httpClient = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
builder.Services.AddSingleton(httpClient);

await builder.Build().RunAsync();
The LogToStandardErrorThreshold setting ensures all logs go to stderr, keeping stdout clear for MCP protocol messages.

Step 3: Create Weather Tools

Create a new directory called Tools and add WeatherTools.cs:
Tools/WeatherTools.cs
using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;

namespace WeatherServer.Tools;

[McpServerToolType]
public sealed class WeatherTools
{
    [McpServerTool, Description("Get weather alerts for a US state.")]
    public static async Task<string> GetAlerts(
        HttpClient client,
        [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
    {
        using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
        var jsonElement = jsonDocument.RootElement;
        var alerts = jsonElement.GetProperty("features").EnumerateArray();

        if (!alerts.Any())
        {
            return "No active alerts for this state.";
        }

        return string.Join("\n--\n", alerts.Select(alert =>
        {
            JsonElement properties = alert.GetProperty("properties");
            return $"""
                    Event: {properties.GetProperty("event").GetString()}
                    Area: {properties.GetProperty("areaDesc").GetString()}
                    Severity: {properties.GetProperty("severity").GetString()}
                    Description: {properties.GetProperty("description").GetString()}
                    Instruction: {properties.GetProperty("instruction").GetString()}
                    """;
        }));
    }

    [McpServerTool, Description("Get weather forecast for a location.")]
    public static async Task<string> GetForecast(
        HttpClient client,
        [Description("Latitude of the location.")] double latitude,
        [Description("Longitude of the location.")] double longitude)
    {
        var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
        using var locationDocument = await client.ReadJsonDocumentAsync(pointUrl);
        var forecastUrl = locationDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
            ?? throw new McpException($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");

        using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
        var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

        return string.Join("\n---\n", periods.Select(period => $"""
                {period.GetProperty("name").GetString()}
                Temperature: {period.GetProperty("temperature").GetInt32()}°F
                Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
                Forecast: {period.GetProperty("detailedForecast").GetString()}
                """));
    }
}

How It Works

1

Attribute-based discovery

The [McpServerToolType] attribute marks the class containing tools, and [McpServerTool] marks each method as an exposed tool.
2

Dependency injection

The HttpClient parameter is automatically injected from the service container when tools are called.
3

Description attributes

The Description attribute provides documentation for tools and parameters, helping LLMs understand how to use them.

Step 4: Run the Server

dotnet run
Your server is now running and ready to accept connections over stdio!

Build an MCP Client

Now you’ll create a client that connects to your weather server and calls its tools.

Step 1: Create a Client Project

1

Create a new console application

dotnet new console -n WeatherClient
cd WeatherClient
2

Add the required package

dotnet add package ModelContextProtocol

Step 2: Create the Client

Replace Program.cs with this code:
Program.cs
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

// Create transport to connect to the server
var clientTransport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "Weather Server",
    Command = "dotnet",
    Arguments = ["run", "--project", "../WeatherServer"],
});

// Create and initialize the client
var client = await McpClient.CreateAsync(clientTransport);

Console.WriteLine("Connected to Weather Server\n");

// List available tools
Console.WriteLine("Available tools:");
foreach (var tool in await client.ListToolsAsync())
{
    Console.WriteLine($"  - {tool.Name}: {tool.Description}");
}

Console.WriteLine();

// Call the GetForecast tool
Console.WriteLine("Getting forecast for San Francisco (37.7749, -122.4194)...");
var forecastResult = await client.CallToolAsync(
    "GetForecast",
    new Dictionary<string, object?>()
    {
        ["latitude"] = 37.7749,
        ["longitude"] = -122.4194
    },
    cancellationToken: CancellationToken.None);

// Display the forecast
foreach (var content in forecastResult.Content.OfType<TextContentBlock>())
{
    Console.WriteLine(content.Text);
}

Console.WriteLine();

// Call the GetAlerts tool
Console.WriteLine("Getting alerts for California...");
var alertsResult = await client.CallToolAsync(
    "GetAlerts",
    new Dictionary<string, object?>() { ["state"] = "CA" },
    cancellationToken: CancellationToken.None);

// Display the alerts
foreach (var content in alertsResult.Content.OfType<TextContentBlock>())
{
    Console.WriteLine(content.Text);
}

How It Works

1

Create a transport

StdioClientTransport launches the server as a child process and communicates over stdin/stdout.
2

Initialize the client

McpClient.CreateAsync() establishes the connection and performs the initialization handshake.
3

List tools

ListToolsAsync() retrieves all tools exposed by the server, including their names and descriptions.
4

Call tools

CallToolAsync() invokes a tool with parameters and returns the result as content blocks.

Step 3: Run the Client

dotnet run
You should see output similar to:
Connected to Weather Server

Available tools:
  - GetForecast: Get weather forecast for a location.
  - GetAlerts: Get weather alerts for a US state.

Getting forecast for San Francisco (37.7749, -122.4194)...
Tonight
Temperature: 54°F
Wind: 5 to 10 mph W
Forecast: Partly cloudy, with a low around 54...

---

Tuesday
Temperature: 68°F
Wind: 5 to 10 mph W
Forecast: Sunny, with a high near 68...

Using Tools with an LLM

MCP tools inherit from AIFunction and work seamlessly with any IChatClient implementation from Microsoft.Extensions.AI:
using Microsoft.Extensions.AI;

// Get available tools from the MCP server
IList<McpClientTool> tools = await client.ListToolsAsync();

// Create your chat client (OpenAI, Anthropic, etc.)
IChatClient chatClient = ...;

// Use the tools with the chat client
var response = await chatClient.GetResponseAsync(
    "What's the weather forecast for Seattle?",
    new ChatOptions { Tools = [.. tools] });

Console.WriteLine(response.Message.Text);
The chat client will automatically invoke the appropriate MCP tools based on the user’s query.

Build an HTTP Server (Optional)

For HTTP-based MCP servers, use the ModelContextProtocol.AspNetCore package:
1

Create a web project

dotnet new web -n WeatherServerHttp
cd WeatherServerHttp
2

Add the package

dotnet add package ModelContextProtocol.AspNetCore
3

Configure the server

Program.cs
using ModelContextProtocol.Server;
using System.ComponentModel;

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

var app = builder.Build();
app.MapMcp();
app.Run("http://localhost:3001");

[McpServerToolType]
public static class EchoTool
{
    [McpServerTool, Description("Echoes the message back to the client.")]
    public static string Echo(string message) => $"hello {message}";
}
4

Connect from a client

var clientTransport = new HttpClientTransport(new()
{
    Endpoint = new Uri("http://localhost:3001")
});
var client = await McpClient.CreateAsync(clientTransport);

Next Steps

Browse Samples

Explore complete example projects including file servers, long-running tasks, and more

API Reference

Dive deep into the SDK’s API documentation

MCP Specification

Learn about the Model Context Protocol specification

Project Template

Use the official MCP Server project template for faster scaffolding

Troubleshooting

Make sure you’re logging to stderr in your server configuration:
builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});
MCP uses stdout for protocol messages, so all application logs must go to stderr.
Verify that:
  • The server path in Command and Arguments is correct
  • The server project builds successfully with dotnet build
  • You can run the server manually with dotnet run
Ensure that:
  • Your tool class has the [McpServerToolType] attribute
  • Each tool method has the [McpServerTool] attribute
  • You’ve called WithToolsFromAssembly() or WithTools<T>() during server setup
For HTTP servers:
  • Make sure the server is running before the client connects
  • Verify the endpoint URL matches exactly (including port number)
  • Check that no firewall is blocking the connection

Build docs developers (and LLMs) love