背景
笔者之前,分别写过两篇关于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的参考站点
- 中文官网:mcp-docs.cn/
- MCP.so:mcp.so/zh
- 痴者工良的博客:www.cnblogs.com/whuanle/p/1...
- Semantic Kernel的学习文档:learn.microsoft.com/zh-cn/seman...
- 本人 之前写的两篇介绍SK的博客,本文的代码部分缺失很多初始化的代码,均在这两篇当中juejin.cn/post/746039...,juejin.cn/post/746330...