New Academy logosu
Yazılım Geliştirme

ASP.NET Core ile MCP Sunucusu Yazmak: TypeScript Olmayanların Rehberi

C# ve ASP.NET Core ile durumsuz bir MCP sunucusunun nasıl kurulacağını; araç tanımları, JSON Schema, kimlik doğrulama, ölçekleme ve gözlemlenebilirlik üzerinden anlatıyoruz.

👨🏻‍🏫11 dk okuma
Yazılım Geliştirme

MCP ekosisteminde dolaşırken şöyle bir his oluşuyor: bu iş TypeScript ve Python geliştiricileri için tasarlanmış. Örnekler npx ile başlıyor, blog yazıları uv kuruyor, konferans sunumlarında ekranda Node var.

Oysa Türkiye'deki kurumsal yazılım dünyasının önemli bir kısmı .NET üzerinde koşuyor. Bankada, sigortada, lojistikte, kamuya iş yapan entegratörlerde on beş yıllık ASP.NET kod tabanları var. O kod tabanının yanına Node.js süreci koymak isteyen kimse yok.

İyi haber: C# resmî SDK'lardan biri ve yeni durumsuz protokol modeli ASP.NET Core ile MCP sunucusu barındırmayı gerçekten kolaylaştırdı. Bu yazıda sıfırdan çalışan bir sunucu kuruyoruz, sonra kurumsal ortamda karşınıza çıkacak şeyleri konuşuyoruz.

Neden .NET tarafında hayat kolaylaştı

Eski MCP modelinde sunucu, istemciye bir oturum kimliği veriyordu ve sonraki her istek o kimliği taşıyordu. Bu, uygulamayı üreten instance'a bağlıyordu. App Service'te üç instance'a scale ettiğinizde ya sticky routing kurmanız ya da paylaşımlı bir oturum deposu yazmanız gerekiyordu.

28 Temmuz 2026 için hazırlanan sürüm adayıyla protokol katmanındaki oturum kaldırılıyor. initialize handshake'i yok, Mcp-Session-Id başlığı yok. Her istek kendi kendine yeten bir POST.

.NET tarafında bunun anlamı şu: MCP sunucunuz artık sıradan bir stateless Web API. Aynı health check'ler, aynı yatay ölçekleme, aynı deployment slot mantığı, aynı Application Insights kurulumu. Özel bir şey yok. Ve bu iyi bir şey.

Kurulum

Boş bir web projesiyle başlıyoruz.

dotnet new web -n Kurumsal.Mcp
cd Kurumsal.Mcp
dotnet add package ModelContextProtocol.AspNetCore --prerelease

Prerelease bayrağı şu an gerekli, çünkü C# SDK'sı 2026-07-28 sürümünü hedefleyen beta aşamasında. Tier 1 SDK'ların final spesifikasyon penceresinde destek yayınlaması bekleniyor.

Program.cs tarafı beklediğinizden kısa:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp("/mcp");

app.Run();

WithToolsFromAssembly() çağrısı assembly'yi tarayıp attribute ile işaretlenmiş tool'ları topluyor. Bu, klasik ASP.NET controller keşfine benziyor ve ekipteki insanlar için tanıdık geliyor.

İlk tool

Diyelim ki bir kargo takip sisteminiz var ve modelin gönderi durumunu sorgulayabilmesini istiyorsunuz.

using System.ComponentModel;
using ModelContextProtocol.Server;

[McpServerToolType]
public class KargoAraclari
{
    private readonly IKargoServisi _servis;

    public KargoAraclari(IKargoServisi servis) => _servis = servis;

    [McpServerTool(Name = "gonderi_durumu")]
    [Description("Takip numarasına göre bir gönderinin güncel durumunu döner.")]
    public async Task<GonderiDurumu> GonderiDurumuAsync(
        [Description("14 haneli takip numarası")] string takipNo,
        CancellationToken ct)
    {
        return await _servis.DurumGetirAsync(takipNo, ct);
    }
}

Burada dikkat edilecek iki şey var.

