Skip to content

Commit 350c479

Browse files
author
Jicheng Lu
committed
sync and refine
1 parent 37bef7b commit 350c479

File tree

15 files changed

+135
-128
lines changed

15 files changed

+135
-128
lines changed

src/Infrastructure/BotSharp.Abstraction/MCP/Services/IMcpService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.MCP.Services;
22

33
public interface IMcpService
44
{
5-
IEnumerable<McpServerOptionModel> GetServerConfigs() => [];
5+
Task<IEnumerable<McpServerOptionModel>> GetServerConfigsAsync() => Task.FromResult<IEnumerable<McpServerOptionModel>>([]);
66
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
namespace BotSharp.Core.Routing.Executor;
1+
namespace BotSharp.Abstraction.Routing.Executor;
22

33
public interface IFunctionExecutor
44
{
55
public Task<bool> ExecuteAsync(RoleDialogModel message);
6-
76
public Task<string> GetIndicatorAsync(RoleDialogModel message);
87
}

src/Infrastructure/BotSharp.Core/MCP/BotSharpMCPExtensions.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,11 @@ public static IServiceCollection AddBotSharpMCP(this IServiceCollection services
1818
{
1919
var settings = config.GetSection("MCP").Get<McpSettings>();
2020
services.AddScoped(provider => settings);
21+
services.AddScoped<IMcpService, McpService>();
2122

2223
if (settings != null && settings.Enabled && !settings.McpServerConfigs.IsNullOrEmpty())
2324
{
24-
services.AddScoped<IMcpService, McpService>();
25-
26-
var clientManager = new McpClientManager(settings);
27-
services.AddScoped(provider => clientManager);
28-
29-
// Register hooks
25+
services.AddScoped<McpClientManager>();
3026
services.AddScoped<IAgentHook, McpToolAgentHook>();
3127
}
3228
return services;

src/Infrastructure/BotSharp.Core/MCP/Helpers/AiFunctionHelper.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
using System.Text.Json;
21
using ModelContextProtocol.Client;
32

43
namespace BotSharp.Core.MCP.Helpers;
54

65
internal static class AiFunctionHelper
76
{
8-
public static FunctionDef MapToFunctionDef(McpClientTool tool)
7+
public static FunctionDef? MapToFunctionDef(McpClientTool tool)
98
{
109
if (tool == null)
1110
{
12-
throw new ArgumentNullException(nameof(tool));
11+
return null;
1312
}
1413

15-
var properties = tool.JsonSchema.GetProperty("properties");
16-
var required = tool.JsonSchema.GetProperty("required");
14+
if (!tool.JsonSchema.TryGetProperty("properties", out var properties))
15+
{
16+
properties = JsonDocument.Parse("{}").RootElement;
17+
}
18+
19+
if (!tool.JsonSchema.TryGetProperty("required", out var required))
20+
{
21+
required = JsonDocument.Parse("[]").RootElement;
22+
}
1723

1824
var funDef = new FunctionDef
1925
{
@@ -23,8 +29,8 @@ public static FunctionDef MapToFunctionDef(McpClientTool tool)
2329
Parameters = new FunctionParametersDef
2430
{
2531
Type = "object",
26-
Properties = JsonDocument.Parse(properties.GetRawText()),
27-
Required = JsonSerializer.Deserialize<List<string>>(required.GetRawText())
32+
Properties = JsonDocument.Parse(properties.GetRawText() ?? "{}"),
33+
Required = JsonSerializer.Deserialize<List<string>>(required.GetRawText() ?? "[]") ?? []
2834
}
2935
};
3036

src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,26 @@ private async Task<IEnumerable<FunctionDef>> GetMcpContent(Agent agent)
4141
return functionDefs;
4242
}
4343

44-
var mcpClientManager = _services.GetRequiredService<McpClientManager>();
45-
var mcps = agent.McpTools.Where(x => !x.Disabled);
44+
var mcpClientManager = _services.GetService<McpClientManager>();
45+
if (mcpClientManager == null)
46+
{
47+
return functionDefs;
48+
}
49+
50+
var mcps = agent.McpTools?.Where(x => !x.Disabled) ?? [];
4651
foreach (var item in mcps)
4752
{
4853
var mcpClient = await mcpClientManager.GetMcpClientAsync(item.ServerId);
49-
if (mcpClient != null)
54+
if (mcpClient == null) continue;
55+
56+
var tools = await mcpClient.ListToolsAsync();
57+
var toolNames = item.Functions.Select(x => x.Name).ToList();
58+
var targetTools = tools.Where(x => toolNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase));
59+
foreach (var tool in targetTools)
5060
{
51-
var tools = await mcpClient.ListToolsAsync();
52-
var toolnames = item.Functions.Select(x => x.Name).ToList();
53-
foreach (var tool in tools.Where(x => toolnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
61+
var funDef = AiFunctionHelper.MapToFunctionDef(tool);
62+
if (funDef != null)
5463
{
55-
var funDef = AiFunctionHelper.MapToFunctionDef(tool);
5664
functionDefs.Add(funDef);
5765
}
5866
}

src/Infrastructure/BotSharp.Core/MCP/Managers/McpClientManager.cs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,63 @@ namespace BotSharp.Core.MCP.Managers;
66

77
public class McpClientManager : IDisposable
88
{
9-
private readonly McpSettings _mcpSettings;
9+
private readonly IServiceProvider _services;
10+
private readonly ILogger<McpClientManager> _logger;
1011

11-
public McpClientManager(McpSettings mcpSettings)
12+
public McpClientManager(
13+
IServiceProvider services,
14+
ILogger<McpClientManager> logger)
1215
{
13-
_mcpSettings = mcpSettings;
16+
_services = services;
17+
_logger = logger;
1418
}
1519

16-
public async Task<IMcpClient> GetMcpClientAsync(string serverId)
20+
public async Task<IMcpClient?> GetMcpClientAsync(string serverId)
1721
{
18-
var config = _mcpSettings.McpServerConfigs.Where(x => x.Id == serverId).FirstOrDefault();
19-
20-
IClientTransport transport;
21-
if (config.SseConfig != null)
22+
try
2223
{
23-
transport = new SseClientTransport(new SseClientTransportOptions
24+
var settings = _services.GetRequiredService<McpSettings>();
25+
var config = settings.McpServerConfigs.Where(x => x.Id == serverId).FirstOrDefault();
26+
if (config == null)
2427
{
25-
Name = config.Name,
26-
Endpoint = new Uri(config.SseConfig.EndPoint),
27-
AdditionalHeaders = config.SseConfig.AdditionalHeaders,
28-
ConnectionTimeout = config.SseConfig.ConnectionTimeout
29-
});
30-
}
31-
else if (config.StdioConfig != null)
32-
{
33-
transport = new StdioClientTransport(new StdioClientTransportOptions
28+
return null;
29+
}
30+
31+
IClientTransport? transport = null;
32+
if (config.SseConfig != null)
33+
{
34+
transport = new SseClientTransport(new SseClientTransportOptions
35+
{
36+
Name = config.Name,
37+
Endpoint = new Uri(config.SseConfig.EndPoint),
38+
AdditionalHeaders = config.SseConfig.AdditionalHeaders,
39+
ConnectionTimeout = config.SseConfig.ConnectionTimeout
40+
});
41+
}
42+
else if (config.StdioConfig != null)
3443
{
35-
Name = config.Name,
36-
Command = config.StdioConfig.Command,
37-
Arguments = config.StdioConfig.Arguments,
38-
EnvironmentVariables = config.StdioConfig.EnvironmentVariables,
39-
ShutdownTimeout = config.StdioConfig.ShutdownTimeout
40-
});
44+
transport = new StdioClientTransport(new StdioClientTransportOptions
45+
{
46+
Name = config.Name,
47+
Command = config.StdioConfig.Command,
48+
Arguments = config.StdioConfig.Arguments,
49+
EnvironmentVariables = config.StdioConfig.EnvironmentVariables,
50+
ShutdownTimeout = config.StdioConfig.ShutdownTimeout
51+
});
52+
}
53+
54+
if (transport == null)
55+
{
56+
return null;
57+
}
58+
59+
return await McpClientFactory.CreateAsync(transport, settings.McpClientOptions);
4160
}
42-
else
61+
catch (Exception ex)
4362
{
44-
throw new ArgumentNullException("Invalid MCP server configuration!");
63+
_logger.LogWarning(ex, $"Error when loading mcp client {serverId}");
64+
return null;
4565
}
46-
47-
return await McpClientFactory.CreateAsync(transport, _mcpSettings.McpClientOptions);
4866
}
4967

5068
public void Dispose()

src/Infrastructure/BotSharp.Core/MCP/Services/McpService.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using BotSharp.Core.MCP.Managers;
22
using BotSharp.Core.MCP.Settings;
3-
using Microsoft.Extensions.Logging;
43
using ModelContextProtocol.Client;
54

65
namespace BotSharp.Core.MCP.Services;
@@ -9,35 +8,35 @@ public class McpService : IMcpService
98
{
109
private readonly IServiceProvider _services;
1110
private readonly ILogger<McpService> _logger;
12-
private readonly McpClientManager _mcpClientManager;
1311

1412
public McpService(
1513
IServiceProvider services,
16-
ILogger<McpService> logger,
17-
McpClientManager mcpClient)
14+
ILogger<McpService> logger)
1815
{
1916
_services = services;
2017
_logger = logger;
21-
_mcpClientManager = mcpClient;
2218
}
2319

24-
public IEnumerable<McpServerOptionModel> GetServerConfigs()
20+
public async Task<IEnumerable<McpServerOptionModel>> GetServerConfigsAsync()
2521
{
22+
var clientManager = _services.GetService<McpClientManager>();
23+
if (clientManager == null) return [];
24+
2625
var options = new List<McpServerOptionModel>();
2726
var settings = _services.GetRequiredService<McpSettings>();
2827
var configs = settings?.McpServerConfigs ?? [];
2928

3029
foreach (var config in configs)
3130
{
32-
var tools = _mcpClientManager.GetMcpClientAsync(config.Id)
33-
.Result.ListToolsAsync()
34-
.Result.Select(x=> x.Name);
31+
var client = await clientManager.GetMcpClientAsync(config.Id);
32+
if (client == null) continue;
3533

34+
var tools = await client.ListToolsAsync();
3635
options.Add(new McpServerOptionModel
3736
{
3837
Id = config.Id,
3938
Name = config.Name,
40-
Tools = tools
39+
Tools = tools.Select(x => x.Name)
4140
});
4241
}
4342

src/Infrastructure/BotSharp.Core/MCP/Settings/MCPSettings.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ public class McpSettings
77
public bool Enabled { get; set; } = true;
88
public McpClientOptions McpClientOptions { get; set; }
99
public List<McpServerConfigModel> McpServerConfigs { get; set; } = [];
10-
1110
}

src/Infrastructure/BotSharp.Core/Routing/Executor/DummyFunctionExecutor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1+
using BotSharp.Abstraction.Routing.Executor;
12
using BotSharp.Abstraction.Templating;
23

34
namespace BotSharp.Core.Routing.Executor;
45

56
public class DummyFunctionExecutor: IFunctionExecutor
67
{
7-
private FunctionDef functionDef;
88
private readonly IServiceProvider _services;
9+
private readonly FunctionDef _functionDef;
910

10-
public DummyFunctionExecutor(FunctionDef function, IServiceProvider services)
11+
public DummyFunctionExecutor(IServiceProvider services, FunctionDef functionDef)
1112
{
12-
functionDef = function;
1313
_services = services;
14+
_functionDef = functionDef;
1415
}
1516

16-
1717
public async Task<bool> ExecuteAsync(RoleDialogModel message)
1818
{
1919
var render = _services.GetRequiredService<ITemplateRender>();
@@ -25,7 +25,7 @@ public async Task<bool> ExecuteAsync(RoleDialogModel message)
2525
dict[item.Key] = item.Value;
2626
}
2727

28-
var text = render.Render(functionDef.Output, dict);
28+
var text = render.Render(_functionDef.Output!, dict);
2929
message.Content = text;
3030
return true;
3131
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1+
using BotSharp.Abstraction.Routing.Executor;
12
using BotSharp.Abstraction.Functions;
23

34
namespace BotSharp.Core.Routing.Executor;
45

56
public class FunctionCallbackExecutor : IFunctionExecutor
67
{
7-
IFunctionCallback functionCallback;
8+
private readonly IFunctionCallback _functionCallback;
89

910
public FunctionCallbackExecutor(IFunctionCallback functionCallback)
1011
{
11-
this.functionCallback = functionCallback;
12+
_functionCallback = functionCallback;
1213
}
1314

1415
public async Task<bool> ExecuteAsync(RoleDialogModel message)
1516
{
16-
return await functionCallback.Execute(message);
17+
return await _functionCallback.Execute(message);
1718
}
1819

1920
public async Task<string> GetIndicatorAsync(RoleDialogModel message)
2021
{
21-
return await functionCallback.GetIndication(message);
22+
return await _functionCallback.GetIndication(message);
2223
}
2324
}
Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,31 @@
11
using BotSharp.Abstraction.Functions;
2+
using BotSharp.Abstraction.Routing.Executor;
23

34
namespace BotSharp.Core.Routing.Executor;
45

56
internal class FunctionExecutorFactory
67
{
7-
public static IFunctionExecutor Create(string functionName, Agent agent, IFunctionCallback functioncall, IServiceProvider serviceProvider)
8+
public static IFunctionExecutor? Create(IServiceProvider services, string functionName, Agent agent)
89
{
9-
if(functioncall != null)
10+
var functionCall = services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == functionName);
11+
if (functionCall != null)
1012
{
11-
return new FunctionCallbackExecutor(functioncall);
13+
return new FunctionCallbackExecutor(functionCall);
1214
}
1315

14-
var funDef = agent?.Functions?.FirstOrDefault(x => x.Name == functionName);
15-
if (funDef != null)
16+
var functions = (agent?.Functions ?? []).Concat(agent?.SecondaryFunctions ?? []);
17+
var funcDef = functions.FirstOrDefault(x => x.Name == functionName);
18+
if (!string.IsNullOrWhiteSpace(funcDef?.Output))
1619
{
17-
if (!string.IsNullOrWhiteSpace(funDef?.Output))
18-
{
19-
return new DummyFunctionExecutor(funDef,serviceProvider);
20-
}
20+
return new DummyFunctionExecutor(services, funcDef);
2121
}
22-
else
22+
23+
var mcpServerId = agent?.McpTools?.Where(x => x.Functions.Any(y => y.Name == funcDef?.Name))?.FirstOrDefault()?.ServerId;
24+
if (!string.IsNullOrWhiteSpace(mcpServerId))
2325
{
24-
funDef = agent?.SecondaryFunctions?.FirstOrDefault(x => x.Name == functionName);
25-
if (funDef != null)
26-
{
27-
if (!string.IsNullOrWhiteSpace(funDef?.Output))
28-
{
29-
return new DummyFunctionExecutor(funDef, serviceProvider);
30-
}
31-
else
32-
{
33-
var mcpServerId = agent?.McpTools?.Where(x => x.Functions.Any(y => y.Name == funDef.Name))
34-
.FirstOrDefault().ServerId;
35-
return new MCPToolExecutor(mcpServerId, functionName, serviceProvider);
36-
}
37-
}
26+
return new McpToolExecutor(services, mcpServerId, functionName);
3827
}
28+
3929
return null;
4030
}
4131
}

0 commit comments

Comments
 (0)