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的参考站点

相关推荐
.生产的驴12 分钟前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑20 分钟前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
追逐时光者1 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵1 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫1 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
嘻嘻嘻嘻嘻嘻ys1 小时前
《Spring Boot 3 + Java 17:响应式云原生架构深度实践与范式革新》
前端·后端
异常君1 小时前
线程池隐患解析:为何阿里巴巴拒绝 Executors
java·后端·代码规范
mazhimazhi2 小时前
GC垃圾收集时,居然还有用户线程在奔跑
后端·面试
Python私教2 小时前
基于 Requests 与 Ollama 的本地大模型交互全栈实践指南
后端
ypf52082 小时前
Tortoise_orm与Aerich 迁移
后端