Birincisi, dependency injection çalışıyor. Tool sınıfınız normal bir servis gibi container'dan besleniyor. Mevcut repository'lerinizi, HttpClient factory'nizi, DbContext'inizi olduğu gibi kullanabiliyorsunuz. MCP sunucusu kod tabanınızın üzerine ince bir katman olarak oturuyor, paralel bir dünya kurmuyorsunuz.

İkincisi, Description attribute'ları dekorasyon değil. Modelin tool'u ne zaman ve nasıl çağıracağına dair tek bilgi kaynağı bunlar. "Kargo sorgular" yazmayın. Ne alır, ne döner, hangi durumda kullanılır, hangi durumda kullanılmaz yazın. Bir tool açıklamasını yazarken kafanızda canlandırmanız gereken şey, dokümantasyon değil, işe yeni başlamış bir stajyere bırakılan not.

Şemaları ciddiye alın

Yeni sürümle birlikte tool şemaları tam JSON Schema 2020-12'ye yükseltildi. Input şemaları hâlâ kökte type: "object" olmak zorunda ama artık oneOf, anyOf, allOf kompozisyonuna, koşullara ve $ref referanslarına izin veriyor.

C# tarafında bu, kayıt tiplerinizin ve enum'larınızın çok daha sadık şekilde yansıtılabilmesi demek. Örneğin "ya müşteri numarası ya vergi numarası, ikisi birden değil" gibi bir kısıtı artık şemada ifade edebiliyorsunuz, tool gövdesinde elle doğrulamak zorunda değilsiniz.

Bir güvenlik notu: dış $ref URI'lerini otomatik çözmeyin, spesifikasyon bunu açıkça yasaklıyor. Şema derinliğini ve doğrulama süresini sınırlayın.

Kurumsal ortamda gerçekte karşılaşacaklarınız

Buraya kadarı demo. Şimdi asıl kısım.

Gateway ve yönlendirme

Yeni transport artık Mcp-Method ve Mcp-Name başlıklarını zorunlu kılıyor. Bu, Türkiye'deki kurumsal ortamlar için beklediğinizden önemli. Çoğu banka ve sigorta şirketinde trafiğin önünde bir API gateway var ve o gateway'de "gövdeyi açıp bakma" politikası genelde yasak.

Artık gerekmiyor. Rate limiting kuralınızı doğrudan Mcp-Method: tools/call üzerine kurabilirsiniz. Belirli bir tool'u kısıtlamak isterseniz Mcp-Name yeterli. Sunucu, başlıkla gövdenin uyuşmadığı istekleri reddediyor, dolayısıyla bu başlıklara güvenebilirsiniz.

Kimlik doğrulama

Bu sürümde altı SEP yetkilendirme tarafını OAuth 2.0 ve OpenID Connect'in gerçek dünyada nasıl kurulduğuna yaklaştırdı. .NET tarafında iyi haber şu ki Microsoft.AspNetCore.Authentication.JwtBearer ile kurduğunuz mevcut yapı büyük ölçüde geçerli.

Dikkat edilecek noktalar:

  • İstemciler artık RFC 9207 uyarınca yetkilendirme yanıtlarındaki iss parametresini doğrulamak zorunda. Kendi Identity Server veya Duende kurulumunuz varsa iss gönderdiğinden emin olun.
  • İstemciler Dynamic Client Registration sırasında OpenID Connect application_type değerini bildiriyor. Bu, masaüstü ve CLI istemcilerinin localhost redirect URI'lerinin reddedilmesi sorununu çözüyor.
  • Durumsuz modelde token doğrulaması istek başına bir kez yapılıyor, oturum bağlamı yok. Bu, per-agent kapsamlı kimlik bilgilerini uygulamayı kolaylaştırıyor.

Ölçekleme

Söylenecek fazla bir şey yok, ki asıl mesele bu. App Service veya Kubernetes fark etmez, üç replika açın, önüne düz round robin koyun, bitti. Sticky session'a, paylaşımlı Redis oturum deposuna, gateway'de deep packet inspection'a ihtiyaç yok.

