From b8926a2c18cbfba86691d01f5228eb02eba65f21 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 26 Feb 2025 12:14:37 +0800 Subject: [PATCH] feat: add pizza mcp test server --- BotSharp.sln | 11 ++ .../BotSharp.MCP/Functions/McpToolFunction.cs | 5 +- src/Infrastructure/BotSharp.MCP/McpPlugin.cs | 10 +- src/WebStarter/WebStarter.csproj | 2 + .../BotSharp.PizzaBot.MCPServer.csproj | 16 +++ tests/BotSharp.PizzaBot.MCPServer/Program.cs | 125 ++++++++++++++++++ .../Functions/MakePaymentFn.cs | 32 ++--- .../agent.json | 9 ++ .../functions/make_payment.json | 36 ++--- 9 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj create mode 100644 tests/BotSharp.PizzaBot.MCPServer/Program.cs diff --git a/BotSharp.sln b/BotSharp.sln index d82f930c..9fb3b0e7 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -129,6 +129,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI" EndProject 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 Debug|Any CPU = Debug|Any CPU @@ -529,6 +531,14 @@ Global {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 + {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Debug|x64.Build.0 = Debug|Any CPU + {AD91B4ED-0623-4710-913E-6D7C893E76EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -591,6 +601,7 @@ Global {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} + {AD91B4ED-0623-4710-913E-6D7C893E76EF} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs index a89cc10c..f5717867 100644 --- a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs +++ b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs @@ -35,10 +35,11 @@ public class McpToolFunction : IFunctionCallback ); // 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 + 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; } diff --git a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs index c54a7050..49cb299f 100644 --- a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs +++ b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs @@ -20,12 +20,10 @@ public class McpPlugin : IBotSharpPlugin 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); - }); + services.AddScoped(provider => { return settings; }); + + clientManager = new MCPClientManager(settings, NullLoggerFactory.Instance); + services.AddSingleton(clientManager); foreach (var server in settings.McpServerConfigs) { diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index b2fcd816..dd846193 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -27,8 +27,10 @@ + + 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..745ecf38 --- /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..81cde36a --- /dev/null +++ b/tests/BotSharp.PizzaBot.MCPServer/Program.cs @@ -0,0 +1,125 @@ +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" } + }, + } + ] + }); + }; + + 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.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 + { + 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/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/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json index 1dab7257..63c82834 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,15 @@ "disabled": false, "isPublic": true, "profiles": [ "pizza" ], + "McpTools": { + "ServerId": "b284db86-e9c2-4c25-a59e-4649797dd130", + "Disabled": "false", + "Functions": [ + { + "Name": "make_payment" + } + ] + }, "routingRules": [ { "field": "order_number", 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