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.
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()} """)); }}
using ModelContextProtocol.Client;using ModelContextProtocol.Protocol;// Create transport to connect to the servervar clientTransport = new StdioClientTransport(new StdioClientTransportOptions{ Name = "Weather Server", Command = "dotnet", Arguments = ["run", "--project", "../WeatherServer"],});// Create and initialize the clientvar client = await McpClient.CreateAsync(clientTransport);Console.WriteLine("Connected to Weather Server\n");// List available toolsConsole.WriteLine("Available tools:");foreach (var tool in await client.ListToolsAsync()){ Console.WriteLine($" - {tool.Name}: {tool.Description}");}Console.WriteLine();// Call the GetForecast toolConsole.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 forecastforeach (var content in forecastResult.Content.OfType<TextContentBlock>()){ Console.WriteLine(content.Text);}Console.WriteLine();// Call the GetAlerts toolConsole.WriteLine("Getting alerts for California...");var alertsResult = await client.CallToolAsync( "GetAlerts", new Dictionary<string, object?>() { ["state"] = "CA" }, cancellationToken: CancellationToken.None);// Display the alertsforeach (var content in alertsResult.Content.OfType<TextContentBlock>()){ Console.WriteLine(content.Text);}
Connected to Weather ServerAvailable 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)...TonightTemperature: 54°FWind: 5 to 10 mph WForecast: Partly cloudy, with a low around 54...---TuesdayTemperature: 68°FWind: 5 to 10 mph WForecast: Sunny, with a high near 68...
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 serverIList<McpClientTool> tools = await client.ListToolsAsync();// Create your chat client (OpenAI, Anthropic, etc.)IChatClient chatClient = ...;// Use the tools with the chat clientvar 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.
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);