commit
6ac8732927
33
BotSharp.sln
33
BotSharp.sln
|
|
@ -127,9 +127,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj", "{8ED8EEF4-06DD-45F5-AA91-BD2395E901B5}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj", "{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{AD91B4ED-0623-4710-913E-6D7C893E76EF}"
|
||||
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}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{8D2AD45F-836A-516F-DE6A-71443CEBB18A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -523,6 +525,30 @@ Global
|
|||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{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
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -584,6 +610,9 @@ Global
|
|||
{F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{8D2AD45F-836A-516F-DE6A-71443CEBB18A} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="EntityFramework" Version="6.4.4" />
|
||||
<PackageVersion Include="mcpdotnet" Version="1.1.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
|
|
@ -13,13 +14,16 @@
|
|||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<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" />
|
||||
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Rougamo.Fody" Version="4.0.4" />
|
||||
<PackageVersion Include="Aspects.Cache" Version="2.0.4" />
|
||||
|
|
@ -106,6 +110,8 @@
|
|||
<PackageVersion Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.1.0-preview.1.25171.12" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BotSharp.Core" Version="$(BotSharpVersion)" />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj" />
|
||||
<ProjectReference Include="..\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\WebStarter\WebStarter.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ var builder = DistributedApplication.CreateBuilder(args);
|
|||
|
||||
var apiService = builder.AddProject<Projects.WebStarter>("apiservice")
|
||||
.WithExternalHttpEndpoints();
|
||||
var mcpService = builder.AddProject<Projects.BotSharp_PizzaBot_MCPServer>("mcpservice")
|
||||
.WithExternalHttpEndpoints();
|
||||
|
||||
builder.AddNpmApp("BotSharpUI", "../../../BotSharp-UI")
|
||||
.WithReference(apiService)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public enum AgentField
|
|||
Sample,
|
||||
LlmConfig,
|
||||
Utility,
|
||||
Acp,
|
||||
McpTool,
|
||||
KnowledgeBase,
|
||||
Rule,
|
||||
MaxMessageCount
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ public partial class AgentService
|
|||
var samples = GetSamplesFromFile(dir);
|
||||
return agent.SetInstruction(defaultInstruction)
|
||||
.SetChannelInstructions(channelInstructions)
|
||||
.SetTemplates(templates)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace BotSharp.Core.Repository
|
|||
case AgentField.Utility:
|
||||
UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
|
||||
break;
|
||||
case AgentField.Acp:
|
||||
case AgentField.McpTool:
|
||||
UpdateAgentMcpTools(agent.Id, agent.McpTools);
|
||||
break;
|
||||
case AgentField.KnowledgeBase:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Core.MCP;
|
||||
|
||||
|
|
@ -16,19 +15,19 @@ internal static class AIFunctionUtilities
|
|||
throw new ArgumentNullException(nameof(tool));
|
||||
}
|
||||
|
||||
var properties = tool.InputSchema?.Properties;
|
||||
var required = tool.InputSchema?.Required ?? new List<string>();
|
||||
var properties = tool.InputSchema.GetProperty("properties");
|
||||
var required = tool.InputSchema.GetProperty("required");
|
||||
|
||||
FunctionDef funDef = new FunctionDef
|
||||
{
|
||||
Name = tool.Name,
|
||||
Description = tool.Description?? string.Empty,
|
||||
Description = tool.Description ?? string.Empty,
|
||||
Type = "function",
|
||||
Parameters = new FunctionParametersDef
|
||||
{
|
||||
Type = "object",
|
||||
Properties = properties != null ? JsonSerializer.SerializeToDocument(properties) : JsonDocument.Parse("{}"),
|
||||
Required = required
|
||||
Properties = JsonDocument.Parse(properties.GetRawText()),
|
||||
Required = JsonSerializer.Deserialize<List<string>>(required.GetRawText())
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="mcpdotnet" Version="1.1.0.1" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
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 BotSharp.Abstraction.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -38,7 +37,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,7 +49,7 @@ public class McpToolFunction : IFunctionCallback
|
|||
.Where(c => c.Type == "text")
|
||||
.Select(c => c.Text));
|
||||
message.Content = json;
|
||||
message.Data = JsonSerializer.Deserialize(json,typeof(object));
|
||||
message.Data = json.JsonContent();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
36
src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs
Normal file
36
src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Conversations;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.MCP.Hooks;
|
||||
|
||||
public class MCPResponseHook : ConversationHookBase
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IConversationStateService _states;
|
||||
|
||||
public MCPResponseHook(IServiceProvider services,
|
||||
IConversationStateService states)
|
||||
{
|
||||
_services = services;
|
||||
_states = states;
|
||||
}
|
||||
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)))
|
||||
{
|
||||
var data = JsonDocument.Parse(JsonSerializer.Serialize(message.Data));
|
||||
state.SaveStateByArgs(data);
|
||||
}
|
||||
await base.OnResponseGenerated(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ using BotSharp.Abstraction.Functions.Models;
|
|||
using BotSharp.Core.Mcp;
|
||||
using BotSharp.Core.MCP;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using BotSharp.Core.Mcp.Settings;
|
||||
using McpDotNet.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
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;
|
||||
|
||||
public MCPClientManager(MCPSettings settings, ILoggerFactory loggerFactory)
|
||||
{
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ 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;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Configuration;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Core.Mcp;
|
||||
|
||||
|
|
@ -31,20 +33,30 @@ public class McpPlugin : IBotSharpPlugin
|
|||
|
||||
foreach (var server in settings.McpServerConfigs)
|
||||
{
|
||||
var client = clientManager.Factory.GetClientAsync(server.Id).Result;
|
||||
var tools = client.ListToolsAsync().Result;
|
||||
|
||||
foreach (var tool in tools.Tools)
|
||||
{
|
||||
services.AddScoped( provider => { return tool; });
|
||||
|
||||
services.AddScoped<IFunctionCallback>( provider => {
|
||||
var funcTool = new McpToolFunction( provider, tool, clientManager);
|
||||
return funcTool;
|
||||
});
|
||||
}
|
||||
RegisterFunctionCall(services, server)
|
||||
.ConfigureAwait(false)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
// Register hooks
|
||||
services.AddScoped<IAgentHook, MCPToolAgentHook>();
|
||||
services.AddScoped<IConversationHook, MCPResponseHook>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RegisterFunctionCall(IServiceCollection services, McpServerConfig server)
|
||||
{
|
||||
var client = await clientManager.GetMcpClientAsync(server.Id);
|
||||
var tools = await client.ListToolsAsync().ToListAsync();
|
||||
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
services.AddScoped(provider => { return tool; });
|
||||
|
||||
services.AddScoped<IFunctionCallback>(provider =>
|
||||
{
|
||||
var funcTool = new McpToolFunction(provider, tool, clientManager);
|
||||
return funcTool;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using McpDotNet.Client;
|
||||
using McpDotNet.Configuration;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Configuration;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BotSharp.Core.Mcp.Settings;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Models;
|
||||
|
||||
|
|
@ -16,7 +11,6 @@ public class AgentMCPToolMongoElement
|
|||
|
||||
public bool Disabled { get; set; }
|
||||
public List<McpFunctionMongoElement> Functions { get; set; } = [];
|
||||
public List<McpTemplateMongoElement> Templates { get; set; } = [];
|
||||
|
||||
public static AgentMCPToolMongoElement ToMongoElement(MCPTool utility)
|
||||
{
|
||||
|
|
@ -51,19 +45,3 @@ public class McpFunctionMongoElement
|
|||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public class McpTemplateMongoElement
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public McpTemplateMongoElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public McpTemplateMongoElement(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ public partial class MongoRepository
|
|||
case AgentField.Utility:
|
||||
UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
|
||||
break;
|
||||
case AgentField.Acp:
|
||||
UpdateAgentMcps(agent.Id, agent.McpTools);
|
||||
case AgentField.McpTool:
|
||||
UpdateAgentMcpTools(agent.Id, agent.McpTools);
|
||||
break;
|
||||
case AgentField.KnowledgeBase:
|
||||
UpdateAgentKnowledgeBases(agent.Id, agent.KnowledgeBases);
|
||||
|
|
@ -264,7 +264,7 @@ public partial class MongoRepository
|
|||
_dc.Agents.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
private void UpdateAgentMcps(string agentId, List<MCPTool> mcps)
|
||||
private void UpdateAgentMcpTools(string agentId, List<MCPTool> mcps)
|
||||
{
|
||||
if (mcps == null) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj" />
|
||||
<ProjectReference Include="..\..\tests\BotSharp.Plugin.PizzaBot\BotSharp.Plugin.PizzaBot.csproj" />
|
||||
<ProjectReference Include="..\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj" />
|
||||
|
|
|
|||
|
|
@ -182,11 +182,9 @@
|
|||
{
|
||||
"Id": "PizzaServer",
|
||||
"Name": "PizzaServer",
|
||||
"TransportType": "stdio",
|
||||
"TransportOptions": {
|
||||
"command": "BotSharp.PizzaBot.MCPServer.exe",
|
||||
"workingDirectory": "E:\\GitHub\\BotSharp\\src\\WebStarter\\bin\\Debug\\net8.0"
|
||||
}
|
||||
"TransportType": "sse",
|
||||
"TransportOptions": [],
|
||||
"Location": "http://localhost:58905/sse"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="mcpdotnet" Version="1.0.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer;
|
||||
|
||||
public static class McpEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
IMcpServer? server = null;
|
||||
SseServerStreamTransport? transport = null;
|
||||
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var mcpServerOptions = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();
|
||||
|
||||
var routeGroup = endpoints.MapGroup("");
|
||||
|
||||
routeGroup.MapGet("/sse", async (HttpResponse response, CancellationToken requestAborted) =>
|
||||
{
|
||||
await using var localTransport = transport = new SseServerStreamTransport(response.Body);
|
||||
await using var localServer = server = McpServerFactory.Create(transport, mcpServerOptions.Value, loggerFactory, endpoints.ServiceProvider);
|
||||
|
||||
await localServer.StartAsync(requestAborted);
|
||||
|
||||
response.Headers.ContentType = "text/event-stream";
|
||||
response.Headers.CacheControl = "no-cache";
|
||||
|
||||
try
|
||||
{
|
||||
await transport.RunAsync(requestAborted);
|
||||
}
|
||||
catch (OperationCanceledException) when (requestAborted.IsCancellationRequested)
|
||||
{
|
||||
// RequestAborted always triggers when the client disconnects before a complete response body is written,
|
||||
// but this is how SSE connections are typically closed.
|
||||
}
|
||||
});
|
||||
|
||||
routeGroup.MapPost("/message", async (HttpContext context) =>
|
||||
{
|
||||
if (transport is null)
|
||||
{
|
||||
await Results.BadRequest("Connect to the /sse endpoint before sending messages.").ExecuteAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);
|
||||
if (message is null)
|
||||
{
|
||||
await Results.BadRequest("No message in request body.").ExecuteAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
await transport.OnMessageReceivedAsync(message, context.RequestAborted);
|
||||
context.Response.StatusCode = StatusCodes.Status202Accepted;
|
||||
await context.Response.WriteAsync("Accepted");
|
||||
});
|
||||
|
||||
return routeGroup;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,204 +1,236 @@
|
|||
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;
|
||||
using BotSharp.PizzaBot.MCPServer;
|
||||
using ModelContextProtocol;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting server...");
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddMcpServer().WithTools();
|
||||
var app = builder.Build();
|
||||
|
||||
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();
|
||||
app.MapGet("/", () => "This is a test server with only stub functionality!");
|
||||
app.MapMcpSse();
|
||||
|
||||
Console.WriteLine("Server object created, registering handlers.");
|
||||
app.Run();
|
||||
|
||||
#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<string, JsonSchemaProperty>()
|
||||
{
|
||||
["order_number"] = new JsonSchemaProperty() { Type = "string", Description = "order number." },
|
||||
["total_amount"] = new JsonSchemaProperty() { Type = "string", Description = "total amount." },
|
||||
},
|
||||
Required = new List<string>() { "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<string, JsonSchemaProperty>()
|
||||
{
|
||||
["pizza_type"] = new JsonSchemaProperty() { Type = "string", Description = "The pizza type." },
|
||||
["quantity"] = new JsonSchemaProperty() { Type = "string", Description = "quantity of pizza." },
|
||||
|
||||
},
|
||||
Required = new List<string>(){ "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<string, JsonSchemaProperty>()
|
||||
{
|
||||
["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" },
|
||||
//namespace BotSharp.PizzaBot.MCPServer
|
||||
//{
|
||||
// internal class Program
|
||||
// {
|
||||
// private static HashSet<string> _subscribedResources = new();
|
||||
// private static readonly object _subscribedResourcesLock = new();
|
||||
|
||||
},
|
||||
Required = new List<string>(){"pizza_type", "quantity", "unit_price" }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
// private static async Task Main(string[] args)
|
||||
// {
|
||||
// Console.WriteLine("Starting server...");
|
||||
|
||||
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.Transaction = Guid.NewGuid().ToString();
|
||||
message.Status = "Success";
|
||||
// McpServerOptions options = new McpServerOptions()
|
||||
// {
|
||||
// ServerInfo = new Implementation() { Name = "PizzaServer", Version = "1.0.0" },
|
||||
// Capabilities = new ServerCapabilities()
|
||||
// {
|
||||
// Tools = ConfigureTools(),
|
||||
// },
|
||||
// ProtocolVersion = "2024-11-05",
|
||||
// ServerInstructions = "This is a test server with only stub functionality"
|
||||
// };
|
||||
// var loggerFactory = CreateLoggerFactory();
|
||||
// await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("TestServer", loggerFactory), options, loggerFactory);
|
||||
// Log.Logger.Information("Server initialized.");
|
||||
|
||||
// Serialize the message to JSON
|
||||
var jso = new JsonSerializerOptions() { WriteIndented = true };
|
||||
var jsonMessage = JsonSerializer.Serialize(message, jso);
|
||||
// await server.StartAsync();
|
||||
|
||||
return new CallToolResponse()
|
||||
{
|
||||
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}");
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
// Log.Logger.Information("Server started.");
|
||||
|
||||
Console.WriteLine("Server initialized.");
|
||||
// // Run until process is stopped by the client (parent process)
|
||||
// while (true)
|
||||
// {
|
||||
// await Task.Delay(5000);
|
||||
|
||||
await server.StartAsync();
|
||||
// // Snapshot the subscribed resources, rather than locking while sending notifications
|
||||
// List<string> resources;
|
||||
// lock (_subscribedResourcesLock)
|
||||
// {
|
||||
// resources = _subscribedResources.ToList();
|
||||
// }
|
||||
|
||||
Console.WriteLine("Server started.");
|
||||
// foreach (var resource in resources)
|
||||
// {
|
||||
// ResourceUpdatedNotificationParams notificationParams = new() { Uri = resource };
|
||||
// await server.SendMessageAsync(new JsonRpcNotification()
|
||||
// {
|
||||
// Method = NotificationMethods.ResourceUpdatedNotification,
|
||||
// Params = notificationParams
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Run until process is stopped by the client (parent process)
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
// private static ToolsCapability ConfigureTools()
|
||||
// {
|
||||
// return new()
|
||||
// {
|
||||
// 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<string, JsonSchemaProperty>()
|
||||
// {
|
||||
// ["order_number"] = new JsonSchemaProperty() { Type = "string", Description = "order number." },
|
||||
// ["total_amount"] = new JsonSchemaProperty() { Type = "string", Description = "total amount." },
|
||||
// },
|
||||
// Required = new List<string>() { "order_number", "total_amount" }
|
||||
// },
|
||||
// },
|
||||
// new Tool()
|
||||
// {
|
||||
// Name = "get_pizza_prices",
|
||||
// Description = "call this function to get pizza unit price",
|
||||
// InputSchema = new JsonSchema()
|
||||
// {
|
||||
// Type = "object",
|
||||
// Properties = new Dictionary<string, JsonSchemaProperty>()
|
||||
// {
|
||||
// ["pizza_type"] = new JsonSchemaProperty() { Type = "string", Description = "The pizza type." },
|
||||
// ["quantity"] = new JsonSchemaProperty() { Type = "string", Description = "quantity of pizza." },
|
||||
|
||||
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();
|
||||
// },
|
||||
// Required = new List<string>(){ "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<string, JsonSchemaProperty>()
|
||||
// {
|
||||
// ["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 = "pizza unit price" },
|
||||
|
||||
var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log");
|
||||
return LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.AddSerilog();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// },
|
||||
// Required = new List<string>(){"pizza_type", "quantity", "unit_price" }
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// });
|
||||
// },
|
||||
|
||||
// CallToolHandler = async (request, cancellationToken) =>
|
||||
// {
|
||||
// if (request.Params.Name == "make_payment")
|
||||
// {
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("order_number", out var order_number))
|
||||
// {
|
||||
// throw new McpServerException("Missing required argument 'order_number'");
|
||||
// }
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("total_amount", out var total_amount))
|
||||
// {
|
||||
// throw new McpServerException("Missing required argument 'total_amount'");
|
||||
// }
|
||||
// //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);
|
||||
|
||||
// return new CallToolResponse()
|
||||
// {
|
||||
// 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")
|
||||
// {
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("pizza_type", out var pizza_type))
|
||||
// {
|
||||
// throw new McpServerException("Missing required argument 'pizza_type'");
|
||||
// }
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("quantity", out var quantity))
|
||||
// {
|
||||
// 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.unit_price = unit_price;
|
||||
// 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.Params.Name == "place_an_order")
|
||||
// {
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("pizza_type", out var pizza_type))
|
||||
// {
|
||||
// throw new McpServerException("Missing required argument 'pizza_type'");
|
||||
// }
|
||||
// if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("quantity", out var quantity))
|
||||
// {
|
||||
// throw new McpServerException("Missing required argument 'quantity'");
|
||||
// }
|
||||
// if (request.Params.Arguments is null || !request.Params.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 = "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
|
||||
// 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();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"profiles": {
|
||||
"BotSharp.PizzaBot.MCPServer": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:58904;http://localhost:58905"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using System.Buffers;
|
||||
using System.Net.ServerSentEvents;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer;
|
||||
|
||||
public class SseServerStreamTransport(Stream sseResponseStream) : ITransport
|
||||
{
|
||||
private readonly Channel<IJsonRpcMessage> _incomingChannel = CreateSingleItemChannel<IJsonRpcMessage>();
|
||||
private readonly Channel<SseItem<IJsonRpcMessage?>> _outgoingSseChannel = CreateSingleItemChannel<SseItem<IJsonRpcMessage?>>();
|
||||
|
||||
private Task? _sseWriteTask;
|
||||
private Utf8JsonWriter? _jsonWriter;
|
||||
|
||||
public bool IsConnected => _sseWriteTask?.IsCompleted == false;
|
||||
|
||||
public Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBufferWriter<byte> writer)
|
||||
{
|
||||
if (item.EventType == "endpoint")
|
||||
{
|
||||
writer.Write("/message"u8);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.DefaultOptions);
|
||||
}
|
||||
|
||||
// The very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single item of a different type,
|
||||
// so we fib and special-case the "endpoint" event type in the formatter.
|
||||
_outgoingSseChannel.Writer.TryWrite(new SseItem<IJsonRpcMessage?>(null, "endpoint"));
|
||||
|
||||
var sseItems = _outgoingSseChannel.Reader.ReadAllAsync(cancellationToken);
|
||||
return _sseWriteTask = SseFormatter.WriteAsync(sseItems, sseResponseStream, WriteJsonRpcMessageToBuffer, cancellationToken);
|
||||
}
|
||||
|
||||
public ChannelReader<IJsonRpcMessage> MessageReader => _incomingChannel.Reader;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_incomingChannel.Writer.TryComplete();
|
||||
_outgoingSseChannel.Writer.TryComplete();
|
||||
return new ValueTask(_sseWriteTask ?? Task.CompletedTask);
|
||||
}
|
||||
|
||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||
_outgoingSseChannel.Writer.WriteAsync(new SseItem<IJsonRpcMessage?>(message), cancellationToken).AsTask();
|
||||
|
||||
public Task OnMessageReceivedAsync(IJsonRpcMessage message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected");
|
||||
}
|
||||
|
||||
return _incomingChannel.Writer.WriteAsync(message, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
private static Channel<T> CreateSingleItemChannel<T>() =>
|
||||
Channel.CreateBounded<T>(new BoundedChannelOptions(1)
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
});
|
||||
|
||||
private Utf8JsonWriter GetUtf8JsonWriter(IBufferWriter<byte> writer)
|
||||
{
|
||||
if (_jsonWriter is null)
|
||||
{
|
||||
_jsonWriter = new Utf8JsonWriter(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_jsonWriter.Reset(writer);
|
||||
}
|
||||
|
||||
return _jsonWriter;
|
||||
}
|
||||
}
|
||||
25
tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs
Normal file
25
tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer.Tools;
|
||||
|
||||
[McpToolType]
|
||||
public static class MakePayment
|
||||
{
|
||||
[McpTool(name: "make_payment"), Description("call this function to make payment.")]
|
||||
public static string Make_Payment(
|
||||
[Description("order number"),Required] string order_number,
|
||||
[Description("total amount"),Required] int total_amount)
|
||||
{
|
||||
if (order_number is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'order_number'");
|
||||
}
|
||||
if (order_number is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'total_amount'");
|
||||
}
|
||||
return "Payment proceed successfully. Thank you for your business. Have a great day!";
|
||||
}
|
||||
}
|
||||
46
tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs
Normal file
46
tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Dynamic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer.Tools;
|
||||
|
||||
[McpToolType]
|
||||
public static class PizzaPrices
|
||||
{
|
||||
[McpTool(name: "get_pizza_prices"), Description("call this function to get pizza unit price.")]
|
||||
public static string GetPizzaPrices(
|
||||
[Description("The pizza type."), Required] string pizza_type,
|
||||
[Description("quantity of pizza"), Required] int quantity)
|
||||
{
|
||||
if (pizza_type is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'pizza_type'");
|
||||
}
|
||||
if (quantity <= 0)
|
||||
{
|
||||
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.unit_price = unit_price;
|
||||
var jso = new JsonSerializerOptions() { WriteIndented = true };
|
||||
var jsonMessage = JsonSerializer.Serialize(message, jso);
|
||||
|
||||
return jsonMessage;
|
||||
}
|
||||
}
|
||||
31
tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs
Normal file
31
tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BotSharp.PizzaBot.MCPServer.Tools;
|
||||
|
||||
[McpToolType]
|
||||
public static class PlaceOrder
|
||||
{
|
||||
[McpTool(name: "place_an_order"), Description("Place an order when user has confirmed the pizza type and quantity.")]
|
||||
public static string PlaceAnOrder(
|
||||
[Description("The pizza type."), Required] string pizza_type,
|
||||
[Description("quantity of pizza"), Required] int quantity,
|
||||
[Description("pizza unit price"),Required] double unit_price)
|
||||
{
|
||||
if (pizza_type is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'pizza_type'");
|
||||
}
|
||||
if (quantity <= 0)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'quantity'");
|
||||
}
|
||||
if (unit_price <= 0)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'unit_price'");
|
||||
}
|
||||
|
||||
return "The order number is P123-01: {order_number = \"P123-01\" }";
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
@ -20,6 +20,6 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"labels": [ "experiment" ]
|
||||
}
|
||||
Loading…
Reference in a new issue