diff --git a/BotSharp.sln b/BotSharp.sln index 9fb3b0e7..3b0a493c 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -131,6 +131,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrast EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{AD91B4ED-0623-4710-913E-6D7C893E76EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet", "..\mcpdotnet\src\mcpdotnet\mcpdotnet.csproj", "{DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -539,6 +541,14 @@ Global {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Release|Any CPU.Build.0 = Release|Any CPU {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Release|x64.ActiveCfg = Release|Any CPU {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Release|x64.Build.0 = Release|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Debug|x64.Build.0 = Debug|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Release|Any CPU.Build.0 = Release|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Release|x64.ActiveCfg = Release|Any CPU + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -602,6 +612,7 @@ Global {AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA} {8ED8EEF4-06DD-45F5-AA91-BD2395E901B5} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {AD91B4ED-0623-4710-913E-6D7C893E76EF} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} + {DF0DBE28-C0FE-4947-8E95-9FBC42B4D44A} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 7fd94340..c8f97206 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -72,7 +72,7 @@ public partial class AgentService hook.OnAgentUtilityLoaded(agent); } - if(agent.McpTools != null) + if(agent.McpTools != null && agent.McpTools.Count >0) { hook.OnAgentMCPToolLoaded(agent); } diff --git a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj index c171a732..dd33481c 100644 --- a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj +++ b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj @@ -8,11 +8,8 @@ $(SolutionDir)packages - - - - + diff --git a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs index f5717867..97ec724f 100644 --- a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs +++ b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs @@ -1,7 +1,11 @@ +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; @@ -13,12 +17,14 @@ namespace BotSharp.Core.Mcp.Functions; public class McpToolFunction : IFunctionCallback { private readonly Tool _tool; - private readonly IMcpClient _client; + private readonly MCPClientManager _clientManager; + private readonly IServiceProvider _serviceProvider; - public McpToolFunction(Tool tool, IMcpClient client) + public McpToolFunction(IServiceProvider provider, Tool tool, MCPClientManager client) { + _serviceProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _tool = tool ?? throw new ArgumentNullException(nameof(tool)); - _client = client ?? throw new ArgumentNullException(nameof(client)); + _clientManager = client ?? throw new ArgumentNullException(nameof(client)); } public string Name => _tool.Name; @@ -27,9 +33,14 @@ public class McpToolFunction : IFunctionCallback { // 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( + var result = await client.CallToolAsync( _tool.Name, argDict.Count == 0 ? new() : argDict ); diff --git a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs index a4ac94b7..48175c8a 100644 --- a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs +++ b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs @@ -26,14 +26,11 @@ public class MCPToolAgentHook : AgentHookBase { 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); diff --git a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs index 49cb299f..3d8ec0c8 100644 --- a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs +++ b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs @@ -1,7 +1,11 @@ +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; @@ -31,13 +35,16 @@ public class McpPlugin : IBotSharpPlugin var tools = client.ListToolsAsync().Result; foreach (var tool in tools.Tools) - { - services.AddScoped(provider => - { - var func = new McpToolFunction(tool, client); - return func; - }); + { + 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/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 376df20f..3f7428e4 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,7 +167,7 @@ "HostAgentId": "01e2fc5c-2c89-4ec7-8470-7688608b496c", "EnableTranslator": false, "LlmConfig": { - "Provider": "openai", + "Provider": "azure-openai", "Model": "gpt-4o-mini" } }, @@ -187,7 +184,8 @@ "Name": "PizzaServer", "TransportType": "stdio", "TransportOptions": { - "Command": "BotSharp.PizzaBot.MCPServer.exe" + "command": "BotSharp.PizzaBot.MCPServer.exe", + "workingDirectory": "E:\\GitHub\\BotSharp\\src\\WebStarter\\bin\\Debug\\net8.0" } } ] @@ -409,6 +407,7 @@ "BotSharp.Core.SideCar", "BotSharp.Core.Crontab", "BotSharp.Logger", + "BotSharp.MCP", "BotSharp.Plugin.MongoStorage", "BotSharp.Plugin.Dashboard", "BotSharp.Plugin.OpenAI", diff --git a/tests/BotSharp.PizzaBot.MCPServer/Program.cs b/tests/BotSharp.PizzaBot.MCPServer/Program.cs index 81cde36a..5e8633f8 100644 --- a/tests/BotSharp.PizzaBot.MCPServer/Program.cs +++ b/tests/BotSharp.PizzaBot.MCPServer/Program.cs @@ -54,6 +54,39 @@ namespace BotSharp.PizzaBot.MCPServer }, 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" } + } } ] }); @@ -72,9 +105,8 @@ namespace BotSharp.PizzaBot.MCPServer throw new McpServerException("Missing required argument 'total_amount'"); } dynamic message = new ExpandoObject(); - message.pepperoni_unit_price = 3.2; - message.cheese_unit_price = 3.5; - message.margherita_unit_price = 3.8; + message.Transaction = Guid.NewGuid().ToString(); + message.Status = "Success"; // Serialize the message to JSON var jso = new JsonSerializerOptions() { WriteIndented = true }; @@ -85,6 +117,53 @@ namespace BotSharp.PizzaBot.MCPServer 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}"); 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/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 62f069bb..021fa3c5 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 @@ -6,5 +6,19 @@ "id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd", "disabled": false, "isPublic": true, - "profiles": [ "pizza" ] + "profiles": [ "pizza" ], + "mcptools": [ + { + "serverid": "PizzaServer", + "disabled": false, + "functions": [ + { + "Name": "get_pizza_price" + }, + { + "Name": "place_an_order" + } + ] + } + ] } \ 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 63c82834..90ee8f69 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,15 +7,17 @@ "disabled": false, "isPublic": true, "profiles": [ "pizza" ], - "McpTools": { - "ServerId": "b284db86-e9c2-4c25-a59e-4649797dd130", - "Disabled": "false", - "Functions": [ - { - "Name": "make_payment" - } - ] - }, + "mcptools": [ + { + "serverid": "PizzaServer", + "disabled": false, + "functions": [ + { + "Name": "make_payment" + } + ] + } + ], "routingRules": [ { "field": "order_number",