原來使用 .net 寫個 MCP Client 也如此簡單

圖片
之前我發了一篇文章,標題是「原來使用 .NET 寫一個 MCP Server 如此簡單」。結果有不少朋友留言敲碗,說:「只有 Server,沒有 Client,這怎麼行?」

呃…我只能說,這個要求顯然很有道理。🙄

所以今天趁上課的空檔,補一篇文,來介紹如何撰寫 MCP Client ,把整個架構補齊。
你會發現,其實,開發 MCP Client 也非常簡單耶。

MCP 架構的本質

不過,在開始之前,有一個觀念需要釐清:無論是 MCP Server 或 MCP Client,其實都與 LLM(大型語言模型)沒有直接關聯,這和很多人理解的不同。

MCP 架構的本質,只是為了讓 AI Agent(或 Chat Bot)更容易知道怎麼呼叫外部的服務提供者(就叫做MCP Server),並且能方便地列舉出這些服務所提供的功能(就叫做MCP Server Tool),以及清楚了解每個功能呼叫時所需的參數。某種程度上,它的角色有點類似早期 Web Service 時代的 WSDL/SOAP,或是現代 REST API 架構中的 Swagger/OpenAPI 👉👉 都是用來提供一種標準化的服務描述與呼叫方式,使服務整合變得更一致、更具可預測性。

至於,什麼時候該呼叫哪一個功能、呼叫時所要填入的具體參數是什麼,這些判斷與決策的工作,才是由 AI(LLM) 來處理的。而真正去執行這些呼叫動作的,則是 AI Agent(或者稱之為,MCP Host)。

簡單的說就是,對談機器人(或AI Agent)可以藉由 LLM 來呼叫遠端的服務,而 MCP 架構則讓對談機器人可以得知有哪些服務可供呼叫,並且在呼叫時應該要傳入哪些參數。

因此,要開發一個能夠呼叫 MCP Server 服務的 Client 端,自然也需要建立一個具備決策與執行能力的 AI Agent。

這部分,我們待會會採用 Semantic Kernel 作為實作的基礎框架。

之前我們早已介紹過 Semantic Kernel ,它不僅可以介接各種 大語言模型(像是 OpenAI 或 Azure OpenAI),還能彈性地設計與掛上各種 skills 與 plugins ,非常適合用來打造具備「對談 → 意圖理解 → 功能選擇 → 發出呼叫」這一整套流程的AI Agent。

透過它,我們可以輕鬆地將 MCP Client 的呼叫行為內嵌在 AI Agent 當中,同時藉由 LLM 的推理能力,來進行工具的執行與協作。

使用 Semantic Kernel 做一個 AI Agent Client 端

底下這段是建立一個 AI Agent 的 Console 程式碼:

// 準備 Semantic Kernel
var builder = Kernel.CreateBuilder();

// 設定 OpenAI 的 API 金鑰和模型 ID
builder.Services.AddOpenAIChatCompletion(
    modelId: config["OpenAI:ModelId"] ?? "gpt-4o",
    apiKey: config["OpenAI:API_KEY"] ?? ""
);
var kernel = builder.Build();

// Create chat history 物件,並且加入系統訊息
var history = new ChatHistory();
history.AddSystemMessage("你是一位 MCP 工具助理,會根據使用者輸入決定是否要使用 tool 來回答問題。");

// Get chat completion service
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

// 開始對談
Console.Write("User > ");
string? userInput;
while (!string.IsNullOrEmpty(userInput = Console.ReadLine()))
{
    // Add user input
    history.AddUserMessage(userInput);

    // Enable auto function calling
    OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
    };

    // Get the response from the AI
    var result = await chatCompletionService.GetChatMessageContentAsync(
        history,
        executionSettings: openAIPromptExecutionSettings,
        kernel: kernel);

    // Print the results
    Console.WriteLine("Assistant > " + result);

    // Add the message from the agent to the chat history
    history.AddMessage(result.Role, result.Content ?? string.Empty);

    // Get user input again
    Console.Write("User > ");
}

