1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セマンティックカーネル:MCPの開始

Posted at

MCPがますます注目を浴びており、主要なモデル会社も続々とMCPをサポートしています。OpenAIも最近MCPのサポートを発表し、コミュニティのSDKも登場しました。今日はModelContextProtocolを使用してサーバーとクライアントを作成し、それらを接続してみましょう。同時に、SKを忘れずに、SKがどのようにMCPを使用するかを見てみましょう。

まずはサーバー側を見てみましょう:

プロジェクトファイルは以下の通りです:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="ModelContextProtocol" Version="0.1.0-preview.2" />
  </ItemGroup>
</Project>

Program.cs:

using ModelContextProtocol;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapMcpSse();
app.Run();

ツール定義:

using ModelContextProtocol.Server;
using System.ComponentModel;

namespace MCPOrderTool.Tools;

[McpServerToolType]
public static class OrderTool
{
    [McpServerTool("queryOrder"), Description("開始日と終了日に基づいて注文を照会する")]
    public static List<Order> QueryList(DateTime? beginTime, DateTime? endTime)
    {
        Console.WriteLine("-------------パラメータ-------------");
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($"BeginTime:{beginTime},EndTime:{endTime}");
        Console.ResetColor();
        Console.WriteLine("-------------パラメータ-------------");
        if (beginTime is null || endTime is null)
        {
            beginTime = DateTime.Now;
            endTime = DateTime.Now;
        }
        return new List<Order>
        {
            new Order("NO000001", "Order1", DateTime.Now.ToString("yyyy-MM-dd"), "Pending"),
            new Order("NO000002", "Order2", DateTime.Now.ToString("yyyy-MM-dd"), "Completed"),
            new Order("NO000003", "Order3", DateTime.Now.ToString("yyyy-MM-dd"), "Pending"),
            new Order("NO000004", "Order4", DateTime.Now.ToString("yyyy-MM-dd"), "Completed"),
            new Order("NO000005", "Order5", DateTime.Now.ToString("yyyy-MM-dd"), "Pending"),
            new Order("NO000006", "Order6", DateTime.Now.ToString("yyyy-MM-dd"), "Completed"),
            new Order("NO000007", "Order7", DateTime.Now.ToString("yyyy-MM-dd"), "Pending"),
            new Order("NO000008", "Order8", DateTime.Now.ToString("yyyy-MM-dd"), "Completed"),
            new Order("NO000009", "Order9", DateTime.Now.ToString("yyyy-MM-dd"), "Pending"),
            new Order("NO000010", "Order10", DateTime.Now.ToString("yyyy-MM-dd"), "Completed"),
        };
    }
}

public record Order(string OrderId, string OrderName, string OrderTime, string OrderStatus);

MCPサーバー側のマップ定義:

using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils.Json;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Protocol.Transport;

public static class McpEndpointRouteBuilderExtensions
{
    public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder endpoints)
    {
        IMcpServer? server = null;
        SseResponseStreamTransport? transport = null;
        var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
        var mcpServerOptions = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();

        var routeGroup = endpoints.MapGroup("");

        routeGroup.MapGet("/sse", async (HttpResponse response, CancellationToken requestAborted) =>
        {
            await using var localTransport = transport = new SseResponseStreamTransport(response.Body);
            await using var localServer = server = McpServerFactory.Create(transport, mcpServerOptions.Value, loggerFactory, endpoints.ServiceProvider);

            await localServer.StartAsync(requestAborted);
            response.Headers.ContentType = "text/event-stream";
            response.Headers.CacheControl = "no-cache";
            try
            {
                await transport.RunAsync(requestAborted);
            }
            catch (OperationCanceledException) when (requestAborted.IsCancellationRequested)
            {
                Console.WriteLine("closed");
            }
        });

        routeGroup.MapPost("/message", async context =>
        {
            if (transport is null)
            {
                await Results.BadRequest("Connect to the /sse endpoint before sending messages.").ExecuteAsync(context);
                return;
            }
            var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);
            if (message is null)
            {
                await Results.BadRequest("No message in request body.").ExecuteAsync(context);
                return;
            }
            await transport.OnMessageReceivedAsync(message, context.RequestAborted);
            context.Response.StatusCode = StatusCodes.Status202Accepted;
            await context.Response.WriteAsync("Accepted");
        });

        return routeGroup;
    }
}

次はクライアントの使用です:

プロジェクトファイル:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.3.0-preview.1.25161.3" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0-preview.2.25163.2" />
    <PackageReference Include="Microsoft.SemanticKernel" Version="1.44.0" />
    <PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.44.0-alpha" />
    <PackageReference Include="ModelContextProtocol" Version="0.1.0-preview.2" />
    <PackageReference Include="ModelContextProtocol-SemanticKernel" Version="0.0.1-preview-03" />
  </ItemGroup>
</Project>

Program.csファイル:

using Azure.Core;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.SemanticKernel.Extensions;
using ModelContextProtocol.SemanticKernel.Options;
using ModelContextProtocol.Server;
using OpenAI;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;

var key = File.ReadAllText("c:/gpt/key.txt");

while (true)
{
    Console.WriteLine("===========================================================");
    Console.WriteLine("1、ツールリストの取得  2、クライアントでツールを呼び出す  3、SKでツールを呼び出す  0、終了");
    Console.WriteLine("===========================================================");
    var no = Console.ReadLine();
    switch (no)
    {
        case "1":
            await MCPClientToolsListAsync();
            break;
        case "2":
            await MCPClientAsync();
            break;
        case "3":
            await SKClientAsync();
            break;
        case "0":
            return;
    }
}

async Task MCPClientToolsListAsync()
{
    var serverConfig = new McpServerConfig
    {
        Id = "QueryOrder",
        Name = "QueryOrder",
        TransportType = TransportTypes.Sse,
        Location = "http://localhost:3001/sse"
    };
    var clientOptions = new McpClientOptions
    {
        ClientInfo = new()
        {
            Name = "QueryOrderClient",
            Version = "0.0.1",
        }
    };
    var mcpClient = await McpClientFactory.CreateAsync(serverConfig, clientOptions);
    Console.WriteLine("ツールを取得:");
    var tools = await mcpClient.ListToolsAsync();

    foreach (var tool in tools)
    {
        Console.WriteLine($"{tool.Name},{tool.Description}");
    }
    Console.WriteLine();
}

async Task MCPClientAsync()
{
    var serverConfig = new McpServerConfig
    {
        Id = "QueryOrder",
        Name = "MCPOrderTool",
        TransportType = TransportTypes.Sse,
        Location = "http://localhost:3001/sse"
    };
    var clientOptions = new McpClientOptions
    {
        ClientInfo = new()
        {
            Name = "QueryOrderClient",
            Version = "0.0.1",
        }
    };
    var mcpClient = await McpClientFactory.CreateAsync(serverConfig, clientOptions);
    var functions = await mcpClient.ListToolsAsync();
    IChatClient chatClient = new OpenAIClient(key).AsChatClient("gpt-4o-mini")
    .AsBuilder().UseFunctionInvocation().Build();
    var response = chatClient.GetStreamingResponseAsync(
        "今週の注文を照会",
        new()
        {
            Tools = [.. functions],
        });
    await foreach (var item in response)
    {
        Console.Write(item.Text);
    }
    Console.WriteLine();
}

async Task SKClientAsync()
{
    var builder = Kernel.CreateBuilder();
    builder.Services.AddLogging(c => c.AddDebug().SetMinimumLevel(LogLevel.Trace));

    builder.Services.AddOpenAIChatCompletion(
        serviceId: "openai",
        modelId: "gpt-4o-mini",
        apiKey: key);

    var kernel = builder.Build();
    kernel.Plugins.AddFromType<TimeInformationPlugin>();
    await kernel.Plugins.AddMcpFunctionsFromSseServerAsync("MCPOrderTool", "http://localhost:3001/sse");
    var executionSettings = new OpenAIPromptExecutionSettings
    {
        Temperature = 0,
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
    };
    var prompt = "今月の注文を照会する";
    var result = kernel.InvokePromptStreamingAsync(prompt, new(executionSettings));
    await foreach (var item in result)
    {
        Console.Write(item.ToString());
    }
    Console.WriteLine();
}

public class TimeInformationPlugin
{
    [KernelFunction, Description("現在のUTC時間を取得する。")]
    public string GetCurrentUtcTime()
        => DateTime.UtcNow.ToString("R");
}

以下は実行結果です:1つ目はサーバー側のツール情報を列挙し、2つ目は通常の方法でMCPサーバーを呼び出します。この時、右の赤い枠に注意してください。時間は2023年で、モデルから取得した時間です。3つ目はSKでツールを呼び出し、SKに現在時間のプラグインを追加しています。青い枠を見てください。正しい時間が取得されています。

画像

(Translated by GPT)
元のリンク:https://mp.weixin.qq.com/s/8GTJTx3Jwe8ELBv9IeWlVA?token=1135395277&lang=zh_CN&wt.mc_id=MVP_325642

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?