Uygulama seviyesinde durum tutmanız gerekiyorsa açık tanıtıcı deseni kullanın. Bir tool sepet_olustur çağrısında sepet_id üretsin, model onu urun_ekle çağrısında argüman olarak geri versin. Durumu Redis'te veya SQL'de siz yönetin, ama protokolün sizin adınıza yönetmesini beklemeyin.

Gözlemlenebilirlik

Logging özelliği deprecate edildi. Yerine yapılandırılmış gözlemlenebilirlik için OpenTelemetry öneriliyor, stdio transport için stderr.

.NET tarafında bu aslında bir kazanç. OpenTelemetry.Extensions.Hosting zaten olgun ve W3C Trace Context yayılımı artık _meta içinde belgelendi. traceparent, tracestate ve baggage anahtar isimleri sabitlendiği için, Claude Desktop'ta başlayan bir çağrı MCP sunucunuzdan geçip arkadaki SQL Server'a kadar tek bir span ağacı olarak Application Insights'ta görünüyor.

Bu, "model neden bu tool'u çağırdı ve neden yavaştı" sorusuna cevap verebilmenin tek makul yolu.

Önbellekleme

Liste sonuçları artık ttlMs ve cacheScope taşıyor. Tool listeniz nadiren değişiyorsa ttlMs değerini cömert tutun. cacheScope ise dikkat isteyen kısım: tool listesi kullanıcıya göre değişiyorsa (ki kurumsal yetkilendirmede sık olur) bunu kullanıcılar arası paylaşılabilir işaretlemeyin.

Tasks kullanıyorsanız

Uzun süren işlemler için Tasks özelliğini kullanan bir sunucunuz varsa, yeniden yazmanız gerekecek. Tasks çekirdek protokolden çıkıp uzantıya taşındı. Yeni yaşam döngüsünde sunucu tools/call çağrısına bir task handle ile cevap veriyor, istemci tasks/get, tasks/update ve tasks/cancel ile ilerletiyor. Task oluşturma kararı sunucuya ait: istemci uzantıyı desteklediğini bildiriyor, hangi çağrının task olarak koşacağına sunucu karar veriyor.

tasks/list tamamen kaldırıldı, çünkü oturum olmadan güvenli şekilde kapsamlandırılamıyor.

.NET dünyasında uzun işlemler için zaten Hangfire veya Azure Functions kullanıyorsanız, Tasks uzantısını o altyapının önüne ince bir kabuk olarak düşünün.

Kapanış

MCP'nin ilk yılında C# geliştiricisi olmak biraz ikinci sınıf vatandaşlık gibiydi. Örnekler başka dildeydi, SDK geriden geliyordu, blog yazıları sizden bahsetmiyordu.

Durumsuz protokole geçişle birlikte bu ayrım büyük ölçüde kapandı. Çünkü protokolün sizden istediği şey artık egzotik değil: kimlik doğrulaması yapan, başlıkları okuyan, JSON dönen bir HTTP endpoint'i. .NET bunu on beş yıldır iyi yapıyor.

Kurumsal bir ortamda mevcut sisteminizin üzerine MCP koymayı düşünüyorsanız, bugün başlamak için iyi bir gün.


Bir sonraki yazıda aynı sunucuyu KVKK açısından ele alacağım: kurumsal veriyi bir dil modeline açtığınızda hukuken ne olur.

#ASP.NET Core#C##MCP sunucusu#kurumsal yazılım#OpenTelemetry
👨🏻‍🏫

Yazar

Nuh ULU

Yapay Zekâ ve Dijital Dönüşüm Eğitmeni

Yapay zekâ okuryazarlığı, üretken yapay zekâ araçları ve kurumsal dönüşüm odaklı uygulamalı eğitimler veren New Academy eğitmeni.

Yapay zekâyı birlikte uygulamaya hazır mısınız?

Beş kişilik sınıflarda, iki gün ve 16 saat süren yüz yüze eğitimlerimizle yeni nesil yetkinlikler kazanın. Yaklaşan tarihleri inceleyin veya kurumsal teklif alın.