sync and refine
This commit is contained in:
parent
37bef7b8c6
commit
350c479486
|
|
@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.MCP.Services;
|
|||
|
||||
public interface IMcpService
|
||||
{
|
||||
IEnumerable<McpServerOptionModel> GetServerConfigs() => [];
|
||||
Task<IEnumerable<McpServerOptionModel>> GetServerConfigsAsync() => Task.FromResult<IEnumerable<McpServerOptionModel>>([]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
namespace BotSharp.Core.Routing.Executor;
|
||||
namespace BotSharp.Abstraction.Routing.Executor;
|
||||
|
||||
public interface IFunctionExecutor
|
||||
{
|
||||
public Task<bool> ExecuteAsync(RoleDialogModel message);
|
||||
|
||||
public Task<string> GetIndicatorAsync(RoleDialogModel message);
|
||||
}
|
||||
|
|
@ -18,15 +18,11 @@ public static class BotSharpMcpExtensions
|
|||
{
|
||||
var settings = config.GetSection("MCP").Get<McpSettings>();
|
||||
services.AddScoped(provider => settings);
|
||||
services.AddScoped<IMcpService, McpService>();
|
||||
|
||||
if (settings != null && settings.Enabled && !settings.McpServerConfigs.IsNullOrEmpty())
|
||||
{
|
||||
services.AddScoped<IMcpService, McpService>();
|
||||
|
||||
var clientManager = new McpClientManager(settings);
|
||||
services.AddScoped(provider => clientManager);
|
||||
|
||||
// Register hooks
|
||||
services.AddScoped<McpClientManager>();
|
||||
services.AddScoped<IAgentHook, McpToolAgentHook>();
|
||||
}
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,25 @@
|
|||
using System.Text.Json;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
namespace BotSharp.Core.MCP.Helpers;
|
||||
|
||||
internal static class AiFunctionHelper
|
||||
{
|
||||
public static FunctionDef MapToFunctionDef(McpClientTool tool)
|
||||
public static FunctionDef? MapToFunctionDef(McpClientTool tool)
|
||||
{
|
||||
if (tool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tool));
|
||||
return null;
|
||||
}
|
||||
|
||||
var properties = tool.JsonSchema.GetProperty("properties");
|
||||
var required = tool.JsonSchema.GetProperty("required");
|
||||
if (!tool.JsonSchema.TryGetProperty("properties", out var properties))
|
||||
{
|
||||
properties = JsonDocument.Parse("{}").RootElement;
|
||||
}
|
||||
|
||||
if (!tool.JsonSchema.TryGetProperty("required", out var required))
|
||||
{
|
||||
required = JsonDocument.Parse("[]").RootElement;
|
||||
}
|
||||
|
||||
var funDef = new FunctionDef
|
||||
{
|
||||
|
|
@ -23,8 +29,8 @@ internal static class AiFunctionHelper
|
|||
Parameters = new FunctionParametersDef
|
||||
{
|
||||
Type = "object",
|
||||
Properties = JsonDocument.Parse(properties.GetRawText()),
|
||||
Required = JsonSerializer.Deserialize<List<string>>(required.GetRawText())
|
||||
Properties = JsonDocument.Parse(properties.GetRawText() ?? "{}"),
|
||||
Required = JsonSerializer.Deserialize<List<string>>(required.GetRawText() ?? "[]") ?? []
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,18 +41,26 @@ public class McpToolAgentHook : AgentHookBase
|
|||
return functionDefs;
|
||||
}
|
||||
|
||||
var mcpClientManager = _services.GetRequiredService<McpClientManager>();
|
||||
var mcps = agent.McpTools.Where(x => !x.Disabled);
|
||||
var mcpClientManager = _services.GetService<McpClientManager>();
|
||||
if (mcpClientManager == null)
|
||||
{
|
||||
return functionDefs;
|
||||
}
|
||||
|
||||
var mcps = agent.McpTools?.Where(x => !x.Disabled) ?? [];
|
||||
foreach (var item in mcps)
|
||||
{
|
||||
var mcpClient = await mcpClientManager.GetMcpClientAsync(item.ServerId);
|
||||
if (mcpClient != null)
|
||||
if (mcpClient == null) continue;
|
||||
|
||||
var tools = await mcpClient.ListToolsAsync();
|
||||
var toolNames = item.Functions.Select(x => x.Name).ToList();
|
||||
var targetTools = tools.Where(x => toolNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase));
|
||||
foreach (var tool in targetTools)
|
||||
{
|
||||
var tools = await mcpClient.ListToolsAsync();
|
||||
var toolnames = item.Functions.Select(x => x.Name).ToList();
|
||||
foreach (var tool in tools.Where(x => toolnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
|
||||
var funDef = AiFunctionHelper.MapToFunctionDef(tool);
|
||||
if (funDef != null)
|
||||
{
|
||||
var funDef = AiFunctionHelper.MapToFunctionDef(tool);
|
||||
functionDefs.Add(funDef);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,45 +6,63 @@ namespace BotSharp.Core.MCP.Managers;
|
|||
|
||||
public class McpClientManager : IDisposable
|
||||
{
|
||||
private readonly McpSettings _mcpSettings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<McpClientManager> _logger;
|
||||
|
||||
public McpClientManager(McpSettings mcpSettings)
|
||||
public McpClientManager(
|
||||
IServiceProvider services,
|
||||
ILogger<McpClientManager> logger)
|
||||
{
|
||||
_mcpSettings = mcpSettings;
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IMcpClient> GetMcpClientAsync(string serverId)
|
||||
public async Task<IMcpClient?> GetMcpClientAsync(string serverId)
|
||||
{
|
||||
var config = _mcpSettings.McpServerConfigs.Where(x => x.Id == serverId).FirstOrDefault();
|
||||
|
||||
IClientTransport transport;
|
||||
if (config.SseConfig != null)
|
||||
try
|
||||
{
|
||||
transport = new SseClientTransport(new SseClientTransportOptions
|
||||
var settings = _services.GetRequiredService<McpSettings>();
|
||||
var config = settings.McpServerConfigs.Where(x => x.Id == serverId).FirstOrDefault();
|
||||
if (config == null)
|
||||
{
|
||||
Name = config.Name,
|
||||
Endpoint = new Uri(config.SseConfig.EndPoint),
|
||||
AdditionalHeaders = config.SseConfig.AdditionalHeaders,
|
||||
ConnectionTimeout = config.SseConfig.ConnectionTimeout
|
||||
});
|
||||
}
|
||||
else if (config.StdioConfig != null)
|
||||
{
|
||||
transport = new StdioClientTransport(new StdioClientTransportOptions
|
||||
{
|
||||
Name = config.Name,
|
||||
Command = config.StdioConfig.Command,
|
||||
Arguments = config.StdioConfig.Arguments,
|
||||
EnvironmentVariables = config.StdioConfig.EnvironmentVariables,
|
||||
ShutdownTimeout = config.StdioConfig.ShutdownTimeout
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException("Invalid MCP server configuration!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return await McpClientFactory.CreateAsync(transport, _mcpSettings.McpClientOptions);
|
||||
IClientTransport? transport = null;
|
||||
if (config.SseConfig != null)
|
||||
{
|
||||
transport = new SseClientTransport(new SseClientTransportOptions
|
||||
{
|
||||
Name = config.Name,
|
||||
Endpoint = new Uri(config.SseConfig.EndPoint),
|
||||
AdditionalHeaders = config.SseConfig.AdditionalHeaders,
|
||||
ConnectionTimeout = config.SseConfig.ConnectionTimeout
|
||||
});
|
||||
}
|
||||
else if (config.StdioConfig != null)
|
||||
{
|
||||
transport = new StdioClientTransport(new StdioClientTransportOptions
|
||||
{
|
||||
Name = config.Name,
|
||||
Command = config.StdioConfig.Command,
|
||||
Arguments = config.StdioConfig.Arguments,
|
||||
EnvironmentVariables = config.StdioConfig.EnvironmentVariables,
|
||||
ShutdownTimeout = config.StdioConfig.ShutdownTimeout
|
||||
});
|
||||
}
|
||||
|
||||
if (transport == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await McpClientFactory.CreateAsync(transport, settings.McpClientOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Error when loading mcp client {serverId}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using BotSharp.Core.MCP.Managers;
|
||||
using BotSharp.Core.MCP.Settings;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
namespace BotSharp.Core.MCP.Services;
|
||||
|
|
@ -9,35 +8,35 @@ public class McpService : IMcpService
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<McpService> _logger;
|
||||
private readonly McpClientManager _mcpClientManager;
|
||||
|
||||
public McpService(
|
||||
IServiceProvider services,
|
||||
ILogger<McpService> logger,
|
||||
McpClientManager mcpClient)
|
||||
ILogger<McpService> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_mcpClientManager = mcpClient;
|
||||
}
|
||||
|
||||
public IEnumerable<McpServerOptionModel> GetServerConfigs()
|
||||
public async Task<IEnumerable<McpServerOptionModel>> GetServerConfigsAsync()
|
||||
{
|
||||
var clientManager = _services.GetService<McpClientManager>();
|
||||
if (clientManager == null) return [];
|
||||
|
||||
var options = new List<McpServerOptionModel>();
|
||||
var settings = _services.GetRequiredService<McpSettings>();
|
||||
var configs = settings?.McpServerConfigs ?? [];
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
var tools = _mcpClientManager.GetMcpClientAsync(config.Id)
|
||||
.Result.ListToolsAsync()
|
||||
.Result.Select(x=> x.Name);
|
||||
var client = await clientManager.GetMcpClientAsync(config.Id);
|
||||
if (client == null) continue;
|
||||
|
||||
var tools = await client.ListToolsAsync();
|
||||
options.Add(new McpServerOptionModel
|
||||
{
|
||||
Id = config.Id,
|
||||
Name = config.Name,
|
||||
Tools = tools
|
||||
Tools = tools.Select(x => x.Name)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,5 +7,4 @@ public class McpSettings
|
|||
public bool Enabled { get; set; } = true;
|
||||
public McpClientOptions McpClientOptions { get; set; }
|
||||
public List<McpServerConfigModel> McpServerConfigs { get; set; } = [];
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
using BotSharp.Abstraction.Routing.Executor;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Routing.Executor;
|
||||
|
||||
public class DummyFunctionExecutor: IFunctionExecutor
|
||||
{
|
||||
private FunctionDef functionDef;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly FunctionDef _functionDef;
|
||||
|
||||
public DummyFunctionExecutor(FunctionDef function, IServiceProvider services)
|
||||
public DummyFunctionExecutor(IServiceProvider services, FunctionDef functionDef)
|
||||
{
|
||||
functionDef = function;
|
||||
_services = services;
|
||||
_functionDef = functionDef;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> ExecuteAsync(RoleDialogModel message)
|
||||
{
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
|
|
@ -25,7 +25,7 @@ public class DummyFunctionExecutor: IFunctionExecutor
|
|||
dict[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
var text = render.Render(functionDef.Output, dict);
|
||||
var text = render.Render(_functionDef.Output!, dict);
|
||||
message.Content = text;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
using BotSharp.Abstraction.Routing.Executor;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
|
||||
namespace BotSharp.Core.Routing.Executor;
|
||||
|
||||
public class FunctionCallbackExecutor : IFunctionExecutor
|
||||
{
|
||||
IFunctionCallback functionCallback;
|
||||
private readonly IFunctionCallback _functionCallback;
|
||||
|
||||
public FunctionCallbackExecutor(IFunctionCallback functionCallback)
|
||||
{
|
||||
this.functionCallback = functionCallback;
|
||||
_functionCallback = functionCallback;
|
||||
}
|
||||
|
||||
public async Task<bool> ExecuteAsync(RoleDialogModel message)
|
||||
{
|
||||
return await functionCallback.Execute(message);
|
||||
return await _functionCallback.Execute(message);
|
||||
}
|
||||
|
||||
public async Task<string> GetIndicatorAsync(RoleDialogModel message)
|
||||
{
|
||||
return await functionCallback.GetIndication(message);
|
||||
return await _functionCallback.GetIndication(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,31 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Routing.Executor;
|
||||
|
||||
namespace BotSharp.Core.Routing.Executor;
|
||||
|
||||
internal class FunctionExecutorFactory
|
||||
{
|
||||
public static IFunctionExecutor Create(string functionName, Agent agent, IFunctionCallback functioncall, IServiceProvider serviceProvider)
|
||||
public static IFunctionExecutor? Create(IServiceProvider services, string functionName, Agent agent)
|
||||
{
|
||||
if(functioncall != null)
|
||||
var functionCall = services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == functionName);
|
||||
if (functionCall != null)
|
||||
{
|
||||
return new FunctionCallbackExecutor(functioncall);
|
||||
return new FunctionCallbackExecutor(functionCall);
|
||||
}
|
||||
|
||||
var funDef = agent?.Functions?.FirstOrDefault(x => x.Name == functionName);
|
||||
if (funDef != null)
|
||||
var functions = (agent?.Functions ?? []).Concat(agent?.SecondaryFunctions ?? []);
|
||||
var funcDef = functions.FirstOrDefault(x => x.Name == functionName);
|
||||
if (!string.IsNullOrWhiteSpace(funcDef?.Output))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(funDef?.Output))
|
||||
{
|
||||
return new DummyFunctionExecutor(funDef,serviceProvider);
|
||||
}
|
||||
return new DummyFunctionExecutor(services, funcDef);
|
||||
}
|
||||
else
|
||||
|
||||
var mcpServerId = agent?.McpTools?.Where(x => x.Functions.Any(y => y.Name == funcDef?.Name))?.FirstOrDefault()?.ServerId;
|
||||
if (!string.IsNullOrWhiteSpace(mcpServerId))
|
||||
{
|
||||
funDef = agent?.SecondaryFunctions?.FirstOrDefault(x => x.Name == functionName);
|
||||
if (funDef != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(funDef?.Output))
|
||||
{
|
||||
return new DummyFunctionExecutor(funDef, serviceProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mcpServerId = agent?.McpTools?.Where(x => x.Functions.Any(y => y.Name == funDef.Name))
|
||||
.FirstOrDefault().ServerId;
|
||||
return new MCPToolExecutor(mcpServerId, functionName, serviceProvider);
|
||||
}
|
||||
}
|
||||
return new McpToolExecutor(services, mcpServerId, functionName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
using BotSharp.Abstraction.Routing.Executor;
|
||||
using BotSharp.Core.MCP.Managers;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
namespace BotSharp.Core.Routing.Executor;
|
||||
|
||||
public class MCPToolExecutor: IFunctionExecutor
|
||||
public class McpToolExecutor: IFunctionExecutor
|
||||
{
|
||||
private readonly McpClientManager _clientManager;
|
||||
private string mcpServer;
|
||||
private string funcName;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly string _mcpServerId;
|
||||
private readonly string _functionName;
|
||||
|
||||
public MCPToolExecutor(string mcpserver, string functionName, IServiceProvider services)
|
||||
public McpToolExecutor(IServiceProvider services, string mcpServerId, string functionName)
|
||||
{
|
||||
_services = services;
|
||||
this.mcpServer = mcpserver;
|
||||
this.funcName = functionName;
|
||||
_clientManager = services.GetRequiredService<McpClientManager>();
|
||||
_mcpServerId = mcpServerId;
|
||||
_functionName = functionName;
|
||||
}
|
||||
|
||||
public async Task<bool> ExecuteAsync(RoleDialogModel message)
|
||||
|
|
@ -23,12 +22,13 @@ public class MCPToolExecutor: IFunctionExecutor
|
|||
try
|
||||
{
|
||||
// Convert arguments to dictionary format expected by mcpdotnet
|
||||
Dictionary<string, object> argDict = JsonToDictionary(message.FunctionArgs);
|
||||
Dictionary<string, object> argDict = JsonToDictionary(message.FunctionArgs);
|
||||
|
||||
var client = await _clientManager.GetMcpClientAsync(mcpServer);
|
||||
var clientManager = _services.GetRequiredService<McpClientManager>();
|
||||
var client = await clientManager.GetMcpClientAsync(_mcpServerId);
|
||||
|
||||
// Call the tool through mcpdotnet
|
||||
var result = await client.CallToolAsync(funcName, !argDict.IsNullOrEmpty() ? argDict : []);
|
||||
var result = await client.CallToolAsync(_functionName, !argDict.IsNullOrEmpty() ? argDict : []);
|
||||
|
||||
// Extract the text content from the result
|
||||
var json = string.Join("\n", result.Content.Where(c => c.Type == "text").Select(c => c.Text));
|
||||
|
|
@ -39,7 +39,7 @@ public class MCPToolExecutor: IFunctionExecutor
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message.Content = $"Error when calling tool {funcName} of MCP server {mcpServer}. {ex.Message}";
|
||||
message.Content = $"Error when calling tool {_functionName} of MCP server {_mcpServerId}. {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using BotSharp.Core.Routing.Executor;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
|
@ -8,14 +6,11 @@ public partial class RoutingService
|
|||
{
|
||||
public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
|
||||
{
|
||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == name);
|
||||
|
||||
var currentAgentId = message.CurrentAgentId;
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(currentAgentId);
|
||||
|
||||
IFunctionExecutor funcExecutor = FunctionExecutorFactory.Create(name, agent, function, _services);
|
||||
|
||||
var funcExecutor = FunctionExecutorFactory.Create(_services, name, agent);
|
||||
if (funcExecutor == null)
|
||||
{
|
||||
message.StopCompletion = true;
|
||||
|
|
@ -24,17 +19,14 @@ public partial class RoutingService
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Clone message
|
||||
var clonedMessage = RoleDialogModel.From(message);
|
||||
clonedMessage.FunctionName = name;
|
||||
|
||||
var hooks = _services
|
||||
.GetRequiredService<ConversationHookProvider>()
|
||||
.HooksOrderByPriority;
|
||||
var hooks = _services.GetRequiredService<ConversationHookProvider>()
|
||||
.HooksOrderByPriority;
|
||||
|
||||
var progressService = _services.GetService<IConversationProgressService>();
|
||||
|
||||
clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message);
|
||||
|
||||
if (progressService?.OnFunctionExecuting != null)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ public class McpController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("/mcp/server-configs")]
|
||||
public IEnumerable<McpServerOptionModel> GetMcpServerConfigs()
|
||||
public async Task<IEnumerable<McpServerOptionModel>> GetMcpServerConfigs()
|
||||
{
|
||||
var mcp = _services.GetRequiredService<IMcpService>();
|
||||
return mcp.GetServerConfigs();
|
||||
return await mcp.GetServerConfigsAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,13 +268,13 @@
|
|||
}
|
||||
},
|
||||
"McpServerConfigs": [
|
||||
{
|
||||
"Id": "PizzaServer",
|
||||
"Name": "PizzaServer",
|
||||
"SseConfig": {
|
||||
"Endpoint": "http://localhost:58905/sse"
|
||||
}
|
||||
}
|
||||
//{
|
||||
// "Id": "PizzaServer",
|
||||
// "Name": "PizzaServer",
|
||||
// "SseConfig": {
|
||||
// "Endpoint": "http://localhost:58905/sse"
|
||||
// }
|
||||
//}
|
||||
]
|
||||
},
|
||||
|
||||
|
|
@ -502,7 +502,6 @@
|
|||
"BotSharp.Core.SideCar",
|
||||
"BotSharp.Core.Crontab",
|
||||
"BotSharp.Core.Realtime",
|
||||
"BotSharp.Core.MCP",
|
||||
"BotSharp.Logger",
|
||||
"BotSharp.Plugin.MongoStorage",
|
||||
"BotSharp.Plugin.Dashboard",
|
||||
|
|
|
|||
Loading…
Reference in a new issue