Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Model Context Protocol (MCP)’ünün Semantic Kernel İle Birlikte Kullanımı

Merhaba,

Bir önceki Model Context Protocol (MCP) Nedir? Derinlemesine Değerlendirelim… başlıklı içeriğimizde MCP’ye dair tam teferruatlı bir incelemede bulunmuş ve özelleştirilmiş MCP Server’lar eşliğinde MCP Client’ların nasıl oluşturulabileceğini ele almıştık. Bu içeriğimizde ise Semantic Kernel ile birlikte MCP protokolünün birlikte nasıl kullanılabileceğini inceleyecek ve AI modelleriyle birebir iletişim süreçlerine odaklı bir seyri deneyimliyor olacağız. O halde buyurun başlayalım…

Neden Semantic Kernel İle MCP’yi Birlikte Kullanmalıyız?

Biliyorsunuz ki Semantic Kernel, AI modelleri ile uygulamalar arasında bağlantı kurmayı kolaylaştıran bir araç takımıdır. MCP ise bir AI modelinin bağlamını yönetmek ve girdileri-çıktıları standardize etmek için tasarlanmış bir protokoldür. Ee haliyle her ikisini bir araya getirerek kümülatif bir işlev ortaya koyulabilir ve Semantic Kernel ile AI modelleri uygulamaya entegre edilirken, MCP ile de bu entegrasyon neticesinde işlevsellik gösterecek olan AI modellerinin bağlamlarına hem standart bir şekilde müdahale edilebilir hem de diğer uygulamalar ve AI modelleriyle bu bağlamın paylaşılması söz konusu olabilir. Bunların yanında AI modellerine gelen prompt’lar neticesinde çağrılacak olan tool’lara dair loglar tutulabilir, trace ve metric işlemleri yapılandırılabilir ve bunlar kolayca monitoring edilebilir.

SK ile MCP’yi Birlikte Nasıl Kullanabiliriz?

Model Context Protocol (MCP)'ünün Semantic Kernel İle Birlikte KullanımıSemantic Kernel ile MCP, birbirlerine çift yönlü işlevsellik kazandırabilecek şekilde kullanılabilmektedir. Şöyle ki, Semantic Kernel’ın işlevsellikleri olan Plugin ve Function‘lar MCP’ye tool olarak aktarılabilir. Benzer mantıkla MCP’de ki tool’lar da Semantic Kernel’a Plugin ve Function olarak yansıtılabilir. Bu çift yönlü kullanım bizlere daha bütüncül bir işlevsellik kazandıracaktır. Nasıl mı? diye sorduğunuzu duyar gibiyim… Hemen izah edelim; Semantic Kernel’da tanımlı olan bir Plugin‘i herhangi bir MCP Server’a tool olarak aktarabilirsek bu durum, sistemdeki diğer AI modellerinin de bu plugin’den istifade etmesine zemin hazırlayacaktır. Aynı şekilde MCP’de ki bir tool’u Semantic Kernel’a Plugin olarak eklediğimizde de bu tool AI modelleriyle olan iş akışları süreçlerinde Semantic Kernel tarafından kullanılabilir olacaktır.

Şimdi gelin bu çift yönlü akışın her ikisini de yapılandırma ve işlevsellik açısından değerlendirelim. Bunun için öncelikle MCP.Client ve MCP.Server adında iki adet proje oluşturalım ve bu projelere gerekli olan kütüphaneleri yükleyerek hazır bir altyapı sağlayalım. Tabi bu çalışmada Semantic Kernel ile ilgili yapılandırmaları MCP.Client projesinde yapacağımız aşikar olsa gerek. Bundan kaynaklı, süreci daha efektif test edebilmek için bu projenin özellikle Asp.NET Core olmasına özen gösterelim.

Ayrıca, MCP’nin AI modeli ile MCP Server’lar arasındaki context paylaşımını tecrübe edebilmek ve bir yandan da bu sürecin Semantic Kernel ile daha da efektif kılınabildiğini deneyimlemek için uygulamaya OpenRouter platformu üzerinden bir adet AI modeli(google/gemini-2.0-flash-exp:free) entegre edelim.

Burada kesinlikle unutulmaması gereken bir husus vardır ki, o da, kullanılacak olan AI modelinin tools/plugin desteğinin olmasıdır. Aksi taktirde içerik sürecinde odak konu olarak yapılandırılan çalışmalardan hiçbir şekilde netice alınamayacaktır.

