MCP w .NET: Jak zbudować serwer AI w ASP.NET Core
Model Context Protocol (MCP) to nowy sposób integracji AI z backendem, który daje pełną kontrolę nad tym, co model może zrobić w Twoim systemie.
W tym artykule pokażę Ci, jak krok po kroku zbudować serwer MCP w ASP.NET Core, zintegrować go z API oraz wykorzystać Aspire i MCP Inspector do debugowania i obserwowalności.
Bez teorii — czysta praktyka i kod, który możesz wykorzystać w produkcji.
🧱 Czym jest MCP i dlaczego ma znaczenie?

MCP (Model Context Protocol) to standard komunikacji między AI a Twoim backendem.
Zamiast:
- wrzucać prompty „na dziko”
- parsować tekst
- zgadywać intencje modelu
Masz:
- jasno zdefiniowane tools
- kontrolowany input/output
- przewidywalne wywołania
👉 W praktyce: AI przestaje być czarną skrzynką
🔥 MCP vs klasyczne API
| Podejście | Problem |
| REST API + prompty | brak kontroli |
| JSON parsing | podatne na błędy |
| MCP | kontrola + bezpieczeństwo |
👉 MCP działa jak anti-corruption layer dla AI
🏗 Tworzenie rozwiązania w ASP.NET Core + Aspire

Zaczynamy od stworzenia projektu w .NET Aspire.
W rezultacie powstało dla nas rozwiązanie Aspire obejmujące cztery projekty.
✔ Struktura rozwiązania
- interfejs API (WeatherForecast)
- AppHost (Aspire)
- ServiceDefaults
- interfejs internetowy Web
👉 Minimalny setup, ale pokazuje cały flow
✔ Konfiguracja Aspire
Nie będziemy korzystać z interfejsu internetowego, więc usunę ten projekt i zostawiamy tylko API, AppHost i ServiceDefaults.
W AppHost usuwamy kod odwołujący się do interfejsu internetowego.
// AppHost - to usuwamy
builder.AddProject<Projects.DevHobby_AspNetCoreMcp_Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithHttpHealthCheck("/health")
.WithReference(apiService)
.WaitFor(apiService);
W efekcie host aplikacji ma do dyspozycji jedynie interfejs API.
var builder = DistributedApplication.CreateBuilder(args);
var apiService = builder.AddProject<Projects.DevHobby_AspNetCoreMcp_ApiService>("apiservice")
.WithHttpHealthCheck("/health");
builder.Build().Run();
👉 Dzięki temu:
- mamy service discovery
- spójne health checki
- łatwą orkiestrację
W tym interfejsie API znajduje się pojedyncza, minimalna ścieżka API o nazwie WeatherForecast, która nie przyjmuje żadnych parametrów. Nasze rozwiązanie jest więc naprawdę proste.
⚙️ Implementacja serwera MCP
Dodajmy nowy, pusty projekt ASP.NET Core Empty, w którym możemy umieścić nasz serwer MCP.
Dodajemy pakiet:
ModelContextProtocol.AspNetCore
Gdzie w klasie Program wklejmy kod z folderu Samples z repozytorium csharp-SDK.
✔ Konfiguracja Program.cs
using System.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = false)
.WithTools<WeatherTools>();
// Configure HttpClientFactory for weather.gov API
builder.Services.AddHttpClient("WeatherApi", client =>
{
client.BaseAddress = new Uri("https://api.weather.gov");
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
});
var app = builder.Build();
app.MapDefaultEndpoints();
app.MapMcp();
app.Run();
Nie potrzebujemy żadnych danych telemetrycznych. Usuwamy. Wszystko to zawiera się w metodzie AddServiceDefaults i na razie zachowamy tylko narzędzie pogodowe.
💡 Dlaczego to podejście jest poprawne?
AddMcpServer()→ rejestruje MCP pipelineWithTools<T>()→ jawna kontrola nad ekspozycją funkcjiHttpClientFactory→ production-ready komunikacja
👉 Brak refleksji = większa kontrola i bezpieczeństwo
🧰 Tworzenie narzędzia (Tool) w MCP

