C# McpClientService 的程式範例

  • 63
  • 0
  • MCP
  • 2025-06-06

C# MCP Client,讓MCPServic 能呼叫到。

GitHub - modelcontextprotocol/csharp-sdk: The official C# SDK for Model Context Protocol servers and clients. Maintained in collaboration with Microsoft.

  目前使用範例版本為0.1.0-preview.11,可以正確讓發布的MCP能連線到。

 直接說,這裡面基本都是靠OPEN AI GPT,經過多次DEBUG,組成的。

此程式主要作用為 中央 AI Agent 的分派應用,因此需要透過DB 取得相關子MCP的 AI Agnt 的介紹,讓AI 能辨識正確調用。

 

  public interface IMcpClientService
   {
       Task<string> CallToolAsync(string toolName, string location, Dictionary<string, object?> parameters);
       Task<string> CallTools(string toolName, Dictionary<string, object?> parameters);
   }
   public class McpClientService : IMcpClientService
   {
       private readonly ILogger<McpClientService> _logger;
       private readonly Dictionary<string, IMcpClient> _clientCache;
       private readonly IMcpClient _client;
       private readonly string _baseMcpUrl;
       private readonly ILoggerFactory _loggerFactory; // 新增 ILoggerFactory
       public McpClientService(ILogger<McpClientService> logger, IConfiguration config, ILoggerFactory loggerFactory)
       {
           _logger = logger;            
           _baseMcpUrl = config["McpServer:Url"];
           _clientCache = new Dictionary<string, IMcpClient>();
           _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
       }
       public async Task<string> CallTools(string toolName, Dictionary<string, object?> parameters)
       {
           // 1. 先讓 AI 規劃要呼叫哪些工具(可能包含查詢工具清單、執行業務工具…)
           var planningResult = await CallToolAsync(toolName, _baseMcpUrl, parameters);
           // 2. 解析出多筆步驟
           var steps = ParseIntentStepsFromAiJson(planningResult);   
           return planningResult;
       }

       public async Task<string> CallToolAsync(string toolName, string location, Dictionary<string, object?> parameters)
       {
           if (string.IsNullOrEmpty(toolName) || string.IsNullOrEmpty(location))
           {
               _logger.LogError("[MCP] 工具名稱或服務位置無效,工具: {Tool}, 位置: {Location}", toolName, location);
               return "錯誤: 工具名稱或服務位置無效";
           }
           try
           {
               // 1. 驗證服務位置
               if (!Uri.TryCreate(location, UriKind.Absolute, out var serviceUri))
               {
                   _logger.LogError("[MCP] 服務位置格式無效: {Location}", location);
                   return $"錯誤: 服務位置 {location} 無效";
               }
               // 2. 獲取或創建客戶端
               var cacheKey = $"{location}:{toolName}";
               if (!_clientCache.TryGetValue(cacheKey, out var client))
               {
                   _logger.LogInformation("[MCP] 創建新客戶端,工具: {Tool}, 位置: {Location}", toolName, location);
                   // 創建 SSE 傳輸
                   var transport = new SseClientTransport(new SseClientTransportOptions
                   {
                       Name = $"MCP Client for {toolName}",
                       Endpoint = serviceUri
                       // 如果需要認證,可以添加:
                       // Headers = new Dictionary<string, string> { ["Authorization"] = "Bearer your-api-key" }
                   });
                   // 配置客戶端選項
                   var clientOptions = new McpClientOptions
                   {
                       ClientInfo = new Implementation
                       {
                           Name = $"MCP Client for {toolName}",
                           Version = "1.0.0"
                       }
                   };
                   // 使用 McpClientFactory 創建客戶端
                   client = await McpClientFactory.CreateAsync(transport, clientOptions, _loggerFactory);
                   _clientCache[cacheKey] = client;
               }
               // 3. 調用工具
               _logger.LogInformation("[MCP] 調用工具 {Tool},位置: {Location}, 參數: {Parameters}",
                   toolName, location, JsonSerializer.Serialize(parameters));
               var result = await client.CallToolAsync(toolName, parameters);
               var textResult = result.Content.FirstOrDefault(c => c.Type == "text")?.Text ?? string.Empty;
               _logger.LogInformation("[MCP] 工具 {Tool} 執行結果: {Result}", toolName, textResult);
               return textResult;
           }
           catch (Exception ex)
           {
               _logger.LogError(ex, "[MCP] Mauritania調用工具 {Tool} 失敗,位置: {Location}", toolName, location);
               return $"系統忙碌中,請重新整理。";
              // return $"錯誤: 調用工具 {toolName} 失敗: {ex.Message}";
           }
       }      

       /// 從 AI 回傳的 JSON 字串中,解析出多筆 IntentStep
       /// </summary>
       private List<IntentStep> ParseIntentStepsFromAiJson(string aiJson)
       {
           if (string.IsNullOrWhiteSpace(aiJson))
               return new List<IntentStep>();
           try
           {
               // 1. 解析最外層 JSON,取出 message 欄位
               using var doc = JsonDocument.Parse(aiJson);
               var root = doc.RootElement;
               if (!root.TryGetProperty("message", out var messageElement))
                   return new List<IntentStep>();
               var rawMessage = messageElement.GetString() ?? "";
               // 2. 去除 ```json ``` 等 Markdown 包裹
               var cleaned = Regex.Replace(rawMessage, "```json|```", "", RegexOptions.IgnoreCase).Trim();
               // 3. 反序列化成 List<IntentStep>
               var steps = JsonSerializer.Deserialize<List<IntentStep>>(cleaned);
               return steps ?? new List<IntentStep>();
           }
           catch (Exception ex)
           {
               _logger.LogError(ex, "[MCP] 解析多步驟 Intent 發生錯誤");
               return new List<IntentStep>();
           }
       }
       public class AiWrapper
       {
           public string Message { get; set; } = string.Empty;
       }
       public class IntentStep
       {
           public string Tool { get; set; } = string.Empty;
           public string UsageType { get; set; } = string.Empty;
           public string ServiceLocation { get; set; } = string.Empty;
           public Dictionary<string, object> Parameters { get; set; } = new();
       }
   }