diff --git a/BotSharp.sln b/BotSharp.sln
index 1e8ac13d..92feb007 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -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}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 80231ce1..c32e26ac 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,7 +1,7 @@
8.0.0
- 2.3.0
+ 2.3.0
true
@@ -15,10 +15,11 @@
-
+
+
@@ -131,7 +132,6 @@
-
diff --git a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj
index 037ed776..2980894f 100644
--- a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj
+++ b/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj
@@ -9,10 +9,11 @@
-
+
+
diff --git a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs
index 0e755df5..07397db4 100644
--- a/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs
+++ b/src/Infrastructure/BotSharp.MCP/Functions/McpToolFunction.cs
@@ -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;
}
diff --git a/src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs b/src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs
index 0b939f2e..9658eae6 100644
--- a/src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs
+++ b/src/Infrastructure/BotSharp.MCP/Hooks/MCPResponseHook.cs
@@ -30,10 +30,12 @@ public class MCPResponseHook : ConversationHookBase
public override async Task OnResponseGenerated(RoleDialogModel message)
{
var agentService = _services.GetRequiredService();
+ var state = _services.GetRequiredService();
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);
}
diff --git a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs
index 32b10d0a..e4cc3e58 100644
--- a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs
+++ b/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs
@@ -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);
diff --git a/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs b/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs
index bbe70205..7e833503 100644
--- a/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs
+++ b/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs
@@ -2,28 +2,27 @@ 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;
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 GetMcpClientAsync(string serverId)
+ {
+ return await McpClientFactory.CreateAsync(mcpSettings.McpServerConfigs
+ .Where(x=> x.Name == serverId).First(), mcpSettings.McpClientOptions);
}
public void Dispose()
diff --git a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs
index 3d8ec0c8..d161984a 100644
--- a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs
+++ b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs
@@ -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;
-
- foreach (var tool in tools.Tools)
- {
- services.AddScoped( provider => { return tool; });
-
- services.AddScoped( provider => {
- var funcTool = new McpToolFunction( provider, tool, clientManager);
- return funcTool;
- });
- }
+ RegisterFunctionCall(services, server)
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult();
}
// Register hooks
services.AddScoped();
+ services.AddScoped();
}
- }
+
+ private async Task RegisterFunctionCall(IServiceCollection services, McpDotNet.Configuration.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(provider =>
+ {
+ var funcTool = new McpToolFunction(provider, tool, clientManager);
+ return funcTool;
+ });
+ }
+ }
+}
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index 2582a8ae..a0e88d0d 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -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",
diff --git a/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj b/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj
index 03b092b1..5d0ed45d 100644
--- a/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj
+++ b/tests/BotSharp.PizzaBot.MCPServer/BotSharp.PizzaBot.MCPServer.csproj
@@ -8,10 +8,12 @@
enable
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/tests/BotSharp.PizzaBot.MCPServer/Program.cs b/tests/BotSharp.PizzaBot.MCPServer/Program.cs
index b2477c99..534054b2 100644
--- a/tests/BotSharp.PizzaBot.MCPServer/Program.cs
+++ b/tests/BotSharp.PizzaBot.MCPServer/Program.cs
@@ -12,6 +12,9 @@ namespace BotSharp.PizzaBot.MCPServer
{
internal class Program
{
+ private static HashSet _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 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(){"pizza_type", "quantity", "unit_price" }
@@ -103,86 +120,98 @@ namespace BotSharp.PizzaBot.MCPServer
}
]
});
- };
+ },
- server.CallToolHandler = async (request, cancellationToken) =>
- {
- if (request.Params.Name == "make_payment")
+ CallToolHandler = async (request, cancellationToken) =>
{
- if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("order_number", out var order_number))
+ if (request.Params.Name == "make_payment")
{
- 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";
+ 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);
+ //// Serialize the message to JSON
+ //var jso = new JsonSerializerOptions() { WriteIndented = true };
+ //var jsonMessage = JsonSerializer.Serialize(message, jso);
- return new CallToolResponse()
+ 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")
{
- Content = [new Content() { Text = jsonMessage, Type = "text" }]
- };
+ 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}");
+ }
}
- 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'");
- }
- 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.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 = jsonMessage, Type = "text" }]
- };
- }
- else
- {
- throw new McpServerException($"Unknown tool: {request.Params.Name}");
- }
- };
+ };
}
+
private static ILoggerFactory CreateLoggerFactory()
{
diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json
index 9699d5ab..2e40c4e0 100644
--- a/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json
+++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json
@@ -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",