Każda operacja dostępna dla AI to tool.
Dodajemy narzedzie WeatherTools które Pobieramy z repozytorium, folderu Tools
✔ Przykład: WeatherTools
using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;
namespace DevHobby.AspNetCoreMcp.McpServer.Tools;
[McpServerToolType]
public sealed class WeatherTools
{
private readonly IHttpClientFactory _httpClientFactory;
public WeatherTools(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[McpServerTool, Description("Get weather alerts for a US state.")]
[McpMeta("category", "weather")]
[McpMeta("dataSource", "weather.gov")]
public async Task<string> GetAlerts(
[Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
{
var client = _httpClientFactory.CreateClient("WeatherApi");
using var responseStream = await client.GetStreamAsync($"/alerts/active/area/{state}");
using var jsonDocument = await JsonDocument.ParseAsync(responseStream)
?? throw new McpException("No JSON returned from alerts endpoint");
var alerts = jsonDocument.RootElement.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.")]
[McpMeta("category", "weather")]
[McpMeta("recommendedModel", "gpt-4")]
public async Task<string> GetForecast(
[Description("Latitude of the location.")] double latitude,
[Description("Longitude of the location.")] double longitude)
{
var client = _httpClientFactory.CreateClient("WeatherApi");
var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
using var locationResponseStream = await client.GetStreamAsync(pointUrl);
using var locationDocument = await JsonDocument.ParseAsync(locationResponseStream);
var forecastUrl = locationDocument?.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
?? throw new McpException($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
using var forecastResponseStream = await client.GetStreamAsync(forecastUrl);
using var forecastDocument = await JsonDocument.ParseAsync(forecastResponseStream);
var periods = forecastDocument?.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray()
?? throw new McpException("No JSON returned from forecast endpoint");
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()}
"""));
}
}
AppHost
Teraz dodamy aby serwer MCP mógł używać funkcji wykrywania usług do łączenia się z naszym interfejsem API.
builder.AddProject<Projects.DevHobby_AspNetCoreMcp_McpServer>("mcp-server")
.WithHttpHealthCheck("/health")
.WithReference(apiService);
McpServer
W pliku Program.cs serwera MCP dodam kod, który utworzy nazwanego HttpClient dla naszej usługi API.
builder.Services.AddHttpClient("SimpleWeather", client =>
client.BaseAddress = new Uri("https://apiservice"));
GetSimpleWeather
Dodajemy nowe narzedzie dla naszego API
[McpServerTool, Description("Get current weather conditions")]
public async Task<string> GetSimpleWeather()
{
var client = _httpClientFactory.CreateClient("SimpleWeather");
var forecast = await client.GetFromJsonAsync<List<WeatherForecast>>("/weatherforecast");
if (forecast == null)
return "No weather data available.";
return JsonSerializer.Serialize(forecast, _jsonOptions);
}
🧠 Co tu się naprawdę dzieje?
- AI wywołuje tool → nie endpoint bezpośrednio
- Ty kontrolujesz:
- co może być wywołane
- jak wygląda response
- jakie są side-effecty
👉 To jest kluczowa różnica względem klasycznego API
✔ Model danych
public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary, int TemperatureF);
Dodam zmienną JsonSerializerOptions, abyśmy nie musieli tworzyć nowej opcji przy każdym żądaniu.
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
🔍 Debugowanie z Aspire i MCP Inspector

Dodajemy pakiet:
CommunityToolkit.Aspire.Hosting.McpInspector
✔ Integracja
W kodzie hosta aplikacji przechwycę odwołanie do niego w zmiennej.
var mcp = builder.AddProject<Projects.WebApplication1>("mcp-server")
.WithHttpHealthCheck("/health")
.WithReference(apiService);
Następnie możemy wywołać builder.AddMcpInspector,
builder.AddMcpInspector("mcp-inspector").WithMcpServer(mcp);
Zbudujmy i uruchommy to.
❗ Typowy problem
👉 Nie działa połączenie?
Najczęściej:
- domyślna ścieżka
/mcp - niepoprawny endpoint
✔ Fix:
.WithMcpServer(mcp, path: "");
Zbudujmy i uruchommy to po fix.
✔ Co daje MCP Inspector?
- podgląd tools
- ręczne wywołania
- trace requestów
- debug komunikacji
👉 To jest game-changer przy integracji AI
🧪 Testowanie MCP Tools

Tools to zwykłe klasy → możesz je normalnie testować
[Fact]
public async Task GetSimpleWeather_ShouldReturnData()
{
var factory = Substitute.For<IHttpClientFactory>();
var client = new HttpClient(new FakeHandler());
factory.CreateClient("SimpleWeather").Returns(client);
var tool = new WeatherTools(factory);
var result = await tool.GetSimpleWeather();
result.Should().NotBeNull();
}
✔ Dlaczego to dobre podejście?
- brak zależności od AI
- pełna kontrola testów
- szybkie unit testy
👉 MCP nie komplikuje testowania — upraszcza je
🧩 Gdzie MCP ma sens w realnym projekcie?
Nie używaj MCP do wszystkiego.
👉 Najlepsze use-case’y:
- integracje AI z domeną (orders, billing)
- orchestration procesów
- kontrola operacji (whitelist)
- systemy wymagające bezpieczeństwa
❌ Kiedy to overkill?
- proste CRUD API
- brak integracji z AI
- małe projekty
📌 Best Practices
✔ Jawnie rejestruj tools
✔ Używaj HttpClientFactory
✔ Nie expose’uj całej domeny
✔ Waliduj input
✔ Loguj każde wywołanie
🔗 Zobacz też
- C# Podstawy Programowania: Twój Pierwszy Krok w Świat Kodowania
- AI w .NET: Zostań Architektem Inteligentnych Aplikacji!
- C# Clean Architecture w Praktyce
- C# – Zbuduj Własnego Tetrisa! Kompletny Przewodnik
- 7 Dniowe Wyzwanie C# Tic Tac Toe
- C# Zbuduj Profesjonalny Portal Randkowy od Podstaw!
🚀 Podsumowanie
MCP zmienia sposób integracji AI z backendem:
👉 zamiast chaosu → kontrola
👉 zamiast promptów → kontrakty
👉 zamiast zgadywania → architektura
Jeśli budujesz systemy, które mają współpracować z AI — to jest kierunek, którego nie możesz ignorować.
📣 Call To Action
Jeśli ten materiał był pomocny:
👉 Zostaw komentarz – jakie use-case’y MCP widzisz u siebie?
👉 subskrybuj nasz kanał na YT!
👉 udostępnij artykuł komuś, kto zaczyna z programowaniem
Zobacz także — powiązane artykuły
👉 MCP w .NET (C#) – jak zbudować serwer AI krok po kroku
👉 Tworzenie klas i obiektów w C# — kompletny przewodnik
👉 Pattern Matching w C# – switch expressions i type patterns
Dołącz do “Od Zera do .NET Developera”
Zacznij swoją przygodę z programowaniem w oparciu o sprawdzone praktyki rynkowe. Wybierz kompletną ścieżkę rozwoju i zbuduj solidne fundamenty.
Dołącz do ścieżki teraz →
