From 739f2a54faf113720270e3820b885132c1501708 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 23 Feb 2025 19:45:05 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9AIntegrated=20model=20context=20pro?= =?UTF-8?q?tocol=EF=BC=88mcp=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BotSharp.sln | 14 +++ .../BotSharp.Plugin.Mcp.csproj | 22 +++++ .../Functions/McpAIFunction.cs | 96 +++++++++++++++++++ .../BotSharp.Plugin.Mcp/MCPClientManager.cs | 33 +++++++ src/Plugins/BotSharp.Plugin.Mcp/McpPlugin.cs | 45 +++++++++ .../Settings/MCPSettings.cs | 13 +++ src/WebStarter/appsettings.json | 16 ++++ 7 files changed, 239 insertions(+) create mode 100644 src/Plugins/BotSharp.Plugin.Mcp/BotSharp.Plugin.Mcp.csproj create mode 100644 src/Plugins/BotSharp.Plugin.Mcp/Functions/McpAIFunction.cs create mode 100644 src/Plugins/BotSharp.Plugin.Mcp/MCPClientManager.cs create mode 100644 src/Plugins/BotSharp.Plugin.Mcp/McpPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.Mcp/Settings/MCPSettings.cs diff --git a/BotSharp.sln b/BotSharp.sln index d4890be1..d8796f8a 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -127,6 +127,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MCP", "MCP", "{B38A04AB-F4BD-4F10-9662-EC110881533B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Mcp", "src\Plugins\BotSharp.Plugin.Mcp\BotSharp.Plugin.Mcp.csproj", "{9475E249-6964-457C-96C2-3F917111123F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -519,6 +523,14 @@ Global {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|Any CPU.Build.0 = Release|Any CPU {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.ActiveCfg = Release|Any CPU {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Debug|x64.ActiveCfg = Debug|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Debug|x64.Build.0 = Debug|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Release|Any CPU.Build.0 = Release|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Release|x64.ActiveCfg = Release|Any CPU + {9475E249-6964-457C-96C2-3F917111123F}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -580,6 +592,8 @@ Global {F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA} + {B38A04AB-F4BD-4F10-9662-EC110881533B} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C} + {9475E249-6964-457C-96C2-3F917111123F} = {B38A04AB-F4BD-4F10-9662-EC110881533B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Plugins/BotSharp.Plugin.Mcp/BotSharp.Plugin.Mcp.csproj b/src/Plugins/BotSharp.Plugin.Mcp/BotSharp.Plugin.Mcp.csproj new file mode 100644 index 00000000..13417a59 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Mcp/BotSharp.Plugin.Mcp.csproj @@ -0,0 +1,22 @@ + + + + $(TargetFramework) + enable + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(GenerateDocumentationFile) + $(SolutionDir)packages + + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.Mcp/Functions/McpAIFunction.cs b/src/Plugins/BotSharp.Plugin.Mcp/Functions/McpAIFunction.cs new file mode 100644 index 00000000..d19ca1e9 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Mcp/Functions/McpAIFunction.cs @@ -0,0 +1,96 @@ +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Functions; +using McpDotNet.Client; +using McpDotNet.Protocol.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.Mcp.Functions; + +public class McpAIFunction : IFunctionCallback +{ + private readonly Tool _tool; + private readonly IMcpClient _client; + + public McpAIFunction(Tool tool, IMcpClient client) + { + _tool = tool ?? throw new ArgumentNullException(nameof(tool)); + _client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public string Name => _tool.Name; + + public async Task Execute(RoleDialogModel message) + { + // Convert arguments to dictionary format expected by mcpdotnet + Dictionary argDict = JsonToDictionary(message.FunctionArgs); + + // Call the tool through mcpdotnet + var result = await _client.CallToolAsync( + _tool.Name, + argDict.Count == 0 ? new() : argDict + ); + + // Extract the text content from the result + // For simplicity in this sample, we'll just concatenate all text content + message.Content = string.Join("\n", result.Content + .Where(c => c.Type == "text") + .Select(c => c.Text)); + return true; + } + + private static Dictionary JsonToDictionary(string? json) + { + if (string.IsNullOrEmpty(json)) + return []; + + // 使用JsonDocument解析JSON字符串 + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + return JsonElementToDictionary(root); + } + + private static Dictionary JsonElementToDictionary(JsonElement element) + { + Dictionary dictionary = []; + + if (element.ValueKind == JsonValueKind.Object) + { + foreach (JsonProperty property in element.EnumerateObject()) + { + dictionary[property.Name] = JsonElementToValue(property.Value); + } + } + + return dictionary; + } + + private static object? JsonElementToValue(JsonElement element) => element.ValueKind switch + { + JsonValueKind.Object => JsonElementToDictionary(element), + JsonValueKind.Array => element.EnumerateArray().Select(JsonElementToValue).ToList(), + JsonValueKind.String => element.GetString(), + JsonValueKind.Number when element.TryGetInt32(out int intValue) => intValue, + JsonValueKind.Number when element.TryGetInt64(out long longValue) => longValue, + JsonValueKind.Number when element.TryGetDouble(out double doubleValue) => doubleValue, + JsonValueKind.Number when element.TryGetDecimal(out decimal decimalValue) => decimalValue, + JsonValueKind.Number when element.TryGetByte(out byte byteValue) => byteValue, + JsonValueKind.Number when element.TryGetSByte(out sbyte sbyteValue) => sbyteValue, + JsonValueKind.Number when element.TryGetUInt16(out ushort uint16Value) => uint16Value, + JsonValueKind.Number when element.TryGetUInt32(out uint uint32Value) => uint32Value, + JsonValueKind.Number when element.TryGetUInt64(out ulong uint64Value) => uint64Value, + JsonValueKind.Number when element.TryGetDateTime(out DateTime dateTimeValue) => dateTimeValue, + JsonValueKind.Number when element.TryGetDateTimeOffset(out DateTimeOffset dateTimeOffsetValue) => dateTimeOffsetValue, + JsonValueKind.Number when element.TryGetGuid(out Guid guidValue) => guidValue, + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + JsonValueKind.Undefined => string.Empty, // JsonElement is undefined (there is no value). + _ => throw new ArgumentOutOfRangeException(nameof(element.ValueKind), element.ValueKind, "Unexpected JsonValueKind encountered.") + }; + +} diff --git a/src/Plugins/BotSharp.Plugin.Mcp/MCPClientManager.cs b/src/Plugins/BotSharp.Plugin.Mcp/MCPClientManager.cs new file mode 100644 index 00000000..e1f01f14 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Mcp/MCPClientManager.cs @@ -0,0 +1,33 @@ +using BotSharp.Plugin.Mcp.Settings; +using McpDotNet.Client; +using Microsoft.Extensions.Logging; +using System; + +namespace BotSharp.Plugin.Mcp; + +public class MCPClientManager : IDisposable +{ + public ILoggerFactory LoggerFactory { get; } + public McpClientFactory Factory { get; } + + + private readonly MCPSettings mcpSettings; + + public MCPClientManager(MCPSettings settings, ILoggerFactory loggerFactory) + { + mcpSettings = settings; + LoggerFactory = loggerFactory; + + // Inject the mock transport into the factory + Factory = new McpClientFactory( + settings.McpServerConfigs, + settings.McpClientOptions, + LoggerFactory + ); + } + + public void Dispose() + { + LoggerFactory?.Dispose(); + } +} diff --git a/src/Plugins/BotSharp.Plugin.Mcp/McpPlugin.cs b/src/Plugins/BotSharp.Plugin.Mcp/McpPlugin.cs new file mode 100644 index 00000000..2bfda270 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Mcp/McpPlugin.cs @@ -0,0 +1,45 @@ +using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.Mcp.Functions; +using BotSharp.Plugin.Mcp.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace BotSharp.Plugin.Mcp; + +public class McpPlugin : IBotSharpPlugin +{ + public string Id => "5d779611-0012-46cb-a754-4ca4770e88ac"; + public string Name => "MCP Plugin"; + public string Description => "Integrated MCP tools"; + + private MCPClientManager clientManager; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var settings = config.GetSection("MCPSettings").Get(); + services.AddScoped(provider => { return settings; }); + services.AddSingleton(provider => + { + var loggerFactory = provider.GetService() ?? NullLoggerFactory.Instance; + return new MCPClientManager(settings, loggerFactory); + }); + + foreach (var server in settings.McpServerConfigs) + { + var client = clientManager.Factory.GetClientAsync(server.Id).Result; + var tools = client.ListToolsAsync().Result; + + foreach (var tool in tools.Tools) + { + services.AddScoped(provider => + { + var func = new McpAIFunction(tool, client); + return func; + }); + } + } + } + } diff --git a/src/Plugins/BotSharp.Plugin.Mcp/Settings/MCPSettings.cs b/src/Plugins/BotSharp.Plugin.Mcp/Settings/MCPSettings.cs new file mode 100644 index 00000000..a57050d5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Mcp/Settings/MCPSettings.cs @@ -0,0 +1,13 @@ +using McpDotNet.Client; +using McpDotNet.Configuration; +using System.Collections.Generic; + +namespace BotSharp.Plugin.Mcp.Settings; + +public class MCPSettings +{ + public McpClientOptions McpClientOptions { get; set; } + + public List McpServerConfigs { get; set; } + +} diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 655fa954..7b19e043 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -175,6 +175,22 @@ } }, + "McpClientOptions": { + "ClientInfo": { + "Name": "SimpleToolsBotsharp", + "Version": "1.0.0" + } + }, + "McpServerOptions": [ + { + "Id": "everything", + "Name": "Everything", + "TransportType": "sse", + "TransportOptions": {}, + "Location": "http://localhost:5000/sse" + } + ] + }, "Conversation": { "DataDir": "conversations", "ShowVerboseLog": false,