How to Build and Consume MCP Servers in .NET

January 27, 2026
Listen to this blog 19m 3s
Sign in to unlock the audio version of this article
Sign in to listen

Large Language Models (LLMs) are becoming more powerful as new models are released. By default, these models are isolated and can’t access real-time data, external databases, or APIs. Model Context Protocol (MCP) provides an open, standardized way for AI models to discover and use external tools, resources, and prompts. By providing access to external tools, AI agents can integrate with various third-party services and automate processes. .NET 10 now fully supports building and using these servers.

In this guide, you’ll learn how MCP works. You’ll build two types of MCP servers in .NET. First, you’ll create a local Stdio server that exposes tools, resources, and prompts. Then, you’ll build a Streamable HTTP server that exposes a tool over HTTP. You’ll discover how you can test both servers using GitHub Copilot. Finally, you’ll integrate both into a custom .NET chat client.

Understanding the Model Context Protocol (MCP)

The Model Context Protocol (MCP) provides a standardized interface for AI models to discover and interact with external tools and data sources. MCP Servers provide the external tooling and resources that chat clients connect to via an MCP Client.

MCP Servers expose three resource types to MCP clients:

  • Tools: Define executable functions that the server exposes for the client to invoke.
  • Resources: Provide contextual data to the client that can be included in its context.
  • Prompts: Let the server expose reusable prompt templates to the client.

These resource types are invoked using JSON-RPC 2.0, using one of the following transport protocols:

  • Stdio: This transport lets you host the MCP Server as a local application on your machine. When invoked by the MCP Client, the MCP Server runs as a terminal application, accepting JSON-RPC requests via stdin, and responding with stdout. This is ideal when an MCP Server interacts with your local system, such as reading and writing to the file system.
  • Streamable HTTP: Designed to cater for hosting an MCP on a remote server. Calls to these servers are made using standard HTTP requests. An example use case is a calendar service that hosts an MCP Server, allowing MCP Clients to retrieve and create events in the user’s calendar.

.NET supports all three resource types and both transport types. Using the official C# SDK for MCP, you’ll implement both server types in the following sections.

Prerequisites

Ensure you have the .NET 10 SDK installed and a code editor or IDE that supports it.

To test your MCP servers, you can use any AI chat client with MCP support. This article demonstrates the servers using GitHub Copilot in Visual Studio Code, which supports all MCP features on its free tier. Alternatively, the MCP Inspector lets you test your MCP Server by manually invoking tools.

One of the MCP Servers you’ll implement uses OpenWeatherMap’s free Geocoding API to retrieve coordinates. To access it, create an API Key by registering for a free account and generating an API Key. Save the API Key for later.

OpenWeather API keys management page. A list of keys is shown, with a highlighted form to create a new key by entering a name.

You’ll also need an OpenAI API Key to implement the MCP Client. This is a paid API, but this article uses one of the more affordable, tool-enabled models. Create a new account, create an API Key,, and store it for later.

OpenAI dashboard API keys page, showing a list of keys and the "Create new secret key" button highlighted.

With those prerequisites in place, you can begin building your first MCP Server.

Building a Stdio MCP Server in .NET

For your first MCP Server, you’ll implement a Stdio server that runs locally and communicates with the LLM via standard input and output. This approach is well-suited for tools that interact with local data.

Your Stdio server will expose a tool that calculates the distance between two coordinates using the Haversine algorithm. It will also expose a “Home Coordinates” resource to store the user’s home address, along with a tool and a prompt template to update it.

Set Up the .NET Project

To start, open a new terminal window and run the following commands:

dotnet new console -o StdioCoordinateServer
cd ./StdioCoordinateServer

This command creates a blank Console application, which is all you need to host a Stdio server.

Now, install the MCP C# SDK using the following command:

dotnet add package ModelContextProtocol --version 0.4.0-preview.3

At the time of writing, the SDK packages are still in beta, so you need to install the specific prerelease version.

You must also install the Microsoft.Extensions.Hosting NuGet package to register the MCP-related services in your project:

dotnet add package Microsoft.Extensions.Hosting --version 10.0.0

With packages installed, you can start developing the server.

Register the Necessary Services

To expose the Stdio MCP Server, register the MCP server in your service container and instruct it to use the Stdio transport in your Program.cs file:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport();
  
