Semantic Kernel也能充当MCP Client

背景

笔者之前,分别写过两篇关于Semantic Kernel(下简称SK)相关的博客,最近模型上下文协议(下称MCP)大火,实际上了解过SK的小伙伴,一看到 MCP的一些具体呈现,会发现,Client 调用 Server的方式,和SK调用插件的过程很像,实际操作了一下,发现确实是可以的。

也就是说,如果我们之前的项目里用到SK做过Agent相关的模块,如今也可以丝滑的让其充当MCP Client的角色,去使用更多MCP生态的东西,而不需要做更多的改动。

虽然SK是为AI Agent的发展而诞生的,但好框架就是好框架,没想到它和MCP也这么契合。

本篇,建立在《再尝Semantic Kernel,Planning特性很香》的基础上,再次扩展一下MCP相关的介绍。

注意:本篇不会深入介绍MCP相关的概念,架构等等前置内容,主要还是通过案例说明SK和MCP Server之间的联系,建议不熟悉MCP相关内容的小伙伴,先登录MCP官网进行了解。

创建一个MCP Server

在MCP的官方介绍文档里已经有C#的官方SDK了,这里我们参照它官方的例子,先来一个MCP Server。

Tips:官方的案例已经非常简洁和完善了,建议没搞过MCP的小伙伴上手试一下,虽然案例很简单,一看就能明白,但那真正跑通的获得感,还是得自己动手试一下才能体会到。

构建服务

这一步我觉得大家还是直接看官方文档更清楚,我这里不在赘述

传送门👉:modelcontextprotocol.io/quickstart/...

需要注意的是,我这里的Server是使用SSE的传输方式。

目前SSE的方式官方已经声明会逐步被Streamable Http的形式替代,但目前还是Built-in状态,本地调试的话还可以使用stdio的方式,这也是Claude Desktop,Cline之类客户端工具支持的方式,这点大家按需设定即可,这部分内容可以参考这里👉:mcp-framework.com/docs/Transp...

编写Tool

定义一个class,然后标记上MCPServer的特定属性,这部分官网也有介绍,我就直接上代码了

csharp 复制代码
[McpServerToolType]
public static class WeatherTools
{
    [McpServerTool(Name = "GetWeather"), Description("获取当前城市的天气")]
    public static async Task<string> GetWeather(
        HttpClient client,
        [Description("中国的城市编码adcode")] string adcode)
    {
        if (string.IsNullOrEmpty(adcode))
        {
            return "adcode不能为空";
        }
        string gdKey = ConfigHelper.GetAppSetting("GaoDeKey");
        var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/v3/weather/weatherInfo?key={gdKey}&city={adcode}&extensions=base");
        var lives = jsonElement.GetProperty("lives").EnumerateArray();

        if (!lives.Any())
        {
            return "当前城市天气获取失败";
        }

        return string.Join("\n--\n",lives.Select(live =>
            {
                var city =  $"{live.GetProperty("province").GetString()}--{live.GetProperty("city").GetString()}";
                var weather = live.GetProperty("weather").GetString();
                var temperature = live.GetProperty("temperature").GetString();
                var windPower = live.GetProperty("windpower").GetString();
                var humidity = live.GetProperty("humidity").GetString();
                return $"城市:{city}\n天气:{weather}\n温度:{temperature}°C\n风力:{windPower}级\n湿度:{humidity}%";
            }));
    }
}

我这里,没有使用官方案例里的天气接口,而是改成了高德的天气接口,因为一会儿还要演示一下SK调用MCP Server的能力,除了调用本地的Server,高德还有一个云端的MCP Server,非常好用,稍后一并介绍一下,正好就连天气接口也改成高德的。

编写完成后,启动我们的Server服务。

bash 复制代码
dotnet run

验证

Tool编写完成后,可以先使用一些软件或者工具类的MCP Client验证一下,开发阶段,这些工具还是非常有必要的,它的角色定位就像我们用到的数据库管理工具,比如SSMS,Pg Admin,DBeaver等。

我这里使用的是官方的MCP Inspector,本地只要有node和npx环境即可。

另外,因为要经常测试一些Server,建议把Python和Python包管理工具uv也安装一下。

然后,我们启动Inspector

shell 复制代码
npx @modelcontextprotocol/inspector node build/index.js

启动之后,控制台会监听一个端口,然后在浏览器打开,然后配置好我们的Server地址,如下图

获取到所有的Tool之后,测试验证一下我们刚完成的天气接口是否生效。

至此,验证工作完成,说明我们的MCP Server是可以正常工作的,接下来就是接入实际的业务系统,来调用这个Server提供的能力了。

在业务系统创建MCP Client

注入SK服务

这部分略过,在前面的系列文章里已经写过了,或者大家也可以直接查看微软的官方文档,这里不再赘述。

编写插件