MCP.Client uygulamasında Semantic Kernel yapılandırması aşağıdaki gibi olacaktır;

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
using System.ClientModel;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddKernel()
    .AddOpenAIChatCompletion(
        modelId: "google/gemini-2.0-flash-exp:free",
        openAIClient: new OpenAIClient(
                credential: new ApiKeyCredential("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"),
                options: new OpenAIClientOptions
                {
                    Endpoint = new Uri("https://openrouter.ai/api/v1")
                }
            )
    );

var app = builder.Build();

.
.
.

app.Run();

Benzer mantıkla yine aynı uygulama bir yandan MCP Host olacağından dolayı, özel olarak MCP Client oluşturmamızı sağlayacak olan yapılandırma aşağıdaki gibi olabilir;

    public class GoogleGeminiAIMCPClient(string MCPServerName, string MCPServerPath)
    {
        private IMcpClient _mcpClient;
        public async Task<IMcpClient> GetClientAsync()
        {
            if (_mcpClient == null)
                _mcpClient = await McpClientFactory.CreateAsync(
                         clientTransport: new StdioClientTransport(new()
                         {
                             Name = MCPServerName,
                             Command = MCPServerPath
                         }),
                         clientOptions: new McpClientOptions()
                         {
                             ClientInfo = new Implementation()
                             {
                                 Name = "Google.Gemini.AI.MCP.Client",
                                 Version = "1.0.0"
                             }
                         }
                     );

            return _mcpClient;
        }

        public async Task<IList<McpClientTool>> GetToolListAsync()
        {
            _mcpClient = await GetClientAsync();
            IList<McpClientTool> tools = await _mcpClient.ListToolsAsync();
            return tools;
        }

        public async Task<object> CallToolAsync(string toolName, Dictionary<string, object?> input)
        {
            _mcpClient = await GetClientAsync();
            CallToolResponse response = await _mcpClient.CallToolAsync(
                       toolName,
                       input
                       );
            return response.Content.First(c => c.Type == "text").Text;
        }
    }

Bakın! olabilir diyorum çünkü buradaki çalışmanın usulü ve yaklaşımı sizlere kalmıştır. Malum, bir MCP Host içerisinde birden fazla MCP Client oluşturulabilir ve bu client’ların her biri farklı MCP Server’larla bağlantı kurabilir. Şöyle ki; A modelinde kullanılacak MCP Server’lar O, R ve P iken, B modelinde kullanılacak olanlar Ç, C, Ğ olabilir. Bunların her birinin yapılandırılması ve yönetimi için bu MCP Host uygulamasında birer MCP Client nesnesi oluşturulması gerekecektir. İşte bu tarz bir ihtiyaca karşın bu tarz hususi sınıflar geliştirebilir ya da daha farklı ve daha güzel çözümler üretebilirsiniz.

Ve hazırlık aşamasının sonuncu hamlesi olarak MCP.Server uygulamasını da aşağıdaki gibi örnek tool eşliğinde oluşturabilirsiniz;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Verbose()
    .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "MCP.Server.Log"))
    .WriteTo.Debug()
    .WriteTo.Console()
    .CreateLogger();

Log.Information("Server başlatılıyor...");

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddHttpClient();

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

await builder.Build().RunAsync();
    [McpServerToolType]
    public class UserTool(IHttpClientFactory httpClientFactory)
    {
        [McpServerTool, Description("Tüm kullanıcıları, bilgileriyle birlikte getirir.")]
        public async Task<List<object>> GetUsersAsync()
        {
            Log.Information("JSONPlaceholder'dan kullanıcı bilgileri çekiliyor...");

            HttpClient httpClient = httpClientFactory.CreateClient();
            HttpResponseMessage httpResponseMessage = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/users");
            string jsonData = await httpResponseMessage.Content.ReadAsStringAsync();

            JsonDocument document = JsonDocument.Parse(jsonData);
            JsonElement root = document.RootElement;

            List<object> usersData = new();

            foreach (JsonElement element in root.EnumerateArray())
            {
                usersData.Add(new
                {
                    Id = element.GetProperty("id").GetInt32(),
                    Name = element.GetProperty("name").GetString(),
                    Username = element.GetProperty("username").GetString(),
                    Email = element.GetProperty("email").GetString()
                });
            }

            Log.Information("JSONPlaceholder'dan kullanıcı bilgileri çekildi...");
            return usersData;
        }
    }

Evet, görüldüğü üzere burada örnek olarak JSONPlaceholder’dan tüm kullanıcıları çeken ‘GetUsers’ tool’unu oluşturmuş bulunuyorum. Burada tool’un adı, işlevsel olarak asenkron süreç barındırdığından dolayı yazılım geleneği icabı ‘…Async’ şeklinde bitiyor olabilir, ancak yine de bu tool client açısından ‘GetUsers’ şeklinde değerlendirilecektir. Bu temel yapılandırmalardan sonra artık içeriğimizin esas konusuna odaklanabilir ve Semantic Kernel eşliğinde MCP manevralarına başlayabiliriz 🙂

MCP’den SK’ya Tool Aktarımı Kullanımı

İlk olarak MCP Server’larındaki yeteneklerin Semantic Kernel’a aktarımını ele alacağız. Bunun için aşağıdaki gibi, yukarıda oluşturduğumuz GoogleGeminiAIMCPClient sınıfı üzerinden bir instance oluşturmalı ve bu MCP Client’a bağlı olan MCP Server’dan tool’ları elde edip, Semantic Kernel yapılandırmasına plugin olarak tanımlamalıyız;

.
.
.
GoogleGeminiAIMCPClient mcpClient = new("MCP.Server", Path.Combine("..", "MCP.Server", "bin", "Debug", "net9.0", "MCP.Server.exe"));
IList<McpClientTool> tools = await mcpClient.GetToolListAsync();

builder.Services
    .AddKernel()
    .AddOpenAIChatCompletion(
        modelId: "google/gemini-2.0-flash-exp:free",
        openAIClient: new OpenAIClient(
                credential: new ApiKeyCredential("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"),
                options: new OpenAIClientOptions
                {
                    Endpoint = new Uri("https://openrouter.ai/api/v1"),
                }
            )
    )
    .Plugins.AddFromFunctions("UserTool", tools.Select(_tool => _tool.AsKernelFunction()));
.
.
.

Yaptığımız çalışmaya göz atarsanız eğer MCP Server’dan gelen tool’ları SK’ya plugin olarak verirken .Plugins.AddFromFunctions komutu üzerinden yapılandırma gerçekleştirmekteyiz. Ayrıca, bu tool’ları plugin olarak kullanılabilir hale getirmek için AsKernelFunction metoduyla Semantic Kernel’ın plugin yapılanmasına uygun function haline getirmekteyiz ve bu vaziyette Plugins property’si içerisine eklemekteyiz.

Şimdi yapılan bu çalışma neticesinde aşağıdaki gibi otomatik plugin’i devreye sokacak şekilde yapılandırılmış olarak AI modele bir prompt gönderelim ve neticeyi gözlemleyelim.

.
.
.
app.MapGet("/", async (IChatCompletionService chatCompletionService, Kernel kernel) =>
{
    ChatMessageContent content = await chatCompletionService.GetChatMessageContentAsync(
        prompt: "Tüm kullanıcıları getir? Kaç adet olduklarını, username ve email bilgilerini '[username | email]' formatında yazmanı istiyorum. Ayrıca sıralamayı username'e göre alfabetik olarak tersine yapmanı istiyorum. Ardından, ---------- çizgi ile sayfayı ayır ve şu dediklerimi yap: bir şirket senaryosunda gibi davranmanı ve bu kullanıcılardan 2 tanesini bu şirkette müdür olarak seçmeni ve diğerlerine de kendine göre belirlediğin senaryolarda farklı görevler atayarak bu yöneticiler altında paylaştırmanı istiyorum. Mış gibi yapacak ve bir hikaye çizeceksin anlayacağın.",
        executionSettings: new OpenAIPromptExecutionSettings()
        {
            FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { }),
        },
        kernel: kernel);

    return content.Content.ToString();
});

Model Context Protocol (MCP)'ünün Semantic Kernel İle Birlikte KullanımıGörüldüğü üzere AI modele gönderdiğimiz prompt’un mahiyetine göre MCP Server’daki tool tetiklenerek veriler elde edilmiş ve istediğimiz şekilde düzenlenip cevap tarafımıza sunulmuştur.

MCP Server’lardaki yetenekler Semantic Kernel’a bu kadar basit bir şekilde aktarılmaktadır. Şimdi gelin, Semantic Kernel’dan MCP Server’a yetenek aktarımını inceleyelim…

SK’dan MCP’ye Plugin Aktarımı ve Kullanımı

Kimi zaman, Semantic Kernel ile AI entegrasyonu yaparak geliştirdiğimiz uygulamanın bir yandan da farklı bir MCP Client uygulaması tarafından MCP Server olarak kullanılması söz konusu olabilir. İşte böyle durumlarda, Semantic Kernel’ın yeteneklerini de MCP Server’a aktarmamız gerekebilir. Bunun için şöyle bir çalışmanın yapılması yeterli olacaktır;

Tabi ilk olarak aşağıdaki gibi Semantic Kernel plugin’i olduğunu varsayalım;

    public sealed class TodoPlugin(IHttpClientFactory httpClientFactory)
    {
        [KernelFunction, Description("Kullanıcılara dair yapılacaklar listesini ve durumlarını getirir.")]
        public async Task<List<object>> GetTodoListAsync()
        {
            Log.Information("Yapılacaklar listesi çekiliyor...");

            HttpClient httpClient = httpClientFactory.CreateClient();
            HttpResponseMessage httpResponseMessage = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos");
            string jsonData = await httpResponseMessage.Content.ReadAsStringAsync();

            JsonDocument document = JsonDocument.Parse(jsonData);
            JsonElement root = document.RootElement;

            List<object> usersData = new();

            foreach (JsonElement element in root.EnumerateArray())
            {
                usersData.Add(new
                {
                    UserId = element.GetProperty("userId").GetInt32(),
                    Id = element.GetProperty("id").GetInt32(),
                    Title = element.GetProperty("title").GetString(),
                    Completed = element.GetProperty("completed").GetBoolean()
                });
            }

            Log.Information("Yapılacaklar listesi çekildi...");
            return usersData;
        }
    }

Ardından bu plugin’i aşağıdaki gibi Semantic Kernel ile yapılandırıp bir yandan da MCP Server’a yansıtabiliriz;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Verbose()
    .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "MCP.Server.Log"))
    .WriteTo.Debug()
    .WriteTo.Console()
    .CreateLogger();

Log.Information("Server başlatılıyor...");

var builder = Host.CreateApplicationBuilder(args);

builder.Services
.AddKernel()
.Plugins.AddFromType<TodoPlugin>();

builder.Services.AddHttpClient();

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly()
    .WithTools(builder.Services.BuildServiceProvider().GetService<Kernel>().Plugins);

await builder.Build().RunAsync();

Burada bizim için 22. satır önem arz etmektedir. Bu satırda dikkat ederseniz, kernel içerisindeki plugin’ler MCP Server’a tool olarak dahil edilmektedir. Tabi bu işlemi yaparken WithTools metodunun verilen parametreyi karşılayan bir overload’ı olmadığını görecek ve bir hatayla karşılaşacaksınız. Gayet doğal… Bu yapılandırmanın başarılı bir şekilde gerçekleştirilebilmesi için ilgili metot adına özel aşağıdaki extension metodunun geliştirilmesi gerekmektedir.

    public static class ToolExtensions
    {
        public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, KernelPluginCollection plugins)
        {
            foreach (var plugin in plugins)
            {
                foreach (var function in plugin)
                    builder.Services.AddSingleton(service => McpServerTool.Create(function.AsAIFunction()));
            }

            return builder;
        }
    }

Yukarıdaki extension metot tanımına dikkat ederseniz eğer WithTools metoduna yeni bir overload kazandırılmakta ve kernel içerisindeki plugin’lerin MCP Server tarafından algılanan tool’lara dönüştürülerek IoC Container’a eklendiği görülmektedir. Evet, buradan da anlaşılacağı üzere MCP Server’larda tool’lar esasında IoC Container’a eklenen servislerdir. Velhasıl, bu işlem neticesinde Semantic Kernel’da tanımlanmış olan fonksiyon(lar) MCP Server açısından tool olarak yapılandırılmış olmaktadır(lar). Ee böylece artık, bu MCP Server’ı kullanan herhangi bir MCP Client tarafından bu tool AI ile etkileşim sürecinde rahatlıkla tetiklenebilir vaziyettedir diyebiliriz.

Şimdi gelin, bu MCP Server’a yukarıda oluşturduğumuz MCP Host uygulaması içerisinde üreteceğimiz bir MCP Client ile bağlanalım ve barındırdığı tool’ları Semantic Kernal’a aktararak AI modelle etkileşim sürecinde kullanmaya odaklanalım.

.
.
.
GoogleGeminiAIMCPClient mcpClient = new("MCP.Server", Path.Combine("..", "MCP.Server", "bin", "Debug", "net9.0", "MCP.Server.exe"));