await builder.Build().RunAsync();

In the snippet above, .AddMcpServer() registers the services, and .WithStdioServerTransport() instructs the MCP Server to use the Stdio transport.

Create the Haversine Calculation MCP Tool

Open your project in your preferred code editor or IDE. Create a new folder in your project called Tools and then paste the following code into a file called HaversineDistanceTool.cs:

using System.ComponentModel;
using ModelContextProtocol.Server;

namespace StdioCoordinateServer;

[McpServerToolType]
public class HaversineDistanceTool
{
    private const double EarthRadiusInMeters = 6378100;

    private double ToRadians(double degrees) => degrees * Math.PI / 180;

    [McpServerTool(Name = "calculate_haversine_distance", Title = "Calculate Haversine Distance")]
    [Description("Calculates the Haversine distance in meters between two geographic coordinates.")]
    public double CalculateHaversineDistance(double latitude1, double longitude1, double latitude2, double longitude2)
    {
        var firstLatitudeInRadians = ToRadians(latitude1);
        var firstLongitudeInRadians = ToRadians(longitude1);
        var secondLatitudeInRadians = ToRadians(latitude2);
        var secondLongitudeInRadians = ToRadians(longitude2);

        var dLat = secondLatitudeInRadians - firstLatitudeInRadians;
        var dLon = secondLongitudeInRadians - firstLongitudeInRadians;

        var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                Math.Cos(firstLatitudeInRadians) * Math.Cos(secondLatitudeInRadians) *
                Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
        var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));

        return EarthRadiusInMeters * c;
    }
}

The snippet above defines an MCP tool. The [McpServerToolType] attribute is added to the class so that it can be scanned for MCP tools.

The CalculateHaversineDistance method implements the logic for calculating the distance between coordinates. To expose the tool over MCP, add the [McpServerTool] attribute to the method, which defines the function name and a user-friendly title. The [Description] attributes also help the LLM understand the tool’s functionality.

Lastly, configure the MCP SDK to scan your project for MCP Tools by adding the following line to your MCP Server registration in Program.cs:

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    // Scan for MCP Tools
    .WithToolsFromAssembly();

Your tool will now be exposed when you run your MCP Server. Before testing it, let’s implement the remaining functionality for this server.

Create the Home Coordinates MCP Resource

Now, you’ll create an MCP resource to store the user’s home coordinates. The user can add this resource as a reference in their chat. The LLM will then be able to retrieve the resource value (i.e., the coordinates) whenever it decides it’s relevant.

First, create a record to store a set of precise coordinates by creating a Coordinates.cs file with the following code:

namespace StdioCoordinateServer;

public record Coordinates(decimal Latitude, decimal Longitude);

Now, create a new Resources folder and, inside it, make a HomeAddressResource.cs class file. Paste the following code inside:

using System.ComponentModel;
using System.Text.Json;
using ModelContextProtocol.Server;

namespace StdioCoordinateServer;

[McpServerResourceType]
public class HomeAddressResource
{
    private readonly JsonSerializerOptions _jsonSerializerOptions = new(JsonSerializerDefaults.Web);
  
    public static Coordinates? HomeAddressCoordinates { get; set; }
  
    [McpServerResource(Name = "home_address_coordinates", Title = "Home Address Coordinates", MimeType = "application/json")]
    [Description("The user's home address coordinates, if it's set.")]
    public string GetHomeAddress()
    {
        // Return error if not set
        if (HomeAddressCoordinates is null)
        {
            return JsonSerializer.Serialize(new
                {
                    Error = "The user's home address has not been set"
                },
                _jsonSerializerOptions);
        }
      
        // Return coordinates as JSON if set
        return JsonSerializer.Serialize(HomeAddressCoordinates, _jsonSerializerOptions);
    }
}

Just as you did with the tool, you must annotate the containing class with the [McpServerResourceType] attribute so the SDK knows to look for resources within it.

The user’s home address is stored in the HomeAddressCoordinates static property so it can be persisted between chat messages. In a real application, you would likely store this in persistent storage, such as a SQLite database or text file.

The GetHomeAddress method, annotated with [McpServerResource], retrieves the resource value. It checks if the address has been set and returns it. Otherwise, it returns a descriptive error message to the LLM.

To expose this resource over the MCP Server, update your Program.cs file with the following line:

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly()
    // Scan for MCP Resources
    .WithResourcesFromAssembly();

Right now, there’s no way to set a home address. To support this, add a new UpdateHomeAddressTool.cs file to your Tools directory with the following code:

using System.ComponentModel;
using ModelContextProtocol.Server;

namespace StdioCoordinateServer;

[McpServerToolType]
public class UpdateHomeAddressTool
{
    [McpServerTool(Name = "update_home_address_coordinates", Title = "Update Home Address Coordinates")]
    [Description("Updates the user's home address coordinates.")]
    public void UpdateHomeAddress(decimal latitude, decimal longitude)
    {
        HomeAddressResource.HomeAddressCoordinates = new Coordinates(latitude, longitude);
    }
}

This tool’s implementation is straightforward: it retrieves the coordinates from the LLM and updates the resource’s property. Since tool scanning is already configured, the MCP SDK will automatically expose this tool.

Create an MCP Prompt Template

Instead of having the user manually write a message to update their home address, you can create a prompt template with placeholders that the user can populate and send.

Create a new Prompts folder in your project. Inside the directory, create a file called UpdateHomeAddressPrompt.cs and paste the following code:

using System.ComponentModel;
using ModelContextProtocol.Server;

namespace StdioCoordinateServer;

[McpServerPromptType]
public class UpdateHomeAddressPrompt
{
    [McpServerPrompt(Name = "update_home_address_prompt", Title = "Update Home Address Prompt")]
    [Description("A prompt to update the user's home address.")]
    public string GetUpdateHomeAddressPrompt(decimal latitude, decimal longitude) =>
        $"Please update the user's home address to latitude {latitude} and longitude {longitude}.";
}

As with the MCP tool and resource, annotate the class with the [McpServerPromptType] attribute so the MCP SDK scans it for prompts. The prompt is implemented as a method, with parameters as placeholder values that the user can specify. The method uses those placeholders to construct the final prompt.

As with MCP tools and resources, update the Program.cs file so it scans and exposes prompts in the project:

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly()
    .WithResourcesFromAssembly()
    // Scans for MCP Prompts
    .WithPromptsFromAssembly();

You’re now ready to test your Stdio server in GitHub Copilot.

Testing the MCP Server

To test the MCP Server, create a new directory and open Visual Studio Code in that directory:

mkdir mcp-server-test
code ./mcp-server-test

To configure MCP servers in Visual Studio Code, you need to create an MCP configuration file in .vscode/mcp.json. Once created, paste the following JSON template:

{
    "servers": {
        "stdio-coordinate-server": {
            "type": "stdio",
            "command": "dotnet",
            "args": [
                "run",
                "--project",
                "<PATH_TO_PROJECT>/StdioCoordinateServer/StdioCoordinateServer.csproj"
            ]
        }
    },
    "inputs": []
}

Replace <PATH_TO_PROJECT> with the absolute path to your Stdio MCP Server project file.

Now, open GitHub Copilot in Visual Studio Code and click on the “Configure Tools…” icon. In the menu that appears, scroll down to the stdio-coordinate-server and select all the tools. Click OK to save.

VS Code UI showing the "Configure Tools" dialog for an AI agent. The user is selecting the "stdio-coordinate-server" tool.

You can start by updating your home address coordinates using the MCP Prompt you defined. You access prompts in Visual Studio Code’s GitHub Copilot with a forward slash:

An AI agent in a code editor returns a latitude value and prompts the user to either 'Insert as text' or 'Run as Command'.

Pass in the latitude and longitude parameters when prompted. You’ll then see a prepopulated message appear, ready to send immediately:

A code editor's AI chat agent, 'Build with Agent', prompted to update a user's address coordinates in a JSON file.

When prompted to run the MCP Tool, click Allow:

Code editor chat UI where an AI agent requests to run a tool to update coordinates, showing an Allow/Skip confirmation prompt.

Your home coordinates are updated! Create a new chat and click on the Add Context button. Select MCP Resources in the menu that appears:

VS Code chat UI where a user adds context to an AI prompt by searching for files, with "MCP Resources" highlighted.

You’ll see your server’s MCP Resource appear. Select it to add it to your chat context:

A code editor's AI chat interface with a 'Build with Agent' prompt and a dropdown to select a resource for the agent to use.

