diff --git a/BotSharp.sln b/BotSharp.sln index b176747f..c6c75391 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -127,7 +127,9 @@ 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.Core.Realtime", "src\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj", "{7ACD8C95-C66B-436A-80E7-541A57D8C3F4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj", "{8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{AD91B4ED-0623-4710-913E-6D7C893E76EF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -521,14 +523,6 @@ 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 - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Debug|x64.Build.0 = Debug|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Release|Any CPU.Build.0 = Release|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Release|x64.ActiveCfg = Release|Any CPU - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -590,7 +584,6 @@ 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} - {7ACD8C95-C66B-436A-80E7-541A57D8C3F4} = {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..a555a7e5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs @@ -0,0 +1,37 @@ +namespace BotSharp.Abstraction.Agents.Models; + +public class MCPTool +{ + public string ServerId { get; set; } + + public bool Disabled { get; set; } + + public IEnumerable Functions { get; set; } = []; + + public MCPTool() + { + + } + + public MCPTool( + IEnumerable? functions = null) + { + Functions = functions ?? []; + } + + public override string ToString() + { + return ServerId; + } +} + + +public class MCPFunction +{ + public string Name { get; set; } + + public MCPFunction(string name) + { + this.Name = name; + } +} 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..c8f97206 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 && agent.McpTools.Count >0) + { + 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..e9727f7f --- /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..97ec724f --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs @@ -0,0 +1,107 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Functions; +using McpDotNet.Client; +using McpDotNet.Protocol.Types; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +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 MCPClientManager _clientManager; + private readonly IServiceProvider _serviceProvider; + + public McpToolFunction(IServiceProvider provider, Tool tool, MCPClientManager client) + { + _serviceProvider = provider ?? throw new ArgumentNullException(nameof(provider)); + _tool = tool ?? throw new ArgumentNullException(nameof(tool)); + _clientManager = 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); + var currentAgentId = message.CurrentAgentId; + var agentService = _serviceProvider.GetRequiredService(); + var agent = await agentService.LoadAgent(currentAgentId); + var serverId = agent.McpTools.Where(t => t.Functions.Any(f => f.Name == Name)).FirstOrDefault().ServerId; + + var client = await _clientManager.Factory.GetClientAsync(serverId); + // Call the tool through mcpdotnet + var result = await client.CallToolAsync( + _tool.Name, + argDict.Count == 0 ? new() : argDict + ); + + // Extract the text content from the result + var json = string.Join("\n", result.Content + .Where(c => c.Type == "text") + .Select(c => c.Text)); + message.Content = json; + message.Data = JsonSerializer.Deserialize(json,typeof(object)); + return true; + } + + private static Dictionary JsonToDictionary(string? json) + { + if (string.IsNullOrEmpty(json)) + return []; + + 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..32b10d0a --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs @@ -0,0 +1,69 @@ +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; +using System.Threading.Tasks; + +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 ??= []; + + var functions = GetMCPContent(agent).Result; + + foreach (var fn in functions) + { + if (!agent.SecondaryFunctions.Any(x => x.Name.Equals(fn.Name, StringComparison.OrdinalIgnoreCase))) + { + agent.SecondaryFunctions.Add(fn); + } + } + } + + private async Task> GetMCPContent(Agent agent) + { + List functionDefs = new List(); + var mcpClientManager = _services.GetRequiredService(); + var mcps = agent.McpTools; + foreach (var item in mcps) + { + var mcpClient = await mcpClientManager.Factory.GetClientAsync(item.ServerId); + if (mcpClient != null) + { + var tools = await mcpClient.ListToolsAsync(); + 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..3d8ec0c8 --- /dev/null +++ b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs @@ -0,0 +1,50 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Plugins; +using BotSharp.Core.Mcp.Functions; +using BotSharp.Core.Mcp.Settings; +using BotSharp.MCP.Hooks; +using McpDotNet.Protocol.Types; +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; }); + + clientManager = new MCPClientManager(settings, NullLoggerFactory.Instance); + services.AddSingleton(clientManager); + + 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 => { return tool; }); + + services.AddScoped( provider => { + var funcTool = new McpToolFunction( provider, tool, clientManager); + return funcTool; + }); + } + } + // Register hooks + services.AddScoped(); + } + } 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/Request/AgentCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs index 8e4d915d..81ec1bc7 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/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/Request/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs index ac2749b5..9f2925e5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/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/View/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs index b4ba1e08..db3af34c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/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..ecad71f3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs @@ -0,0 +1,69 @@ +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 + { + Disabled = utility.Disabled, + Functions = utility.Functions?.Select(x => new McpFunctionMongoElement(x.Name))?.ToList() ?? [], + }; + } + + public static MCPTool ToDomainElement(AgentMCPToolMongoElement utility) + { + return new MCPTool + { + Disabled = utility.Disabled, + Functions = utility.Functions?.Select(x => new MCPFunction(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/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 78c4537c..d06fa30a 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -27,8 +27,10 @@ + + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 9d8afd80..2582a8ae 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -50,10 +50,7 @@ "Name": "gpt-35-turbo", "Version": "1106", "ApiKey": "", - "Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/", - "Type": "chat", - "PromptCost": 0.0015, - "CompletionCost": 0.002 + "Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/" }, { "Name": "gpt-35-turbo-instruct", @@ -170,11 +167,29 @@ "HostAgentId": "01e2fc5c-2c89-4ec7-8470-7688608b496c", "EnableTranslator": false, "LlmConfig": { - "Provider": "openai", + "Provider": "azure-openai", "Model": "gpt-4o-mini" } }, - + "MCPSettings": { + "McpClientOptions": { + "ClientInfo": { + "Name": "SimpleToolsBotsharp", + "Version": "1.0.0" + } + }, + "McpServerConfigs": [ + { + "Id": "PizzaServer", + "Name": "PizzaServer", + "TransportType": "stdio", + "TransportOptions": { + "command": "BotSharp.PizzaBot.MCPServer.exe", + "workingDirectory": "E:\\GitHub\\BotSharp\\src\\WebStarter\\bin\\Debug\\net8.0" + } + } + ] + }, "Conversation": { "DataDir": "conversations", "ShowVerboseLog": false, @@ -397,6 +412,7 @@ "BotSharp.Core.Crontab", "BotSharp.Core.Realtime", "BotSharp.Logger", + "BotSharp.MCP", "BotSharp.Plugin.MongoStorage", "BotSharp.Plugin.Dashboard", "BotSharp.Plugin.OpenAI", diff --git a/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj b/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj new file mode 100644 index 00000000..1158e06a --- /dev/null +++ b/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + 12.0 + enable + + + + + + + + diff --git a/tests/BotSharp.PizzaBot.MCPServer/Program.cs b/tests/BotSharp.PizzaBot.MCPServer/Program.cs new file mode 100644 index 00000000..5e8633f8 --- /dev/null +++ b/tests/BotSharp.PizzaBot.MCPServer/Program.cs @@ -0,0 +1,204 @@ +using McpDotNet.Protocol.Transport; +using McpDotNet.Protocol.Types; +using McpDotNet.Server; +using Microsoft.Extensions.Logging; +using Serilog; +using System.Dynamic; +using System.Text; +using System.Text.Json; + +namespace BotSharp.PizzaBot.MCPServer +{ + internal class Program + { + private static async Task Main(string[] args) + { + Console.WriteLine("Starting server..."); + + McpServerOptions options = new McpServerOptions() + { + ServerInfo = new Implementation() { Name = "PizzaServer", Version = "1.0.0" }, + Capabilities = new ServerCapabilities() + { + Tools = new(), + Resources = new(), + Prompts = new(), + }, + ProtocolVersion = "2024-11-05" + }; + var loggerFactory = CreateLoggerFactory(); + McpServerFactory factory = new McpServerFactory(new StdioServerTransport("PizzaServer", loggerFactory), options, loggerFactory, + "This is a test server with only stub functionality"); + IMcpServer server = factory.CreateServer(); + + Console.WriteLine("Server object created, registering handlers."); + + #region Tools + server.ListToolsHandler = (request, cancellationToken) => + { + return Task.FromResult(new ListToolsResult() + { + Tools = + [ + new Tool() + { + Name = "make_payment", + Description = "call this function to make payment", + InputSchema = new JsonSchema() + { + Type = "object", + Properties = new Dictionary() + { + ["order_number"] = new JsonSchemaProperty() { Type = "string", Description = "order number." }, + ["total_amount"] = new JsonSchemaProperty() { Type = "string", Description = "total amount." }, + }, + Required = new List() { "order_number", "total_amount" } + }, + }, + new Tool() + { + Name = "get_pizza_prices", + Description = "call this function to get pizza prices", + InputSchema = new JsonSchema() + { + Type = "object", + Properties = new Dictionary() + { + ["pizza_type"] = new JsonSchemaProperty() { Type = "string", Description = "The pizza type." }, + ["quantity"] = new JsonSchemaProperty() { Type = "string", Description = "quantity of pizza." }, + + }, + Required = new List(){ "pizza_type", "quantity" } + } + }, + new Tool() + { + Name = "place_an_order", + Description = "Place an order when user has confirmed the pizza type and quantity.", + InputSchema = new JsonSchema() + { + Type = "object", + Properties = new Dictionary() + { + ["pizza_type"] = new JsonSchemaProperty() { Type = "string", Description = "The pizza type." }, + ["quantity"] = new JsonSchemaProperty() { Type = "number", Description = "quantity of pizza." }, + ["unit_price"] = new JsonSchemaProperty() { Type = "number", Description = "unit price" }, + + }, + Required = new List(){"pizza_type", "quantity", "unit_price" } + } + } + ] + }); + }; + + server.CallToolHandler = async (request, cancellationToken) => + { + if (request.Name == "make_payment") + { + if (request.Arguments is null || !request.Arguments.TryGetValue("order_number", out var order_number)) + { + throw new McpServerException("Missing required argument 'order_number'"); + } + if (request.Arguments is null || !request.Arguments.TryGetValue("total_amount", out var total_amount)) + { + throw new McpServerException("Missing required argument 'total_amount'"); + } + dynamic message = new ExpandoObject(); + message.Transaction = Guid.NewGuid().ToString(); + message.Status = "Success"; + + // Serialize the message to JSON + var jso = new JsonSerializerOptions() { WriteIndented = true }; + var jsonMessage = JsonSerializer.Serialize(message, jso); + + return new CallToolResponse() + { + Content = [new Content() { Text = jsonMessage , Type = "text" }] + }; + } + else if(request.Name == "get_pizza_prices") + { + if (request.Arguments is null || !request.Arguments.TryGetValue("pizza_type", out var pizza_type)) + { + throw new McpServerException("Missing required argument 'pizza_type'"); + } + if (request.Arguments is null || !request.Arguments.TryGetValue("quantity", out var quantity)) + { + throw new McpServerException("Missing required argument 'quantity'"); + } + dynamic message = new ExpandoObject(); + message.pepperoni_unit_price = 3.2; + message.cheese_unit_price = 3.5; + message.margherita_unit_price = 3.8; + // Serialize the message to JSON + var jso = new JsonSerializerOptions() { WriteIndented = true }; + var jsonMessage = JsonSerializer.Serialize(message, jso); + return new CallToolResponse() + { + Content = [new Content() { Text = jsonMessage, Type = "text" }] + }; + } + else if (request.Name == "place_an_order") + { + if (request.Arguments is null || !request.Arguments.TryGetValue("pizza_type", out var pizza_type)) + { + throw new McpServerException("Missing required argument 'pizza_type'"); + } + if (request.Arguments is null || !request.Arguments.TryGetValue("quantity", out var quantity)) + { + throw new McpServerException("Missing required argument 'quantity'"); + } + if (request.Arguments is null || !request.Arguments.TryGetValue("unit_price", out var unit_price)) + { + throw new McpServerException("Missing required argument 'unit_price'"); + } + dynamic message = new ExpandoObject(); + message.order_number = "P123-01"; + message.Content = "The order number is P123-01"; + // Serialize the message to JSON + var jso = new JsonSerializerOptions() { WriteIndented = true }; + var jsonMessage = JsonSerializer.Serialize(message, jso); + return new CallToolResponse() + { + Content = [new Content() { Text = jsonMessage, Type = "text" }] + }; + } + else + { + throw new McpServerException($"Unknown tool: {request.Name}"); + } + }; + #endregion + + Console.WriteLine("Server initialized."); + + await server.StartAsync(); + + Console.WriteLine("Server started."); + + // Run until process is stopped by the client (parent process) + while (true) + { + await Task.Delay(1000); + } + } + + private static ILoggerFactory CreateLoggerFactory() + { + // Use serilog + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() // Capture all log levels + .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"), + rollingInterval: RollingInterval.Day, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); + + var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log"); + return LoggerFactory.Create(builder => + { + builder.AddSerilog(); + }); + } + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs index 4fa39c7c..9433c6a8 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs @@ -1,21 +1,21 @@ -using BotSharp.Abstraction.Conversations.Models; -using System.Text.Json; +//using BotSharp.Abstraction.Conversations.Models; +//using System.Text.Json; -namespace BotSharp.Plugin.PizzaBot.Functions; +//namespace BotSharp.Plugin.PizzaBot.Functions; -public class GetPizzaPricesFn : IFunctionCallback -{ - public string Name => "get_pizza_price"; +//public class GetPizzaPricesFn : IFunctionCallback +//{ +// public string Name => "get_pizza_price"; - public async Task Execute(RoleDialogModel message) - { - message.Data = new - { - pepperoni_unit_price = 3.2, - cheese_unit_price = 3.5, - margherita_unit_price = 3.8, - }; - message.Content = JsonSerializer.Serialize(message.Data); - return true; - } -} +// public async Task Execute(RoleDialogModel message) +// { +// message.Data = new +// { +// pepperoni_unit_price = 3.2, +// cheese_unit_price = 3.5, +// margherita_unit_price = 3.8, +// }; +// message.Content = JsonSerializer.Serialize(message.Data); +// return true; +// } +//} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs index 9a239fd2..1ce3aac3 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs @@ -46,7 +46,7 @@ public class GetPizzaTypesFn : IFunctionCallback }).ToArray() } }; - + return true; } } diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/MakePaymentFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/MakePaymentFn.cs index 144eb05a..5a34a6ce 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/MakePaymentFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/MakePaymentFn.cs @@ -1,19 +1,19 @@ -using BotSharp.Abstraction.Conversations.Models; +//using BotSharp.Abstraction.Conversations.Models; -namespace BotSharp.Plugin.PizzaBot.Functions; +//namespace BotSharp.Plugin.PizzaBot.Functions; -public class MakePaymentFn : IFunctionCallback -{ - public string Name => "make_payment"; +//public class MakePaymentFn : IFunctionCallback +//{ +// public string Name => "make_payment"; - public async Task Execute(RoleDialogModel message) - { - message.Content = "Payment proceed successfully. Thank you for your business. Have a great day!"; - message.Data = new - { - Transaction = Guid.NewGuid().ToString(), - Status = "Success" - }; - return true; - } -} +// public async Task Execute(RoleDialogModel message) +// { +// message.Content = "Payment proceed successfully. Thank you for your business. Have a great day!"; +// message.Data = new +// { +// Transaction = Guid.NewGuid().ToString(), +// Status = "Success" +// }; +// return true; +// } +//} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs index 87466956..47d27363 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs @@ -1,24 +1,24 @@ -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Conversations.Models; +//using BotSharp.Abstraction.Conversations; +//using BotSharp.Abstraction.Conversations.Models; -namespace BotSharp.Plugin.PizzaBot.Functions; +//namespace BotSharp.Plugin.PizzaBot.Functions; -public class PlaceOrderFn : IFunctionCallback -{ - public string Name => "place_an_order"; +//public class PlaceOrderFn : IFunctionCallback +//{ +// public string Name => "place_an_order"; - private readonly IServiceProvider _service; - public PlaceOrderFn(IServiceProvider service) - { - _service = service; - } +// private readonly IServiceProvider _service; +// public PlaceOrderFn(IServiceProvider service) +// { +// _service = service; +// } - public async Task Execute(RoleDialogModel message) - { - message.Content = "The order number is P123-01"; - var state = _service.GetRequiredService(); - state.SetState("order_number", "P123-01"); +// public async Task Execute(RoleDialogModel message) +// { +// message.Content = "The order number is P123-01"; +// var state = _service.GetRequiredService(); +// state.SetState("order_number", "P123-01"); - return true; - } -} +// return true; +// } +//} diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json index 40988cb2..7ec18d46 100644 --- a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json +++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json @@ -7,5 +7,19 @@ "disabled": false, "isPublic": true, "profiles": [ "pizza" ], + "mcptools": [ + { + "serverid": "PizzaServer", + "disabled": false, + "functions": [ + { + "Name": "get_pizza_price" + }, + { + "Name": "place_an_order" + } + ] + } + ] "labels": [ "experiment" ] } \ No newline at end of file diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/get_pizza_price.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/get_pizza_price.json index e2eaac31..1ecdbb65 100644 --- a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/get_pizza_price.json +++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/get_pizza_price.json @@ -1,18 +1,18 @@ -{ - "name": "get_pizza_price", - "description": "call this function to get the pizza price", - "parameters": { - "type": "object", - "properties": { - "pizza_type": { - "type": "string", - "description": "The pizza type." - }, - "quantity": { - "type": "string", - "description": "quantity of pizza." - } - }, - "required": [ "pizza_type", "quantity" ] - } -} \ No newline at end of file +//{ +// "name": "get_pizza_price", +// "description": "call this function to get the pizza price", +// "parameters": { +// "type": "object", +// "properties": { +// "pizza_type": { +// "type": "string", +// "description": "The pizza type." +// }, +// "quantity": { +// "type": "string", +// "description": "quantity of pizza." +// } +// }, +// "required": [ "pizza_type", "quantity" ] +// } +//} \ No newline at end of file diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/place_an_order.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/place_an_order.json index d5514fba..a01f0005 100644 --- a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/place_an_order.json +++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions/place_an_order.json @@ -1,22 +1,22 @@ -{ - "name": "place_an_order", - "description": "Place an order when user has confirmed the pizza type and quantity.", - "parameters": { - "type": "object", - "properties": { - "pizza_type": { - "type": "string", - "description": "The pizza type." - }, - "quantity": { - "type": "number", - "description": "quantity of pizza." - }, - "unit_price": { - "type": "number", - "description": "unit price" - } - }, - "required": [ "pizza_type", "quantity", "unit_price" ] - } -} \ No newline at end of file +//{ +// "name": "place_an_order", +// "description": "Place an order when user has confirmed the pizza type and quantity.", +// "parameters": { +// "type": "object", +// "properties": { +// "pizza_type": { +// "type": "string", +// "description": "The pizza type." +// }, +// "quantity": { +// "type": "number", +// "description": "quantity of pizza." +// }, +// "unit_price": { +// "type": "number", +// "description": "unit price" +// } +// }, +// "required": [ "pizza_type", "quantity", "unit_price" ] +// } +//} \ No newline at end of file diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json index ef47f34d..f34ffb76 100644 --- a/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json +++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json @@ -7,6 +7,17 @@ "disabled": false, "isPublic": true, "profiles": [ "pizza" ], + "mcptools": [ + { + "serverid": "PizzaServer", + "disabled": false, + "functions": [ + { + "Name": "make_payment" + } + ] + } + ], "labels": [ "experiment" ], "routingRules": [ { diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions/make_payment.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions/make_payment.json index 82782c94..914f215e 100644 --- a/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions/make_payment.json +++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions/make_payment.json @@ -1,18 +1,18 @@ -{ - "name": "make_payment", - "description": "call this function to make payment", - "parameters": { - "type": "object", - "properties": { - "order_number": { - "type": "string", - "description": "order number." - }, - "total_amount": { - "type": "string", - "description": "total amount." - } - }, - "required": [ "order_number", "total_amount" ] - } -} \ No newline at end of file +//{ +// "name": "make_payment", +// "description": "call this function to make payment", +// "parameters": { +// "type": "object", +// "properties": { +// "order_number": { +// "type": "string", +// "description": "order number." +// }, +// "total_amount": { +// "type": "string", +// "description": "total amount." +// } +// }, +// "required": [ "order_number", "total_amount" ] +// } +//} \ No newline at end of file