GoogleGeminiAIMCPClient mcpClient2 = new("MCP.Server.SK", Path.Combine("..", "MCP.Server.SK", "bin", "Debug", "net9.0", "MCP.Server.SK.exe"));

IList<McpClientTool> tools = await mcpClient.GetToolListAsync();

foreach (var tool in await mcpClient2.GetToolListAsync())
    tools.Add(tool);

builder.Services
    .AddKernel()
    .AddOpenAIChatCompletion(
        modelId: "google/gemini-2.0-flash-exp:free",
        openAIClient: new OpenAIClient(
                credential: new ApiKeyCredential("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"),
                options: new OpenAIClientOptions
                {
                    Endpoint = new Uri("https://openrouter.ai/api/v1"),
                }
            )
    )
    .Plugins.AddFromFunctions("UserTool", tools.Select(_tool => _tool.AsKernelFunction()));
.
.
.

Yukarıdaki çalışmayı incelerseniz eğer 6. satırda biraz önce oluşturduğumuz MCP Server’a bağlanan yeni bir MCP Client oluşturulmakta ve akabinde tüm MCP Client’lar üzerinden MCP Server’lardaki tool’lar elde edilip birleştirilmekte ve bunlar Semantic Kernel’ın plugin’lerine function olarak dönüştürülüp, eklenmektedir.

Artık bu çalışmadan sonra aşağıdaki gibi bir prompt eşliğinde AI modelle testimizi gerçekleştirebilir ve üretilecek cevap üzerinden gözlemde bulunabiliriz.

.
.
.
app.MapGet("/", async (IChatCompletionService chatCompletionService, Kernel kernel) =>
{
    ChatMessageContent content = await chatCompletionService.GetChatMessageContentAsync(
        prompt: "Tüm kullanıcıları ve bu kullanıcıların yapılacak listelerini istiyorum. bunları ekranda [kullanıcı adı - yapılacak] şeklinde listelemeni istiyorum!",
        executionSettings: new OpenAIPromptExecutionSettings()
        {
            FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { }),
        },
        kernel: kernel);

    return content.Content.ToString();
});

Model Context Protocol (MCP)'ünün Semantic Kernel İle Birlikte KullanımıDikkat ederseniz, AI modele gönderilen prompt’ta hem tüm kullanıcılara hem de bu kullanıcıların yapılacak listesine atıfta bulunulmaktadır. Dolayısıyla bu iki veri esasında farklı MCP Server’lardaki ayrı tool’lardan elde edilebilmektedir. Haliyle AI modelimiz lazım olan tüm verileri farklı tool’lardan elde edip ihtiyacımız olan cevabı oluşturup tarafımıza sunmaktadır. Ve daha da ilginci, bu veriler arasında mantıksalın dışında herhangi bir fiziksel ilişki bulunmadığı halde kullandığımız AI modeli bu ilişkileri de gerçekleştirmiş ve bir agent mantalitesinde elde ettiği ayrı verileri birleştirerek kümülatif bir netice sentezlemiştir.

Harika değil mi? 🙂

Evet, böylece Semantic Kernel’da tasarlanmış olan plugin’leri de farklı MCP Client’ların istifade edebilmesi için bu şekilde tool’lara dönüştürebilmekte ve sistemi yetenek açısından bütünsel erişebilir hale getirebilmekteyiz.

Nihai olarak;
Bu içeriğimizde, MCP protokolünü Semantic Kernel ile birlikte AI modelleriyle etkileşime sokarak çalışma süreçlerine MCP Server’lardaki harici servisleri dahil etmiş ve böylece üretilecek cevapları özelleştirerek bir agent mantığında uygulama geliştirmeyi tecrübe etmiş bulunuyoruz.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek çalışmaya aşağıdaki GitHub adresinden erişebilirsiniz.
https://github.com/gncyyldz/Semantic_Kernel_With_MCP

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. Bilal Yalnız dedi ki:

    Öncelikle yazın için teşekkürler.

    Burada SK’yı hiç kullanmayıp “GetUsers” methodunda yaptığın gibi “GetTodoList” methodunu da yapsaydın (Yani ikisi de aynı client) ne farkı olurdu ? Dataların ilişkisi olmamasına rağmen aynı sonucu verecekti değil mi ?

    Yani burda SK niye var tam anlamadım. Avantajını algılayamadım.

    Son olarak da “CallToolAsync” methodunu hiç kullanmadık. Buna ihtiyaç yok demi?

    Teşekkürler

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir