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();
}
}