写点什么

Semantic Kernel 也能充当 MCP Client

作者:为自己带盐
  • 2025-04-26
    河南
  • 本文字数:4469 字

    阅读完需:约 15 分钟

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 相关的介绍。


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


:::

创建一个 MCP Server

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


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


:::

构建服务

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


传送门👉:https://modelcontextprotocol.io/quickstart/server#building-your-server-5


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


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

编写 Tool

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


[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 服务。


dotnet run
复制代码

验证

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


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


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



然后,我们启动 Inspector


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



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




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



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

在业务系统创建 MCP Client

注入 SK 服务

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

编写插件

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


插件的代码如下


[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 系统里进行的演示,所以以接口形式来调用插件,代码如下


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,我们这里可以直接在接口或者服务类里编写调用代码


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



发布于: 2025-04-26阅读数: 2
用户头像

学着写代码 2019-04-11 加入

是一枚,热爱技术,天赋不高,又有点轴,的猿。。

评论

发布
暂无评论
Semantic Kernel也能充当MCP Client_semantic kernel_为自己带盐_InfoQ写作社区