diff --git a/BotSharp.sln b/BotSharp.sln index d4890be1..d82f930c 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -127,6 +127,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj", "{8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -519,6 +521,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 + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Debug|x64.Build.0 = Debug|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Release|Any CPU.Build.0 = Release|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Release|x64.ActiveCfg = Release|Any CPU + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -580,6 +590,7 @@ 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} + {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index bec14b85..1103f2f5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -57,5 +57,11 @@ public abstract class AgentHookBase : IAgentHook public virtual void OnAgentUtilityLoaded(Agent agent) { + + } + + public virtual void OnAgentMCPToolLoaded(Agent agent) + { + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs index 0c39a960..413f9eb8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs @@ -19,6 +19,7 @@ public enum AgentField Sample, LlmConfig, Utility, + Acp, KnowledgeBase, Rule, MaxMessageCount diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index a3f53fb6..624616d7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -27,6 +27,8 @@ public interface IAgentHook void OnAgentUtilityLoaded(Agent agent); + void OnAgentMCPToolLoaded(Agent agent); + /// /// Triggered when agent is loaded completely. /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 085b0f9d..fc28e571 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -95,15 +95,20 @@ public class Agent public List Labels { get; set; } = new(); /// - /// Merge utilities from entry agent + /// Merge Utility from entry agent /// public bool MergeUtility { get; set; } /// - /// Agent utilities + /// Agent Utility /// public List Utilities { get; set; } = new(); + /// + /// Agent MCP Tools + /// + public List McpTools { get; set; } = new(); + /// /// Agent rules /// @@ -157,6 +162,7 @@ public class Agent Responses = agent.Responses, Samples = agent.Samples, Utilities = agent.Utilities, + McpTools = agent.McpTools, Knowledges = agent.Knowledges, IsPublic = agent.IsPublic, Disabled = agent.Disabled, @@ -298,4 +304,10 @@ public class Agent LlmConfig = llmConfig; return this; } + + public Agent SetMcps(List mcps) + { + McpTools = mcps ?? []; + return this; + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs new file mode 100644 index 00000000..fc8fbb62 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs @@ -0,0 +1,41 @@ +namespace BotSharp.Abstraction.Agents.Models; + +public class MCPTool +{ + public string Name { get; set; } + + public string ServerId { get; set; } + + public bool Disabled { get; set; } + + public IEnumerable Functions { get; set; } = []; + + public MCPTool() + { + + } + + public MCPTool( + string name, + IEnumerable? functions = null) + { + Name = name; + Functions = functions ?? []; + } + + public override string ToString() + { + return Name; + } +} + + +public class MCPFunction +{ + public string Name { get; set; } + + public MCPFunction() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs index 1101f160..5ab0ea03 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs @@ -38,6 +38,7 @@ public class BasicAgentHook : AgentHookBase agent.SecondaryInstructions.Add(prompt); } } + private (IEnumerable, IEnumerable) GetUtilityContent(Agent agent) { @@ -81,5 +82,5 @@ public class BasicAgentHook : AgentHookBase .Distinct().ToList(); return (functionNames, templateNames); - } + } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 15ba26a1..7fd94340 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -71,6 +71,11 @@ public partial class AgentService { hook.OnAgentUtilityLoaded(agent); } + + if(agent.McpTools != null) + { + hook.OnAgentMCPToolLoaded(agent); + } hook.OnAgentLoaded(agent); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index a9cc58d9..7e9da19c 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -40,6 +40,7 @@ public partial class AgentService record.Responses = agent.Responses ?? []; record.Samples = agent.Samples ?? []; record.Utilities = agent.Utilities ?? []; + record.McpTools = agent.McpTools ?? []; record.KnowledgeBases = agent.KnowledgeBases ?? []; record.Rules = agent.Rules ?? []; if (agent.LlmConfig != null && !agent.LlmConfig.IsInherit) @@ -106,6 +107,7 @@ public partial class AgentService .SetResponses(foundAgent.Responses) .SetSamples(foundAgent.Samples) .SetUtilities(foundAgent.Utilities) + .SetMcps(foundAgent.McpTools) .SetKnowledgeBases(foundAgent.KnowledgeBases) .SetRules(foundAgent.Rules) .SetLlmConfig(foundAgent.LlmConfig); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index d7a6d121..ae401508 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -60,6 +60,9 @@ namespace BotSharp.Core.Repository case AgentField.Utility: UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities); break; + case AgentField.Acp: + UpdateAgentMcpTools(agent.Id, agent.McpTools); + break; case AgentField.KnowledgeBase: UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases); break; @@ -191,6 +194,20 @@ namespace BotSharp.Core.Repository File.WriteAllText(agentFile, json); } + private void UpdateAgentMcpTools(string agentId, List mcptools) + { + if (mcptools == null) return; + + var (agent, agentFile) = GetAgentFromFile(agentId); + if (agent == null) return; + + + agent.McpTools = mcptools; + agent.UpdatedDateTime = DateTime.UtcNow; + var json = JsonSerializer.Serialize(agent, _options); + File.WriteAllText(agentFile, json); + } + private void UpdateAgentKnowledgeBases(string agentId, List knowledgeBases) { if (knowledgeBases == null) return; @@ -360,6 +377,7 @@ namespace BotSharp.Core.Repository agent.Profiles = inputAgent.Profiles; agent.Labels = inputAgent.Labels; agent.Utilities = inputAgent.Utilities; + agent.McpTools = inputAgent.McpTools; agent.KnowledgeBases = inputAgent.KnowledgeBases; agent.RoutingRules = inputAgent.RoutingRules; agent.Rules = inputAgent.Rules; diff --git a/src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs b/src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs new file mode 100644 index 00000000..e86cbf92 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs @@ -0,0 +1,37 @@ +using BotSharp.Abstraction.Functions.Models; +using McpDotNet.Protocol.Types; +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BotSharp.Core.MCP; + +internal static class AIFunctionUtilities +{ + public static FunctionDef MapToFunctionDef(Tool tool) + { + if (tool == null) + { + throw new ArgumentNullException(nameof(tool)); + } + + var properties = tool.InputSchema?.Properties; + var required = tool.InputSchema?.Required ?? new List(); + + FunctionDef funDef = new FunctionDef + { + Name = tool.Name, + Description = tool.Description?? string.Empty, + Type = "function", + Parameters = new FunctionParametersDef + { + Type = "object", + Properties = properties != null ? JsonSerializer.SerializeToDocument(properties) : JsonDocument.Parse("{}"), + Required = required + } + }; + + return funDef; + } +} diff --git a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj new file mode 100644 index 00000000..c171a732 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj @@ -0,0 +1,18 @@ + + + + $(TargetFramework) + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(SolutionDir)packages + + + + + + + + + + diff --git a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs new file mode 100644 index 00000000..5df8fb07 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.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.Core.Mcp.Functions; + +public class McpToolFunction : IFunctionCallback +{ + private readonly Tool _tool; + private readonly IMcpClient _client; + + public McpToolFunction(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/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs new file mode 100644 index 00000000..a4ac94b7 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs @@ -0,0 +1,71 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Agents.Settings; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Core.Mcp; +using BotSharp.Core.MCP; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BotSharp.MCP.Hooks; + +public class MCPToolAgentHook : AgentHookBase +{ + public override string SelfId => string.Empty; + + public MCPToolAgentHook(IServiceProvider services, AgentSettings settings) + : base(services, settings) + { + } + + public override void OnAgentMCPToolLoaded(Agent agent) + { + if (agent.Type == AgentType.Routing) + return; + + var conv = _services.GetRequiredService(); + var isConvMode = conv.IsConversationMode(); + if (!isConvMode) return; + + agent.SecondaryFunctions ??= []; + agent.SecondaryInstructions ??= []; + agent.McpTools ??= []; + + var functions = GetMCPContent(agent); + + foreach (var fn in functions) + { + if (!agent.SecondaryFunctions.Any(x => x.Name.Equals(fn.Name, StringComparison.OrdinalIgnoreCase))) + { + agent.SecondaryFunctions.Add(fn); + } + } + } + + private IEnumerable GetMCPContent(Agent agent) + { + List functionDefs = new List(); + var mcpClientManager = _services.GetRequiredService(); + var mcps = agent.McpTools; + foreach (var item in mcps) + { + var ua = mcpClientManager.Factory.GetClientAsync(item.ServerId).Result; + if (ua != null) + { + var tools = ua.ListToolsAsync().Result; + var funcnames = item.Functions.Select(x => x.Name).ToList(); + foreach (var tool in tools.Tools.Where(x => funcnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))) + { + var funDef = AIFunctionUtilities.MapToFunctionDef(tool); + functionDefs.Add(funDef); + } + } + } + + return functionDefs; + } +} diff --git a/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs b/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs new file mode 100644 index 00000000..bbe70205 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs @@ -0,0 +1,33 @@ +using BotSharp.Core.Mcp.Settings; +using McpDotNet.Client; +using Microsoft.Extensions.Logging; +using System; + +namespace BotSharp.Core.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/Infrastructure/BotSharp.MCP/McpPlugin.cs b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs new file mode 100644 index 00000000..c54a7050 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs @@ -0,0 +1,45 @@ +using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Plugins; +using BotSharp.Core.Mcp.Functions; +using BotSharp.Core.Mcp.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace BotSharp.Core.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 McpToolFunction(tool, client); + return func; + }); + } + } + } + } diff --git a/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs b/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs new file mode 100644 index 00000000..34e408e3 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs @@ -0,0 +1,13 @@ +using McpDotNet.Client; +using McpDotNet.Configuration; +using System.Collections.Generic; + +namespace BotSharp.Core.Mcp.Settings; + +public class MCPSettings +{ + public McpClientOptions McpClientOptions { get; set; } + + public List McpServerConfigs { get; set; } + +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs index 8e4d915d..81ec1bc7 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentCreationModel.cs @@ -55,6 +55,7 @@ public class AgentCreationModel public int? MaxMessageCount { get; set; } public List Utilities { get; set; } = new(); + public List McpTools { get; set; } = new(); public List RoutingRules { get; set; } = new(); public List KnowledgeBases { get; set; } = new(); public List Rules { get; set; } = new(); @@ -73,6 +74,7 @@ public class AgentCreationModel Responses = Responses, Samples = Samples, Utilities = Utilities, + McpTools = McpTools, IsPublic = IsPublic, Type = Type, Disabled = Disabled, diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs index ac2749b5..9f2925e5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentUpdateModel.cs @@ -39,6 +39,11 @@ public class AgentUpdateModel /// public List? Utilities { get; set; } + /// + /// McpTools + /// + public List? McpTools { get; set; } + /// /// knowledge bases /// @@ -103,6 +108,7 @@ public class AgentUpdateModel Functions = Functions ?? [], Responses = Responses ?? [], Utilities = Utilities ?? [], + McpTools = McpTools ?? [], KnowledgeBases = KnowledgeBases ?? [], Rules = Rules ?? [], LlmConfig = LlmConfig ?? new() diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index b4ba1e08..db3af34c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -24,6 +24,7 @@ public class AgentViewModel [JsonPropertyName("merge_utility")] public bool MergeUtility { get; set; } public List Utilities { get; set; } + public List Acps { get; set; } [JsonPropertyName("knowledge_bases")] public List KnowledgeBases { get; set; } @@ -86,6 +87,7 @@ public class AgentViewModel Responses = agent.Responses ?? [], Samples = agent.Samples ?? [], Utilities = agent.Utilities ?? [], + Acps = agent.McpTools ?? [], KnowledgeBases = agent.KnowledgeBases ?? [], IsPublic= agent.IsPublic, Disabled = agent.Disabled, diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs index bdfdbe80..a0d99e0d 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs @@ -12,17 +12,18 @@ public class AgentDocument : MongoBase public bool Disabled { get; set; } public bool MergeUtility { get; set; } public int? MaxMessageCount { get; set; } - public List ChannelInstructions { get; set; } = []; - public List Templates { get; set; } = []; - public List Functions { get; set; } = []; - public List Responses { get; set; } = []; - public List Samples { get; set; } = []; - public List Utilities { get; set; } = []; - public List KnowledgeBases { get; set; } = []; - public List Profiles { get; set; } = []; - public List Labels { get; set; } = []; - public List RoutingRules { get; set; } = []; - public List Rules { get; set; } = []; + public List ChannelInstructions { get; set; } + public List Templates { get; set; } + public List Functions { get; set; } + public List Responses { get; set; } + public List Samples { get; set; } + public List Utilities { get; set; } + public List McpTools { get; set; } + public List KnowledgeBases { get; set; } + public List Profiles { get; set; } + public List Labels { get; set; } + public List RoutingRules { get; set; } + public List Rules { get; set; } public AgentLlmConfigMongoElement? LlmConfig { get; set; } public DateTime CreatedTime { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs new file mode 100644 index 00000000..dae4c665 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs @@ -0,0 +1,73 @@ +using BotSharp.Abstraction.Agents.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.MongoStorage.Models; + +[BsonIgnoreExtraElements(Inherited = true)] +public class AgentMCPToolMongoElement +{ + public string Name { get; set; } + + public string ServerId { get; set; } + + public bool Disabled { get; set; } + public List Functions { get; set; } = []; + public List Templates { get; set; } = []; + + public static AgentMCPToolMongoElement ToMongoElement(MCPTool utility) + { + return new AgentMCPToolMongoElement + { + Name = utility.Name, + Disabled = utility.Disabled, + Functions = utility.Functions?.Select(x => new McpFunctionMongoElement(x.Name))?.ToList() ?? [], + Templates = utility.Templates?.Select(x => new McpTemplateMongoElement(x.Name))?.ToList() ?? [] + }; + } + + public static MCPTool ToDomainElement(AgentMCPToolMongoElement utility) + { + return new MCPTool + { + Name = utility.Name, + Disabled = utility.Disabled, + Functions = utility.Functions?.Select(x => new MCPFunction(x.Name))?.ToList() ?? [], + Templates = utility.Templates?.Select(x => new ACPTemplate(x.Name))?.ToList() ?? [] + }; + } +} + +public class McpFunctionMongoElement +{ + public string Name { get; set; } + + public McpFunctionMongoElement() + { + + } + + public McpFunctionMongoElement(string name) + { + Name = name; + } +} + +public class McpTemplateMongoElement +{ + public string Name { get; set; } + + public McpTemplateMongoElement() + { + + } + + public McpTemplateMongoElement(string name) + { + Name = name; + } +} + diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 90dd9cdf..069845ef 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -61,6 +61,9 @@ public partial class MongoRepository case AgentField.Utility: UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities); break; + case AgentField.Acp: + UpdateAgentMcps(agent.Id, agent.McpTools); + break; case AgentField.KnowledgeBase: UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases); break; @@ -261,6 +264,19 @@ public partial class MongoRepository _dc.Agents.UpdateOne(filter, update); } + private void UpdateAgentMcps(string agentId, List mcps) + { + if (mcps == null) return; + + var elements = mcps?.Select(x => AgentMCPToolMongoElement.ToMongoElement(x))?.ToList() ?? []; + + var filter = Builders.Filter.Eq(x => x.Id, agentId); + var update = Builders.Update + .Set(x => x.McpTools, elements) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Agents.UpdateOne(filter, update); + } private void UpdateAgentKnowledgeBases(string agentId, List knowledgeBases) { if (knowledgeBases == null) return; @@ -330,6 +346,7 @@ public partial class MongoRepository .Set(x => x.Responses, agent.Responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList()) .Set(x => x.Samples, agent.Samples) .Set(x => x.Utilities, agent.Utilities.Select(u => AgentUtilityMongoElement.ToMongoElement(u)).ToList()) + .Set(x => x.McpTools, agent.McpTools.Select(u => AgentMCPToolMongoElement.ToMongoElement(u)).ToList()) .Set(x => x.KnowledgeBases, agent.KnowledgeBases.Select(u => AgentKnowledgeBaseMongoElement.ToMongoElement(u)).ToList()) .Set(x => x.Rules, agent.Rules.Select(e => AgentRuleMongoElement.ToMongoElement(e)).ToList()) .Set(x => x.LlmConfig, AgentLlmConfigMongoElement.ToMongoElement(agent.LlmConfig)) @@ -510,6 +527,7 @@ public partial class MongoRepository Responses = x.Responses?.Select(r => AgentResponseMongoElement.ToMongoElement(r))?.ToList() ?? [], RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [], Utilities = x.Utilities?.Select(u => AgentUtilityMongoElement.ToMongoElement(u))?.ToList() ?? [], + McpTools = x.McpTools?.Select(u => AgentMCPToolMongoElement.ToMongoElement(u))?.ToList() ?? [], KnowledgeBases = x.KnowledgeBases?.Select(k => AgentKnowledgeBaseMongoElement.ToMongoElement(k))?.ToList() ?? [], Rules = x.Rules?.Select(e => AgentRuleMongoElement.ToMongoElement(e))?.ToList() ?? [], CreatedTime = x.CreatedDateTime, @@ -604,6 +622,7 @@ public partial class MongoRepository Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [], RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [], Utilities = agentDoc.Utilities?.Select(u => AgentUtilityMongoElement.ToDomainElement(u))?.ToList() ?? [], + McpTools = agentDoc.McpTools?.Select(u => AgentMCPToolMongoElement.ToDomainElement(u))?.ToList() ?? [], KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? [], Rules = agentDoc.Rules?.Select(e => AgentRuleMongoElement.ToDomainElement(e))?.ToList() ?? [] }; diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index a0f02893..b8ab5f02 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -174,7 +174,23 @@ "Model": "gpt-4o-mini" } }, - + "MCPSettings": { + "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,