本質上它只是一個 Console 程式,運行起來之後,用戶就可以在迴圈中持續與 AI 對談。

然後,在用戶與 AI 對談的過程當中,LLM 就會根據與用戶對談的內容進行判斷,決定是否需要呼叫 MCP Server 所提供的功能。

例如,我們範例中的例子是查詢已請假天數,只要用戶和AI對談過程中,提到想查詢截至目前為止的請假天數,那LLM就會透過MCP Client呼叫MCP Server中的功能,來查詢請假天數,具體運行狀況如下:

等等,那上面的程式碼當中,並沒有指定MCP Server在哪裡啊?
沒錯,所以我們接著要補上底下這段程式碼,用來在AI Agent中實作 MCP Client,並且連接上MCP Server:

// 建立 MCP client
// 注意:這裡假設 MCP Server 可在本地端運行,並且可以透過 StdioClientTransport 連接
await using IMcpClient mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(
        // 注意:這裡假設 MCP Server 的執行檔在 "../McpServer/McpServer.csproj" 路徑下
        new()
        {
            Name = "LocalMcpServer",
            Command = "dotnet",
            Arguments = ["run", "--project", "../McpServer/McpServer.csproj"],
        }
    )
);

// 取得 tools 並註冊到 Semantic Kernel
var tools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
// 列出 MCP工具名稱和描述

Console.WriteLine("\n\nAvailable MCP Tools:");
foreach (var tool in tools)
{
    Console.WriteLine($"{tool.Name}: {tool.Description}");
}
// 將 MCP 工具轉換為 Semantic Kernel 函數
// 並加入到 Kernel 中
kernel.Plugins.AddFromFunctions("McpTools", tools.Select(t => t.AsKernelFunction()));

上面這段程式碼,則是用來建立一個 MCP Client 連線,並且把 MCP Server 上提供的工具自動轉換成 Semantic Kernel 可識別的函數,註冊進我們以 Semantic Kernel 開發的 AI Agent 中。

這樣一來,當用戶與AI對談的過程中,若 LLM 判斷出某個回應需要呼叫外部工具(MCP Server Tool) 來處理時,就能自動呼叫這些已掛載上來的 MCP Tool 完成對應的操作。

這裡我們透過 StdioClientTransport 的方式來連接 MCP Server,範例中,我們把 MCP Server 建立成另一個 .NET 專案,路徑在 ../McpServer/McpServer.csproj,執行該專案的指令是 dotnet run。如此一來,MCP Client 會自動啟動這個 Server 並建立Server與Client間的通訊。

接著,我們呼叫 mcpClient.ListToolsAsync() 來列出 Server 端提供的所有工具(例如:查詢請假天數、進行請假…等),然後使用 tool.AsKernelFunction() 將這些 MCP 工具轉換為 Semantic Kernel 中的 KernelFunction,並加進 kernel.Plugins 這個集合裡。

總結

整個程式碼實現了底下這幾件事情:

  1. 建立了 AI Agent,讓用戶可在 Console 中輸入文字與AI Agent 對談
  2. 對談過程中, LLM 可藉由分析語意,判斷是否需要呼叫 MCP 工具
  3. 若需要,Agent 就會呼叫 MCP Server 的工具來取得(或寫入)資料
  4. 最後再把執行結果回覆給使用者

執行結果如下:

透過這樣的方式,我們可以把 AI Agent 與外部服務清楚的切分開來,讓耦合性降低,又同時可以實現 AI Agent 與 外部功能可輕鬆合作的效果。

完整的 Repo 我放在 GitHub上,提供給大家參考。
https://github.com/isdaviddong/LeaveRequestMcpClientServer


參考 課程:
https://www.studyhost.tw/NewCourses/LineBot

留言

這個網誌中的熱門文章

原來使用 .net 寫個 MCP Server 如此簡單

使用LM Studio輕鬆在本地端以API呼叫大語言模型(LLM)

開啟 teams 中的『會議轉錄(謄寫)』與Copilot會議記錄、摘要功能

使用 Dify 建立企業請假機器人