Send a message asking what your home coordinates are:

An AI chat in a code editor answers "What are my home coordinates?" with latitude and longitude for central London.

Notice how the original coordinates you provided are returned in the response because the MCP Resource is included in the LLM’s context.

Building a Streamable HTTP MCP Server in .NET

The previous MCP Server used the Stdio transport to communicate with the LLM. However, this transport is not ideal if you want to offer a hosted MCP Server as a service. The Streamable HTTP transport is designed for these scenarios.

In this demonstration, you’ll build a Streamable HTTP MCP Server that accepts a location name and uses the OpenWeatherMap Geocoding API to retrieve its coordinates. These coordinates can then be used with the Stdio server you just built.

Set Up the .NET Project

Open a new terminal window and create a new C# Web API project using the following command:

dotnet new webapi -o StreamableHttpGeocodingServer
cd ./StreamableHttpGeocodingServer

This time you’ll install the ModelContextProtocol.AspNetCore package, which lets you expose MCP endpoints in ASP.NET:

dotnet add package ModelContextProtocol.AspNetCore --version 0.4.0-preview.3

Setup App Settings

Open the project in your preferred editor. Before diving into the code, open the appsettings.json file and add the following properties:

{
  // Existing config...
  "OpenWeatherMap": {
    "BaseUrl": "https://api.openweathermap.org",
    "ApiKey": "<YOUR_API_KEY>"
  }
}

Replace <YOUR_API_KEY> with the API Key you created earlier. Since this is a sensitive value, you would typically store it in a secrets store, such as AWS Secrets Manager or Azure Key Vault, instead of in appsettings.json. For convenience, this demo stores the API Key here.

Register the Necessary Services

With your configuration out of the way, you can proceed with setting up the necessary services by replacing the entire Program.cs file with the snippet below:

var builder = WebApplication.CreateBuilder(args);

// Register HttpClient to call OpenWeatherMap API
builder.Services.AddHttpClient("OpenWeatherMap", client =>
{
    var baseUrl = builder.Configuration["OpenWeatherMap:BaseUrl"] ??
                  throw new InvalidOperationException("OpenWeatherMap Base URL not configured");
    var apiKey = builder.Configuration["OpenWeatherMap:ApiKey"] ??
                 throw new InvalidOperationException("OpenWeatherMap API key not configured.");

    client.BaseAddress = new Uri(baseUrl);
    client.DefaultRequestHeaders.Add("x-api-key", apiKey);
});

// Register MCP Server with HTTP Transport
builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp();

await app.RunAsync();

The first service registration adds the IHttpClientFactory so the DI container can manage HttpClient instances, which you’ll use when calling the OpenWeatherMap API.

Next, the MCP Server SDK is registered. This time, you call .WithHttpTransport() instead of .WithStdioServerTransport(). The .WithToolsFromAssembly() method is also called so your geocoding tool gets exposed by the server.

The app.MapMcp() method registers the relevant HTTP endpoints with the MCP Server.

Create the Geocoding MCP Tool

Create a new Tools folder to store your MCP Tools. Inside, create a C# class file called LocationCoordinateLookupTool.cs and paste the following code inside:

using System.ComponentModel;
using System.Text.Json;
using ModelContextProtocol.Server;

namespace StreamableHttpGeocodingServer;

// Record to deserialize the OpenWeatherMap Geocoding results
public record GeocodingSearchResult(string Name, decimal Lat, decimal Lon, string Country);

[McpServerToolType]
public class LocationCoordinateLookupTool(IHttpClientFactory httpClientFactory)
{
    private readonly HttpClient _httpClient = httpClientFactory.CreateClient("OpenWeatherMap");
    private readonly JsonSerializerOptions _jsonSerializerOptions = new(JsonSerializerDefaults.Web);
  
    [McpServerTool, Description("Search for a location and retrieve its coordinates")]
    public async Task<string> SearchLocationCoordinates(
        [Description("Query containing the city name and ISO 3166 country code")] string query)
    {
        var response = await _httpClient.GetFromJsonAsync<GeocodingSearchResult[]>($"/geo/1.0/direct?q={query}&limit=1");

        var result = response?.FirstOrDefault();
        if (result is null)
        {
            return JsonSerializer.Serialize(new
            {
                Error = "Could not find coordinates for the specified location."
            }, _jsonSerializerOptions);
        }

        return JsonSerializer.Serialize(new
        {
            result.Name, 
            result.Country, 
            Latitude = result.Lat, 
            Longitude = result.Lon
        }, _jsonSerializerOptions);
    }
}

