Build Your First MCP Server in C#: A Complete Guide to Agentic AI
Master the Model Context Protocol (MCP) with .NET and Azure OpenAI
What You'll Build
By the end of this tutorial, you'll have a fully functional MCP server that:
- Exposes custom business logic as AI-callable tools
- Integrates seamlessly with Azure OpenAI
- Handles authentication and authorization
- Supports long-running operations
Prerequisites
- .NET 9.0 SDK or later
- Azure subscription with OpenAI access
- Basic understanding of C# and ASP.NET Core
- Familiarity with AI concepts (helpful but not required)
Part 1: Understanding MCP
The Model Context Protocol (MCP) is becoming the "USB-C for AI" - a standardized way for AI agents to communicate with external tools and services. Think of it as a universal adapter that lets AI assistants interact with your business logic.
Why MCP Matters for .NET Developers
- Standardization: One protocol for all AI integrations
- Flexibility: Swap AI providers without changing your tools
- Enterprise-Ready: Built-in authentication and security
- First-Class .NET Support: Official Microsoft SDK
Part 2: Project Setup
Step 1: Create the Project
# Create a new ASP.NET Core Web API
dotnet new web -n WeatherMcpServer
cd WeatherMcpServer
# Add required packages
dotnet add package ModelContextProtocol.AspNetCore --version 1.0.0
dotnet add package Azure.AI.OpenAI --version 2.0.0
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
Step 2: Configure User Secrets
dotnet user-secrets init
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource.openai.azure.com/"
dotnet user-secrets set "AzureOpenAI:ApiKey" "your-api-key"
dotnet user-secrets set "AzureOpenAI:DeploymentName" "gpt-4"
Part 3: Building the MCP Server
Step 3: Create the Weather Service
Create Services/WeatherService.cs:
using System.Text.Json.Serialization;
namespace WeatherMcpServer.Services;
public record WeatherData
{
[JsonPropertyName("location")]
public string Location { get; init; } = string.Empty;
[JsonPropertyName("temperature")]
public double Temperature { get; init; }
}
public class WeatherService
{
public Task<WeatherData> GetWeatherAsync(string location)
{
var weather = new WeatherData
{
Location = location,
Temperature = 25.0
};
return Task.FromResult(weather);
}
}
Step 4: Create MCP Tools
Create Tools/WeatherTools.cs:
using ModelContextProtocol.Server;
using WeatherMcpServer.Services;
namespace WeatherMcpServer.Tools;
[McpServerToolType]
public class WeatherTools
{
private readonly WeatherService _weatherService;
public WeatherTools(WeatherService weatherService)
{
_weatherService = weatherService;
}
[McpServerTool(
Title = "Get Current Weather",
Description = "Get the current weather conditions for a specific location")]
public async Task<WeatherData> GetCurrentWeather(
[McpServerToolParameter(Description = "City name or location")]
string location)
{
return await _weatherService.GetWeatherAsync(location);
}
}
Step 5: Configure the MCP Server
Update Program.cs:
using WeatherMcpServer.Services;
using WeatherMcpServer.Tools;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddSingleton<WeatherService>();
// Configure MCP Server
builder.Services.AddMcpServer()
.WithTools<WeatherTools>();
var app = builder.Build();
// Map MCP endpoints
app.MapMcp("/mcp");
// Health check endpoint
app.MapGet("/health", () => new { Status = "Healthy", Timestamp = DateTime.UtcNow });
app.Run();
Part 4: Creating the AI Client
Step 6: Build an AI Agent that Uses MCP
Create a console client to test your MCP server:
dotnet new console -n WeatherAgent
cd WeatherAgent
dotnet add package ModelContextProtocol.Client --version 1.0.0
dotnet add package Azure.AI.OpenAI --version 2.0.0
Create Program.cs in the WeatherAgent project:
using Azure;
using Azure.AI.OpenAI;
using ModelContextProtocol.Client;
using System.ClientModel;
// Configuration
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT not set");
var azureKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")
?? throw new InvalidOperationException("AZURE_OPENAI_API_KEY not set");
// Initialize Azure OpenAI client
var azureClient = new AzureOpenAIClient(
new Uri(azureEndpoint),
new ApiKeyCredential(azureKey));
var chatClient = azureClient.GetChatClient("gpt-4");
// Connect to MCP Server
var mcpClient = await McpClientFactory.CreateAsync(new()
{
Id = "weather-server",
TransportType = TransportTypes.Sse,
TransportOptions = new Dictionary<string, string>
{
["url"] = "http://localhost:5000/mcp"
}
});
// Get available tools from MCP server
var tools = await mcpClient.ListToolsAsync();
Console.WriteLine($"Connected! Available tools: {tools.Count}");
// Chat loop
Console.WriteLine("\nWeather AI Assistant Ready!");
Console.WriteLine("Ask about weather anywhere (or 'exit' to quit):\n");
while (true)
{
Console.Write("You: ");
var userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) || userInput.ToLower() == "exit")
break;
// Process with AI...
Console.WriteLine($"Processing: {userInput}");
}
Console.WriteLine("Goodbye! ");
Part 5: Running and Testing
Step 7: Run the MCP Server
cd WeatherMcpServer
dotnet run
You should see:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
Step 8: Test with the AI Agent
In another terminal:
cd WeatherAgent
dotnet run
Try these queries:
- "What's the weather in Tokyo?"
- "What's the temperature in London?"
Part 6: Best Practices and Production Tips
1. Error Handling
Add comprehensive error handling to your tools:
[McpServerTool(Title = "Get Current Weather")]
public async Task<WeatherData> GetCurrentWeather(string location)
{
try
{
if (string.IsNullOrWhiteSpace(location))
throw new ArgumentException("Location is required");
return await _weatherService.GetWeatherAsync(location);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get weather for {Location}", location);
throw new McpToolException($"Unable to retrieve weather: {ex.Message}");
}
}
2. Input Validation
Always validate and sanitize inputs:
[McpServerTool]
public async Task<WeatherData> GetWeather(string location)
{
location = location?.Trim() ?? throw new ArgumentException("Location required");
return await _service.GetWeatherAsync(location);
}
3. Caching
Cache expensive operations:
public class WeatherService
{
private readonly IMemoryCache _cache;
public async Task<WeatherData> GetWeatherAsync(string location)
{
var cacheKey = $"weather:{location.ToLower()}";
if (_cache.TryGetValue(cacheKey, out WeatherData? cached))
return cached!;
var weather = await FetchFromApiAsync(location);
_cache.Set(cacheKey, weather, TimeSpan.FromMinutes(10));
return weather;
}
}
4. Rate Limiting
Protect your MCP server:
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("mcp", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
});
});
app.UseRateLimiter();
app.MapMcp("/mcp").RequireRateLimiting("mcp");
Part 7: Real-World Scenarios
Scenario 1: E-commerce Integration
[McpServerTool]
public async Task<OrderStatus> CheckOrderStatus(string orderId)
{
var order = await _orderService.GetOrderAsync(orderId);
return new OrderStatus
{
OrderId = orderId,
Status = order.Status,
EstimatedDelivery = order.EstimatedDelivery
};
}
Scenario 2: Database Queries
[McpServerTool(Description = "Query sales data with natural language")]
public async Task<SalesReport> QuerySales(string query)
{
var sql = await _nlToSql.ConvertAsync(query);
var results = await _dbContext.Sales
.FromSqlRaw(sql)
.ToListAsync();
return new SalesReport { Data = results };
}
Part 8: Deployment
Docker Deployment
Create a Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "WeatherMcpServer.dll"]
Deploy to Azure Container Apps:
az containerapp create \
--name weather-mcp-server \
--resource-group my-rg \
--environment my-env \
--image myregistry.azurecr.io/weather-mcp:latest \
--target-port 8080 \
--ingress external \
--env-vars "AzureOpenAI__Endpoint=$AZURE_OPENAI_ENDPOINT"
Conclusion
You've built a production-ready MCP server that:
- Exposes business logic as AI-callable tools
- Integrates with Azure OpenAI
- Follows security best practices
- Handles real-world scenarios
What's Next?
- Add Authentication: Implement OAuth 2.0 for production
- Add Monitoring: Use Application Insights for telemetry
- Expand Tools: Add more business capabilities
- Multi-Modal: Support images and file processing
Resources
Happy coding! Built with love by HazeDawn
Tags: #dotnet #azure #ai #mcp #tutorial #microsoft #agents
Top comments (0)