这里呢,因为我在之前的项目里,已经开始使用SK框架了,并且完成了部分的Agent功能,都是以Plugin的方式注入到系统里的,这里的演示也就暂时以这种方式来接入,后续再根据实际情况调整。

插件的代码如下

csharp 复制代码
[KernelFunction("call_weather_api")]
[Description("通过地理编码,获取天气信息")]
[return: Description("如果运行正常,返回编号所属地址的天气详情")]
public async Task<string> CallWeatherApi(string adcode)
{
    Logger.Debug("--------天气插件正确执行---------------");
    var defaultOptions = new McpClientOptions
    {
        ClientInfo = new() { Name = "SK", Version = "1.0.0" }
    };

    var defaultConfig = new SseClientTransportOptions
    {
        Endpoint = new Uri($"http://localhost:5001/sse"),
        Name = "Magic.Services.MCPServer",
    };
    await using var client = await McpClientFactory.CreateAsync(
        new SseClientTransport(defaultConfig),
        defaultOptions);

    var result = await client.CallToolAsync("GetWeather", new Dictionary<string, object?>
    {
        { "adcode",adcode}
    });

    return JsonHelper.JsonSerialize(result);
}

调用

我这里是在Web系统里进行的演示,所以以接口形式来调用插件,代码如下

csharp 复制代码
public async Task<IActionResult> CallLocalServer(string adcode)
{
    _kernel.Plugins.AddFromType<LocalServer>("LocalServer", _serviceProvider);
    // 获取聊天完成服务
    var chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>();
    // 启用自动函数调用
    OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
        //FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
    };
    PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
    ChatHistory chatHistory = [];
    chatHistory.AddSystemMessage($"你是一个天气预报员,本函数的入参会给你一个中国地理位置编码,你要调用合适的MCP Server来完成天气预报。");
    chatHistory.AddUserMessage($"查询地理编码为【{adcode}】的地点天气");
    var chatResult = await chatCompletionService.GetChatMessageContentAsync(
        chatHistory,
        openAIPromptExecutionSettings,
        _kernel);
    Console.Write($"\nAssistant : {chatResult}\n");

    return Json(chatResult);
}

验证

为了方便验证,可以先把接口的访问等级降低,直接通过URL地址访问,效果如下

控制台打印的信息如下

至此,我们已经在本地创建了一个MCPServer,并通过MCP Inspector进行了验证,同时又在原有使用SK的系统里,通过SK成功调用了这个Server提供的tool,接入复杂度可以接受,效果也非常不错。

接下来,再试试SK能不能成功调用高德地图的MCP Server

调用高德MCP Server

前置工作

注意,如果前面的天气接口你是使用的高德的服务,那么相信你已经注册了高德的key,如果没有,这里需要去注册一下

编写服务

由于调用的是第三方的MCP,我们这里可以直接在接口或者服务类里编写调用代码

csharp 复制代码
public async Task<IActionResult> CallGaodeServer(string msg)
{
    // 第一步:创建 mcp 客户端
    var defaultOptions = new McpClientOptions
    {
        ClientInfo = new() { Name = "地图规划", Version = "1.0.0" }
    };

    var defaultConfig = new SseClientTransportOptions
    {
        Endpoint = new Uri(ConfigurationHelper.GetSectionValue("GaodeMCP")),
        Name = "Magic.Services.MCPServer",
    };
    await using var client = await McpClientFactory.CreateAsync(
    new SseClientTransport(defaultConfig),
    defaultOptions);

    var tools = await client.ListToolsAsync();

    foreach (var tool in tools)
    {
        Logger.Debug($"秀一下高德的能力之--- {tool.Name}");
    }

    #pragma warning disable SKEXP0001
    _kernel.Plugins.AddFromFunctions("gaodemap", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
    #pragma warning restore SKEXP0001
    // 获取聊天完成服务
    var chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>();
    // 启用自动函数调用
    OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
    };
    PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
    ChatHistory chatHistory = [];
    chatHistory.AddUserMessage(msg);
    var chatResult = await chatCompletionService.GetChatMessageContentAsync(
        chatHistory,
        openAIPromptExecutionSettings,
        _kernel);
    Console.Write($"\nAssistant : {chatResult}\n");

    return Json(chatResult);
}

验证

验证工作,还是和前面一样,暂时在浏览器里直接访问即可。

控制台打印的信息更友好一点

结束语

好了,至此,我们成功在原有的系统上,使用SK框架充当MCP Client的角色,完成了本地MCP Server的调用和第三方MCP Server的调用目标。

最后,再推荐几个介绍MCP的参考站点

相关推荐
程序员爱钓鱼2 小时前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__2 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
uzong7 小时前
技术故障复盘模版
后端
GetcharZp8 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程8 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研8 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi9 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国10 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy10 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack10 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt