feat:Data with Json

1、Json Data  Save to State
2、mcpdotnet with source ref
This commit is contained in:
geffzhang 2025-03-18 18:43:31 +08:00
parent bf6b30d3ce
commit 35358f20b9
12 changed files with 192 additions and 130 deletions

View file

@ -133,6 +133,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Realtime", "src\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj", "{781F1465-365C-0F22-1775-25025DAFA4C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet", "..\mcpdotnet\src\mcpdotnet\mcpdotnet.csproj", "{32D455C9-C7CF-928B-F878-8EAF0198328E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -549,6 +551,14 @@ Global
{781F1465-365C-0F22-1775-25025DAFA4C7}.Release|Any CPU.Build.0 = Release|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Release|x64.ActiveCfg = Release|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Release|x64.Build.0 = Release|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Debug|x64.ActiveCfg = Debug|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Debug|x64.Build.0 = Debug|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Release|Any CPU.Build.0 = Release|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Release|x64.ActiveCfg = Release|Any CPU
{32D455C9-C7CF-928B-F878-8EAF0198328E}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -613,6 +623,7 @@ Global
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{8D2AD45F-836A-516F-DE6A-71443CEBB18A} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
{781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{32D455C9-C7CF-928B-F878-8EAF0198328E} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -19,6 +19,7 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.0-preview.1.25080.5" />
<PackageVersion Include="System.Memory.Data" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
@ -131,7 +132,6 @@
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.5" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="6.0.27" />
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="6.0.15" />

View file

@ -9,10 +9,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="mcpdotnet" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\mcpdotnet\src\mcpdotnet\mcpdotnet.csproj" />
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project>

View file

@ -39,7 +39,7 @@ public class McpToolFunction : IFunctionCallback
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);
var client = await _clientManager.GetMcpClientAsync(serverId);
// Call the tool through mcpdotnet
var result = await client.CallToolAsync(
_tool.Name,
@ -50,8 +50,8 @@ public class McpToolFunction : IFunctionCallback
var json = string.Join("\n", result.Content
.Where(c => c.Type == "text")
.Select(c => c.Text));
message.Content = json.JsonContent();
message.Content = json;
message.Data = json.JsonContent();
return true;
}

View file

@ -30,10 +30,12 @@ public class MCPResponseHook : ConversationHookBase
public override async Task OnResponseGenerated(RoleDialogModel message)
{
var agentService = _services.GetRequiredService<IAgentService>();
var state = _services.GetRequiredService<IConversationStateService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
if(agent.McpTools.Any(item => item.Functions.Any(x=> x.Name == message.FunctionName)))
{
message.Data = JsonSerializer.Deserialize(message.Content, typeof(object));
var data = JsonDocument.Parse(JsonSerializer.Serialize(message.Data));
state.SaveStateByArgs(data);
}
await base.OnResponseGenerated(message);
}

View file

@ -6,6 +6,7 @@ using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Core.Mcp;
using BotSharp.Core.MCP;
using McpDotNet.Client;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
@ -51,12 +52,12 @@ public class MCPToolAgentHook : AgentHookBase
var mcps = agent.McpTools;
foreach (var item in mcps)
{
var mcpClient = await mcpClientManager.Factory.GetClientAsync(item.ServerId);
var mcpClient = await mcpClientManager.GetMcpClientAsync(item.ServerId);
if (mcpClient != null)
{
var tools = await mcpClient.ListToolsAsync();
var tools = await mcpClient.ListToolsAsync().ToListAsync();
var funcnames = item.Functions.Select(x => x.Name).ToList();
foreach (var tool in tools.Tools.Where(x => funcnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
foreach (var tool in tools.Where(x => funcnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
{
var funDef = AIFunctionUtilities.MapToFunctionDef(tool);
functionDefs.Add(funDef);

View file

@ -2,14 +2,14 @@ using BotSharp.Core.Mcp.Settings;
using McpDotNet.Client;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace BotSharp.Core.Mcp;
public class MCPClientManager : IDisposable
{
public ILoggerFactory LoggerFactory { get; }
public McpClientFactory Factory { get; }
private readonly MCPSettings mcpSettings;
@ -17,13 +17,12 @@ public class MCPClientManager : IDisposable
{
mcpSettings = settings;
LoggerFactory = loggerFactory;
}
// Inject the mock transport into the factory
Factory = new McpClientFactory(
settings.McpServerConfigs,
settings.McpClientOptions,
LoggerFactory
);
public async Task<IMcpClient> GetMcpClientAsync(string serverId)
{
return await McpClientFactory.CreateAsync(mcpSettings.McpServerConfigs
.Where(x=> x.Name == serverId).First(), mcpSettings.McpClientOptions);
}
public void Dispose()

View file

@ -5,11 +5,14 @@ using BotSharp.Abstraction.Plugins;
using BotSharp.Core.Mcp.Functions;
using BotSharp.Core.Mcp.Settings;
using BotSharp.MCP.Hooks;
using McpDotNet.Client;
using McpDotNet.Protocol.Types;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Linq;
using System.Threading.Tasks;
namespace BotSharp.Core.Mcp;
@ -31,20 +34,30 @@ public class McpPlugin : IBotSharpPlugin
foreach (var server in settings.McpServerConfigs)
{
var client = clientManager.Factory.GetClientAsync(server.Id).Result;
var tools = client.ListToolsAsync().Result;
RegisterFunctionCall(services, server)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
// Register hooks
services.AddScoped<IAgentHook, MCPToolAgentHook>();
services.AddScoped<IConversationHook, MCPResponseHook>();
}
foreach (var tool in tools.Tools)
private async Task RegisterFunctionCall(IServiceCollection services, McpDotNet.Configuration.McpServerConfig server)
{
services.AddScoped( provider => { return tool; });
var client = await clientManager.GetMcpClientAsync(server.Id);
var tools = await client.ListToolsAsync().ToListAsync();
services.AddScoped<IFunctionCallback>( provider => {
var funcTool = new McpToolFunction( provider, tool, clientManager);
foreach (var tool in tools)
{
services.AddScoped(provider => { return tool; });
services.AddScoped<IFunctionCallback>(provider =>
{
var funcTool = new McpToolFunction(provider, tool, clientManager);
return funcTool;
});
}
}
// Register hooks
services.AddScoped<IAgentHook, MCPToolAgentHook>();
}
}
}

View file

@ -46,11 +46,15 @@
"Provider": "azure-openai",
"Models": [
{
"Id": "gpt-3.5-turbo",
"Name": "gpt-35-turbo",
"Version": "1106",
"ApiKey": "",
"Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/"
"Id": "gpt-4o-mini",
"Name": "gpt-4o-mini",
"ApiKey": "c1cee6ddfb3d4f469587789efe7a2ba9",
"Endpoint": "https://ai4c-demo.openai.azure.com/",
"Type": "chat",
"PromptCost": 0.0015,
"CompletionCost": 0.002,
"MaxTokens": null,
"Temperature": 1.0
},
{
"Name": "gpt-35-turbo-instruct",

View file

@ -8,10 +8,12 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="mcpdotnet" />
<PackageReference Include="Microsoft.Extensions.Hosting"/>
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Sinks.File" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\mcpdotnet\src\mcpdotnet\mcpdotnet.csproj" />
</ItemGroup>
</Project>

View file

@ -12,6 +12,9 @@ namespace BotSharp.PizzaBot.MCPServer
{
internal class Program
{
private static HashSet<string> _subscribedResources = new();
private static readonly object _subscribedResourcesLock = new();
private static async Task Main(string[] args)
{
Console.WriteLine("Starting server...");
@ -21,34 +24,48 @@ namespace BotSharp.PizzaBot.MCPServer
ServerInfo = new Implementation() { Name = "PizzaServer", Version = "1.0.0" },
Capabilities = new ServerCapabilities()
{
//Tools = ConfigureTools(),
Tools = ConfigureTools(),
},
ProtocolVersion = "2024-11-05",
ServerInstructions = "This is a test server with only stub functionality"
};
var loggerFactory = CreateLoggerFactory();
McpServerFactory factory = new McpServerFactory(new StdioServerTransport("PizzaServer", loggerFactory), options, loggerFactory);
IMcpServer server = factory.CreateServer();
ConfigureTools(server);
Console.WriteLine("Server object created, registering handlers.");
Console.WriteLine("Server initialized.");
await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("TestServer", loggerFactory), options, loggerFactory);
Log.Logger.Information("Server initialized.");
await server.StartAsync();
Console.WriteLine("Server started.");
Log.Logger.Information("Server started.");
// Run until process is stopped by the client (parent process)
while (true)
{
await Task.Delay(1000);
await Task.Delay(5000);
// Snapshot the subscribed resources, rather than locking while sending notifications
List<string> resources;
lock (_subscribedResourcesLock)
{
resources = _subscribedResources.ToList();
}
foreach (var resource in resources)
{
ResourceUpdatedNotificationParams notificationParams = new() { Uri = resource };
await server.SendMessageAsync(new JsonRpcNotification()
{
Method = NotificationMethods.ResourceUpdatedNotification,
Params = notificationParams
});
}
}
}
private static void ConfigureTools(IMcpServer server)
private static ToolsCapability ConfigureTools()
{
server.ListToolsHandler = (request, cancellationToken) =>
return new()
{
ListToolsHandler = (request, cancellationToken) =>
{
return Task.FromResult(new ListToolsResult()
{
@ -71,7 +88,7 @@ namespace BotSharp.PizzaBot.MCPServer
new Tool()
{
Name = "get_pizza_prices",
Description = "call this function to get pizza prices",
Description = "call this function to get pizza unit price",
InputSchema = new JsonSchema()
{
Type = "object",
@ -95,7 +112,7 @@ namespace BotSharp.PizzaBot.MCPServer
{
["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" },
["unit_price"] = new JsonSchemaProperty() { Type = "number", Description = "pizza unit price" },
},
Required = new List<string>(){"pizza_type", "quantity", "unit_price" }
@ -103,9 +120,9 @@ namespace BotSharp.PizzaBot.MCPServer
}
]
});
};
},
server.CallToolHandler = async (request, cancellationToken) =>
CallToolHandler = async (request, cancellationToken) =>
{
if (request.Params.Name == "make_payment")
{
@ -117,17 +134,17 @@ namespace BotSharp.PizzaBot.MCPServer
{
throw new McpServerException("Missing required argument 'total_amount'");
}
dynamic message = new ExpandoObject();
message.Transaction = Guid.NewGuid().ToString();
message.Status = "Success";
//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);
//// 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" }]
Content = [new Content() { Text = "Payment proceed successfully. Thank you for your business. Have a great day!", Type = "text" }]
};
}
else if (request.Params.Name == "get_pizza_prices")
@ -140,11 +157,21 @@ namespace BotSharp.PizzaBot.MCPServer
{
throw new McpServerException("Missing required argument 'quantity'");
}
double unit_price = 0;
if(pizza_type.ToString() == "Pepperoni Pizza")
{
unit_price = 3.2 * (int)quantity;
}
else if(pizza_type.ToString() == "Cheese Pizza")
{
unit_price = 3.5 * (int)quantity; ;
}
else if(pizza_type.ToString() == "Margherita Pizza")
{
unit_price = 3.8 * (int)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
message.unit_price = unit_price;
var jso = new JsonSerializerOptions() { WriteIndented = true };
var jsonMessage = JsonSerializer.Serialize(message, jso);
return new CallToolResponse()
@ -166,24 +193,26 @@ namespace BotSharp.PizzaBot.MCPServer
{
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);
//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" }]
Content = [new Content() { Text = "The order number is P123-01: {order_number = \"P123-01\" }", Type = "text" }]
};
}
else
{
throw new McpServerException($"Unknown tool: {request.Params.Name}");
}
}
};
}
private static ILoggerFactory CreateLoggerFactory()
{
// Use serilog

View file

@ -1,6 +1,6 @@
{
"name": "Ordering",
"description": "Provide types of pizza available, unit price, total cost and place the order.",
"description": "Provide types of pizza available, pizza unit price, total cost and place the order.",
"createdDateTime": "2023-07-26T02:29:25.123224Z",
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",