在 Day 23 中,介紹如何使用 Semantic Kernel 使用 Streamable HTTP 模式串接 MCP Server ,今天來另一種 Stdio 模式串接 MCP Server。Stdio 模式是透過標準輸入輸出進行通訊,適合在本地環境或受限網路環境中使用,Stdio 模式下,MCP Server 會與執行在本地端的 MCP Client(例如 LLM runtime 或自訂應用)透過標準輸入輸出來交換 JSON-RPC 訊息。簡單來說,就是在本地端啟動一個 MCP Server 程式,然後「透過標準輸入輸出進行通訊」與 MCP Client(你的 LLM 應用) 進行互動,過程中 MCP Client 會把請求透過 stdin 傳給 MCP Server,MCP Server 再透過 stdout 把回應送回去,整個過程像是在同一個機器內部用管線(pipe)傳資料。
為了示範 Stdio 模式,需要先建立一個 MCP Server 的範例程式,這裡我使用 .NET 主控台應用程式來實現,它提供了訂單查詢的功能,可以使用訂單編號查詢或是客戶姓名關鍵字模糊查詢。此外要建立本地 MCP Server 範例程式,還需要官方的 MCP .NET 套件 ModelContextProtocol
的支援。
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting --version 9.0.6
OrderTool
,提供兩個方法 GetOrderById
和 SearchOrdersByCustomerName
,分別用於根據訂單編號查詢訂單和根據客戶姓名關鍵字模糊查詢訂單。作為 MCP Server 工具,必須使用 [McpServerToolType]
標註類別,並使用 [McpServerTool]
標註方法,這樣 MCP Server 才能識別並暴露這些工具給 MCP Client 使用。另外,使用 Description
屬性來提供方法的描述,這部份相關重要,它是有助於 MCP Client 理解工具的功能。[McpServerToolType]
public class OrderTool
{
// --- 模擬資料(實務上應該從資料庫取得) ---
private static readonly List<OrderDto> _orders =
[
new(1001, "王小美", new(2025, 6, 1), 1299, "已出貨"),
new(1002, "陳錢錢", new(2025, 6, 3), 2599, "處理中"),
new(1003, "阿土伯", new(2025, 6, 5), 499, "已取消")
];
// 依訂單編號查詢
[McpServerTool,
Description("Query order data by order ID")]
public OrderDto? GetOrderById(int orderId) =>
_orders.FirstOrDefault(o => o.Id == orderId);
// 依客戶姓名關鍵字模糊查詢
[McpServerTool,
Description("Query order list by customer name keyword")]
public IEnumerable<OrderDto> SearchOrdersByCustomer(string keyword) =>
_orders.Where(o =>
o.Customer.Contains(keyword, StringComparison.OrdinalIgnoreCase));
}
Program.cs
,使用 AddMcpServer
來建立 MCP Server 主機,並在指定 WithStdioServerTransport
使用使用 Stdio 傳輸方式,最後透過 WithToolsFromAssembly
掃描工具類,將工具加入到 MCP Server 中,最後用 await builder.Build().RunAsync()
啟動 MCP Server 。Console.WriteLine("Hello, MCP Server!");
var builder = Host.CreateApplicationBuilder(args);
// 建立 MCP Server,採用 Stdio 傳輸並自動掃描工具
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly(); // 掃描工具類別
await builder.Build().RunAsync();
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
apiKey: Config.OpenAI_ApiKey,
modelId: Config.ModelId)
.Build();
run
來執行指定路徑的 MCP Server 專案,根據專案路徑調整 --project
的值 //連線到 MCP Server (stdio模式,指定MCP Server Project Path)
var clientTransport = new StdioClientTransport(new()
{
Name = "OrderServer",
Command = "dotnet",
Arguments = ["run", "--project", "../mymcpserver"]
});
await using var mcpClient = await McpClient.CreateAsync(clientTransport!);
// 取得 MCP Server 工具清單
var tools = await mcpClient.ListToolsAsync();
// 工具清單顯示
foreach (var tool in tools)
{
Console.WriteLine($"Connected to My MCP server with tools: {tool.Name}");
}
// 匯入工具並組裝 Agent
kernel.Plugins.AddFromFunctions("McpTools", tools.Select(t => t.AsKernelFunction()));
// 建立 Agent
ChatCompletionAgent agent =
new()
{
Name = "SupportAgent",
Description = "一個可以回答訂單資訊的助手",
Instructions = @"你是一位專業且有禮貌的助手,負責協助顧客查詢訂單資訊。請依照以下規則提供回覆:
1. 如果詢問訂單狀態,請引導提供訂單編號或是顧客姓名,並使用已提供的查詢工具查詢對應資料。
2. 回覆時要友善、清楚,避免使用過於技術化的語言。
3. 若問題無法直接回答,請告知「我會轉交給專人協助」,並避免捏造答案。
4. 僅限提供查詢訂單狀態,若超出職責範圍,請禮貌回覆並引導聯繫客服專線0800-888-888。",
Kernel = kernel,
Arguments = new(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
};
// 建立對話歷史 thread
ChatHistoryAgentThread agentThread = new();
Console.Write("User > ");
string? userInput;
while ((userInput = Console.ReadLine()) is not null)
{
if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
ChatMessageContent message = new(AuthorRole.User, userInput);
bool isFirst = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
if (string.IsNullOrEmpty(response.Content))
{
StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType<StreamingFunctionCallUpdateContent>().SingleOrDefault();
//追蹤函數調用
if (!string.IsNullOrEmpty(functionCall?.Name))
{
Console.WriteLine($"\n# trace {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {functionCall.Name}");
}
continue;
}
if (!isFirst)
{
Console.Write($"{response.Role} - {response.AuthorName ?? "*"} > ");
isFirst = true;
}
Console.Write($"{response.Content}");
}
Console.WriteLine();
Console.WriteLine($"\n# trace chat thread with agent: {agent.Name} - {agent.Description},threadId: {agentThread.Id} \n");
Console.Write("User > ");
}
Connected to My MCP server with tools: search_orders_by_customer
Connected to My MCP server with tools: get_order_by_id
User > 阿土伯的訂單現在情況是什麼
# trace Assistant - SupportAgent: FUNCTION CALL - McpTools-search_orders_by_customer
assistant - SupportAgent > 阿土伯的訂單目前狀態是「已取消」。如果您有其他訂單需要查詢,或需要更多協助,歡迎提供訂單編號或進一步告知。我很樂意協助您!
# trace chat thread with agent: SupportAgent - 一個可以回答訂單資訊的助手,threadId: 7283c2f704d9415aaafd02e7b407e76a
透過 Stdio 模式串接 MCP Server,可以在本地環境中打造 MCP Server + MCP Client 的架構,適合在受限網路環境中使用,或是本地工具的整合,例如讀取本地的檔案系統,或是調用本地CLI服務等,同時也利用 MCP Server 提供的工具來增強 LLM 的能力。簡單來說,Stdio 適合「本地、桌面、工具類」的應用場景,而 Streamable HTTP 適合「Web、遠程、服務類」的場景!