背景
筆者之前,分別寫過兩篇關于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的小伙伴上手試一下,雖然案例很簡單,一看就能明白,但那真正跑通的獲得感,還是得自己動手試一下才能體會到。
構建服務
這一步我覺得大家還是直接看官方文檔更清楚,我這里不在贅述
傳送門👉: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的參考站點
- 中文官網:https://mcp-docs.cn/
- MCP.so:https://mcp.so/zh
- 癡者工良的博客:https://www.cnblogs.com/whuanle/p/18837493
- Semantic Kernel的學習文檔:https://learn.microsoft.com/zh-cn/semantic-kernel/
- 本人 之前寫的兩篇介紹SK的博客,本文的代碼部分缺失很多初始化的代碼,均在這兩篇當中https://juejin.cn/post/7460393309552164902,https://juejin.cn/post/7463301527991762955