The IHttpClientFactory configured in the Program.cs file is injected into the tool class and the OpenWeatherMap client is retrievd. The class has the [McpSeverToolType] attribute, and the method also has the appropriate [McpServerTool] attribute, very much like the Stdio server.

You might have noticed the tool parameter also has a [Description] attribute. This gives the model more information on what should be specified for the query parameter.

The tool takes the incoming query and tries to retrieve its coordinates using OpenWeatherMap. If coordinates are found, they’re returned; otherwise, a descriptive error message is returned.

Testing the MCP Server

You can now run your Streamable HTTP MCP Server using the following command:

dotnet run --project ./StreamableHttpGeocodingServer.csproj

Note the server’s URL, as you’ll need it when setting up the MCP Server in GitHub Copilot:

Console output for a .NET application starting up. The listening URL, http://localhost:5171, is highlighted.

Return to your Visual Studio Code instance and open the .vscode/mcp.json file you previously configured the Stdio server in. Add the following JSON object after the existing server configuration:

{
    "servers": {
        // ...
        "streamable-http-geocoding-server": {
            "type": "http",
            "url": "<MCP_SERVER_BASE_URL>"
        }
    },
    "inputs": []
}

Start the MCP Server from the mcp.json file and add the MCP Tool to your current chat session.

VS Code UI showing the 'Configure Tools' dialog for an AI agent, with a list of tools over a JSON configuration file.

Start by setting your home coordinates again. Instead of specifying exact coordinates, you can name a city, and the model will use the new MCP Server to retrieve and store its coordinates.

When prompted to run the new MCP Tool, allow it to run.

AI chat in a code editor asks to run a tool to find coordinates for Paris, prompting the user with "Allow" and "Skip" buttons.

The AI model will then make another call to your Stdio MCP Server to save the coordinates retrieved from the Geocoding MCP Tool. Allow it to run:

AI chat in a code editor asking for permission to run a command that updates a user's home address with new coordinates.

Add the Home Address MCP Resource to your chat so that it’s in your chat context:

AI agent in a chat UI updating a home address. It selects an MCP resource, runs two commands, and confirms the update.

You can now ask it to calculate the distance from your home address to another location.

Grant it permission to look up the coordinates of the other location:

AI assistant in a code editor asking permission to run a  command with an Allow/Skip prompt.

It will then request permission to call the Calculate Haversine Distance MCP Tool you developed previously, using the two sets of coordinates:

An AI agent in a code editor chat requests permission to run a script to calculate geographic distance using JSON coordinates

After running the tool, it will output the result:

An AI agent in a chat UI calculates the distance between Paris and New York, showing the commands it ran to find the answer.

Notice how the AI model selected the correct MCP tool to run and orchestrated them in the correct order to fulfill the user’s request.

Building an MCP Client in .NET

You’ve seen how to build a Stdio and Streamable HTTP MCP Server in .NET. Until now, you’ve been testing the servers in Visual Studio Code’s GitHub Copilot. This section demonstrates how to invoke these MCP Tools from a .NET application using an OpenAI model.

Set Up the .NET Project

You’ll build a simple chatbot in a Console application. Start by running the following command to create the new project:

dotnet new console -o McpClient
cd ./McpClient

Install a few AI-related packages to scaffold the AI chat and MCP Tools:

dotnet add package Microsoft.Extensions.AI --version 10.0.0
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.0.0-preview.1.25560.10
dotnet add package ModelContextProtocol.Core --version 0.4.0-preview.3

The Microsoft.Extensions.* packages let you interface with the OpenAI API, and the ModelContextProtocol.Core package provides the necessary classes to connect the chat to your MCP Tools.

Also, install the following packages to help configure the application:

dotnet add package Microsoft.Extensions.Hosting --version 10.0.0
dotnet add package Microsoft.Extensions.Configuration --version 10.0.0
dotnet add package Microsoft.Extensions.Configuration.Json --version 10.0.0

Setup App Settings

Since you’ll be calling the OpenAI API and your existing MCP Tools, you need to store some configuration settings. Create a new appsettings.json file in your project directory and add the following properties:

{
    "OpenAI": {
        "ApiKey": "<OPENAI_API_KEY>",
        "ModelName": "gpt-5-mini-2025-08-07"
    },
    "StdioCoordinateServer": {
        "ProjectPath": "<PATH_TO_STDIO_PROJECT>/StdioCoordinateServer.csproj"
    },
    "StreamableHttpGeocodingServer": {
        "BaseUrl": "<GEOCODING_SERVER_BASE_URL>"
    }
}

The configuration specifies the GPT-5 Mini model, an affordable LLM from OpenAI that supports function calls. Replace <OPENAI_API_KEY> with the API Key you created at the beginning of this guide.

For the MCP Server configuration, replace <PATH_TO_STDIO_PROJECT> with the actual path to the Stdio MCP Server project. Also, replace <GEOCODING_SERVER_BASE_URL> with the base URL for your Streamable HTTP MCP Server.

To ensure the appsettings.json file is copied to your build folder, add the following to your McpClient.csproj file:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <!-- App Properties -->
    </PropertyGroup>
    <ItemGroup>
      <!-- Dependencies -->
    </ItemGroup>
    <!-- Add the section below -->
    <ItemGroup>
      <None Update="appsettings.json">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      </None>
    </ItemGroup>
</Project>

Scaffold the Console Application

Open the Program.cs file and replace the code with the following:

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using ModelContextProtocol.Client;
using OpenAI;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;

var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .Build();

The code above loads the appsettings.json file, allowing you to access the OpenAI and MCP Server configurations.

Now, create a new chat client by appending the file with the following:

var apiKey = config["OpenAI:ApiKey"];
var modelName = config["OpenAI:ModelName"];
var chatClient = new ChatClientBuilder(new OpenAIClient(apiKey).GetChatClient(modelName).AsIChatClient())
    .UseFunctionInvocation()
    .Build();

Notice how you add .UseFunctionInvocation() to instruct the chat to use functions exposed by your MCP Servers.

Next, configure the MCP Servers by adding the following code to the bottom of the Program.cs file:

var localMcpServer = await McpClient.CreateAsync(new StdioClientTransport(new()
{
    Command = "dotnet",
    Arguments = ["run", "--project", config["StdioCoordinateServer:ProjectPath"]],
    Name = "stdio-coordinate-server"
}));

var remoteMcpServer = await McpClient.CreateAsync(new HttpClientTransport(new()
{
    Endpoint = new Uri(config["StreamableHttpGeocodingServer:BaseUrl"]),
    Name = "streamable-http-geocoding-server"
}));


List<AITool> availableTools = [..await localMcpServer.ListToolsAsync(), ..await remoteMcpServer.ListToolsAsync()];

The snippet above creates an MCP Client for each of your MCP Servers. After adding the servers, you compile a list of all the available tools on both servers.

You can now add an infinite loop at the end of the Program.cs file that prompts the user for a message and responds until you terminate the process:

var messages = new List<ChatMessage>();
while (true)
{
    Console.Write("User: ");
    messages.Add(new ChatMessage(ChatRole.User, Console.ReadLine()));

    Console.Write("\nAI: ");
    List<ChatResponseUpdate> updates = [];
    await foreach (var update in chatClient.GetStreamingResponseAsync(messages, new() { Tools = availableTools }))
    {
        Console.Write(update);
        updates.Add(update);
    }
    Console.WriteLine("\n");

    messages.AddMessages(updates);
}

The snippet starts by creating a list of chat messages. This lets you store state across messages, since LLMs are stateless. The loop prompts the user for a message and sends it to the OpenAI API with the available tools. The response is streamed to the console and added to the message history.

Testing the .NET MCP Client

To test the client, run the project using the following terminal command:

dotnet run --project ./McpClient.csproj

You’ll be prompted to enter a message. For example, ask for the distance between two cities:

Terminal showing an AI's answer to the distance between DC and NYC: ~204 miles, calculated with the Haversine formula.

If you watch the logs of your other projects, you’ll see that the chat client makes calls to the MCP Servers to retrieve the coordinates of the two cities, then calculates the distance between them.

Conclusion

Congratulations! You’ve created a Stdio and Streamable HTTP MCP Server with MCP Tools, Resources, and Prompts. You saw how easy it is to test each server using GitHub Copilot in Visual Studio Code. By connecting to multiple MCP Servers, the AI model can orchestrate calls appropriately. Finally, you created your own chat client in .NET and configured it to call your two MCP Servers.

While this guide covered how to get started with MCP in .NET, there is much more functionality available. MCP supports authorization, giving you control over who can access the hosted server. There are also operations a server can call on the client, such as Sampling and File System Roots, that are worth exploring further.

Given how rapidly MCP and agentic AI are being adopted, it’s definitely worth exploring further. Check out the Dometrain courses below, which dive deep into Agentic AI and MCP.

About the Author

Ivan Kahl

Ivan Kahl

Ivan Kahl is a Software Engineer and Technical Writer specializing in .NET. His expertise also includes in-depth exposure to web technologies like React and cloud platforms such as AWS and Azure. He’s designed and built complex systems, often with Domain-Driven Design and Event-Driven Architecture. He has written on various technologies for global companies as a freelancer and runs a personal blog sharing his learnings.

View all courses by Ivan Kahl

What's New

Leaderboard
feature

Leaderboard

See how you stack up against other learners. Track your progress, climb the ranks, and compete with the Dometrain community.

Learn more about Leaderboard
Hands-On: Learn PostgreSQL
course

Hands-On: Learn PostgreSQL

Learn PostgreSQL through hands-on coding exercises. Practice what you learn with interactive challenges designed for every level.

Learn more about Hands-On: Learn PostgreSQL
Free Hands-On: C# for Beginners
course

Free Hands-On: C# for Beginners

Learn C# through hands-on coding exercises. Practice what you learn with interactive challenges designed for everyone, from beginners to experts.

Learn more about Free Hands-On: C# for Beginners
Getting Started: AI for .NET Developers
course

Getting Started: AI for .NET Developers

Get started with integrating AI into your .NET applications effectively using the latest LLM best practices.

Learn more about Getting Started: AI for .NET Developers
Getting Started: Building .NET Applications on AWS
course

Getting Started: Building .NET Applications on AWS

Learn how to build and deploy .NET applications on AWS using CDK, Lambda, DynamoDB, S3, and more.

Learn more about Getting Started: Building .NET Applications on AWS
What's new in C# 14
blog

What's new in C# 14

This guide covers every new C# 14 feature, explains its benefits, and provides practical code examples to help you navigate how you can use them.

Learn more about What's new in C# 14
Let's Build It: AI Chatbot with RAG in .NET Using Your Data
course

Let's Build It: AI Chatbot with RAG in .NET Using Your Data

Build a Retrieval-Augmented Generation (RAG) chatbot that can answer questions using your data.

Learn more about Let's Build It: AI Chatbot with RAG in .NET Using Your Data
From Zero to Hero: SignalR in .NET
course

From Zero to Hero: SignalR in .NET

Enable enterprise-grade real-time communication for your web apps with SignalR.

Learn more about From Zero to Hero: SignalR in .NET
Deep Dive: Solution Architecture
course

Deep Dive: Solution Architecture

Master solution architecture and turn business needs into scalable, maintainable systems.

Learn more about Deep Dive: Solution Architecture
Migrating: ASP.NET Web APIs to ASP.NET Core
course

Migrating: ASP.NET Web APIs to ASP.NET Core

A step-by-step process to migrate ASP.NET Web APIs from .NET Framework to ASP.NET Core.

Learn more about Migrating: ASP.NET Web APIs to ASP.NET Core
Getting Started: Caching in .NET
course

Getting Started: Caching in .NET

Let's make the hardest thing in programming easy for .NET software engineers.

Learn more about Getting Started: Caching in .NET
From Zero to Hero: Testing with xUnit in C#
course

From Zero to Hero: Testing with xUnit in C#

Learn how to test any codebase in .NET with the latest version of xUnit, the industry-standard testing library.

Learn more about From Zero to Hero: Testing with xUnit in C#
Create a ChatGPT Console AI Chatbot in C#
blog

Create a ChatGPT Console AI Chatbot in C#

This walkthrough is your hands-on entry point to create a basic C# console application that talks to ChatGPT using the OpenAI API.

Learn more about Create a ChatGPT Console AI Chatbot in C#