From 660d422069dbf2244be2f03a4f64471af420cec0 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 17:52:28 +0100 Subject: [PATCH 1/8] add mcp prompt sample --- .../Extensions/PromptResultExtensions.cs | 45 ++++++++ .../MCPClient/Extensions/RoleExtensions.cs | 28 +++++ .../MCPClient/Program.cs | 96 ++++++++++++++++ .../MCPServer/MCPServer.csproj | 1 + .../MCPServer/Program.cs | 9 +- .../Prompts/GetCurrentWeatherForCityPrompt.cs | 108 ++++++++++++++++++ .../MCPServer/Prompts/PromptDefinition.cs | 22 ++++ .../MCPServer/Prompts/PromptRegistry.cs | 61 ++++++++++ .../MCPServer/Tools/DateTimeUtils.cs | 2 +- 9 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/PromptResultExtensions.cs create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/RoleExtensions.cs create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/PromptResultExtensions.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/PromptResultExtensions.cs new file mode 100644 index 000000000000..0843331a2fc9 --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/PromptResultExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using ModelContextProtocol.Protocol.Types; + +namespace MCPClient; + +/// +/// Extension methods for . +/// +internal static class PromptResultExtensions +{ + /// + /// Converts a to a . + /// + /// The prompt result to convert. + /// The corresponding . + public static ChatHistory ToChatHistory(this GetPromptResult result) + { + ChatHistory chatHistory = []; + + foreach (PromptMessage message in result.Messages) + { + ChatMessageContentItemCollection items = []; + + switch (message.Content.Type) + { + case "text": + items.Add(new TextContent(message.Content.Text)); + break; + case "image": + items.Add(new ImageContent(Convert.FromBase64String(message.Content.Data!), message.Content.MimeType)); + break; + default: + throw new InvalidOperationException($"Unexpected message content type '{message.Content.Type}'"); + } + + chatHistory.Add(new ChatMessageContent(message.Role.ToAuthorRole(), items)); + } + + return chatHistory; + } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/RoleExtensions.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/RoleExtensions.cs new file mode 100644 index 000000000000..f653ab1a991d --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Extensions/RoleExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.SemanticKernel.ChatCompletion; +using ModelContextProtocol.Protocol.Types; + +namespace MCPClient; + +/// +/// Extension methods for the enum. +/// +internal static class RoleExtensions +{ + /// + /// Converts a to a . + /// + /// The MCP role to convert. + /// The corresponding . + public static AuthorRole ToAuthorRole(this Role role) + { + return role switch + { + Role.User => AuthorRole.User, + Role.Assistant => AuthorRole.Assistant, + _ => throw new InvalidOperationException($"Unexpected role '{role}'") + }; + } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Program.cs index 41d50c1674a6..333246575c44 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Program.cs @@ -7,10 +7,12 @@ using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using ModelContextProtocol; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Transport; +using ModelContextProtocol.Protocol.Types; namespace MCPClient; @@ -18,6 +20,28 @@ internal sealed class Program { public static async Task Main(string[] args) { + // Use the MCP tools with the Semantic Kernel + await UseMCPToolsWithSKAsync(); + + // Use the MCP tools and MCP prompt with the Semantic Kernel + await UseMCPToolsAndPromptWithSKAsync(); + } + + /// + /// Demonstrates how to use the MCP tools with the Semantic Kernel. + /// The code in this method: + /// 1. Creates an MCP client. + /// 2. Retrieves the list of tools provided by the MCP server. + /// 3. Creates a kernel and registers the MCP tools as Kernel functions. + /// 4. Sends the prompt to AI model together with the MCP tools represented as Kernel functions. + /// 5. The AI model calls DateTimeUtils-GetCurrentDateTimeInUtc function to get the current date time in UTC required as an argument for the next function. + /// 6. The AI model calls WeatherUtils-GetWeatherForCity function with the current date time and the `Boston` arguments extracted from the prompt to get the weather information. + /// 7. Having received the weather information from the function call, the AI model returns the answer to the prompt. + /// + private static async Task UseMCPToolsWithSKAsync() + { + Console.WriteLine($"Running the {nameof(UseMCPToolsWithSKAsync)} sample."); + // Create an MCP client await using IMcpClient mcpClient = await CreateMcpClientAsync(); @@ -43,10 +67,67 @@ public static async Task Main(string[] args) FunctionResult result = await kernel.InvokePromptAsync(prompt, new(executionSettings)); Console.WriteLine(result); + Console.WriteLine(); // The expected output is: The likely color of the sky in Boston today is gray, as it is currently rainy. } + /// + /// Demonstrates how to use the MCP tools and MCP prompt with the Semantic Kernel. + /// The code in this method: + /// 1. Creates an MCP client. + /// 2. Retrieves the list of tools provided by the MCP server. + /// 3. Retrieves the list of prompts provided by the MCP server. + /// 4. Creates a kernel and registers the MCP tools as Kernel functions. + /// 5. Requests the `GetCurrentWeatherForCity` prompt from the MCP server. + /// 6. The MCP server renders the prompt using the `Boston` as value for the `city` parameter and the result of the `DateTimeUtils-GetCurrentDateTimeInUtc` server-side invocation added to the prompt as part of prompt rendering. + /// 7. Converts the MCP server prompt: list of messages where each message is represented by content and role to a chat history. + /// 8. Sends the chat history to the AI model together with the MCP tools represented as Kernel functions. + /// 9. The AI model calls WeatherUtils-GetWeatherForCity function with the current date time and the `Boston` arguments extracted from the prompt to get the weather information. + /// 10. Having received the weather information from the function call, the AI model returns the answer to the prompt. + /// + private static async Task UseMCPToolsAndPromptWithSKAsync() + { + Console.WriteLine($"Running the {nameof(UseMCPToolsAndPromptWithSKAsync)} sample."); + + // Create an MCP client + await using IMcpClient mcpClient = await CreateMcpClientAsync(); + + // Retrieve and display the list provided by the MCP server + IList tools = await mcpClient.ListToolsAsync(); + DisplayTools(tools); + + // Retrieve and display the list of prompts provided by the MCP server + IList prompts = await mcpClient.ListPromptsAsync(); + DisplayPrompts(prompts); + + // Create a kernel and register the MCP tools + Kernel kernel = CreateKernelWithChatCompletionService(); + kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction())); + + // Enable automatic function calling + OpenAIPromptExecutionSettings executionSettings = new() + { + Temperature = 0, + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true }) + }; + + // Retrieve the `GetCurrentWeatherForCity` prompt from the MCP server and convert it to a chat history + GetPromptResult promptResult = await mcpClient.GetPromptAsync("GetCurrentWeatherForCity", new Dictionary() { ["city"] = "Boston" }); + + ChatHistory chatHistory = promptResult.ToChatHistory(); + + // Execute a prompt using the MCP tools and prompt + IChatCompletionService chatCompletion = kernel.GetRequiredService(); + + ChatMessageContent result = await chatCompletion.GetChatMessageContentAsync(chatHistory, executionSettings, kernel); + + Console.WriteLine(result); + Console.WriteLine(); + + // The expected output is: The weather in Boston as of 2025-04-02 16:39:40 is 61°F and rainy. + } + /// /// Creates an instance of with the OpenAI chat completion service registered. /// @@ -129,5 +210,20 @@ private static void DisplayTools(IList tools) { Console.WriteLine($"- {tool.Name}: {tool.Description}"); } + Console.WriteLine(); + } + + /// + /// Displays the list of available MCP prompts. + /// + /// The list of the prompts to display. + private static void DisplayPrompts(IList prompts) + { + Console.WriteLine("Available MCP prompts:"); + foreach (var prompt in prompts) + { + Console.WriteLine($"- {prompt.Name}: {prompt.Description}"); + } + Console.WriteLine(); } } diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj index 628827a0e11a..c5c5c611b4f9 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj @@ -13,6 +13,7 @@ + diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs index 79aa59a5a180..b73ba54d9a67 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using MCPServer; +using MCPServer.Prompts; using MCPServer.Tools; using Microsoft.SemanticKernel; @@ -12,10 +13,16 @@ // Build the kernel Kernel kernel = kernelBuilder.Build(); +// Register prompts +PromptRegistry.RegisterPrompt(GetCurrentWeatherForCityPrompt.GetDefinition(kernel)); + var builder = Host.CreateEmptyApplicationBuilder(settings: null); builder.Services .AddMcpServer() .WithStdioServerTransport() // Add all functions from the kernel plugins to the MCP server as tools - .WithTools(kernel.Plugins); + .WithTools(kernel.Plugins) + // Register prompt handlers + .WithListPromptsHandler(PromptRegistry.GetHandlerForListPromptRequestsAsync) + .WithGetPromptHandler(PromptRegistry.GetHandlerForGetPromptRequestsAsync); await builder.Build().RunAsync(); diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs new file mode 100644 index 000000000000..3bb3fff1edb0 --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.PromptTemplates.Handlebars; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace MCPServer.Prompts; + +/// +/// Represents the GetCurrentWeatherForCity prompt. +/// +internal static class GetCurrentWeatherForCityPrompt +{ + private static readonly PromptTemplateConfig s_promptTemplateConfig = PromptTemplateConfig.FromJson(""" + { + "name": "GetCurrentWeatherForCity", + "description": "Provides current weather information for a specified city.", + "template_format": "handlebars", + "template": "What is the weather in {{city}} as of {{DateTimeUtils-GetCurrentDateTimeInUtc}}?", + "input_variables": [ + { + "name": "city", + "description": "The city for which to get the weather.", + "is_required": true + } + ] + } + """); + + private static readonly IPromptTemplate s_promptTemplate = new HandlebarsPromptTemplateFactory().Create(s_promptTemplateConfig); + + /// + /// Gets this prompt definition. + /// + /// An instance of the kernel to render the prompt. + /// The prompt definition. + public static PromptDefinition GetDefinition(Kernel kernel) + { + return new() + { + Prompt = GetPrompt(), + Handler = (context, cancellationToken) => GetPromptHandlerAsync(context, kernel, cancellationToken) + }; + } + + /// + /// Creates an MCP prompt from SK prompt template. + /// + /// The MCP prompt. + private static Prompt GetPrompt() + { + // Create the MCP prompt arguments + List? arguments = null; + + foreach (var inputVariable in s_promptTemplateConfig.InputVariables) + { + (arguments ??= []).Add(new() + { + Name = inputVariable.Name, + Description = inputVariable.Description, + Required = inputVariable.IsRequired + }); + } + + // Create the MCP prompt + return new Prompt + { + Name = s_promptTemplateConfig.Name!, + Description = s_promptTemplateConfig.Description, + Arguments = arguments + }; + } + + /// + /// Handles the prompt request by rendering the prompt. + /// + /// The prompt request context. + /// The kernel to render the prompt. + /// The cancellation token. + /// The prompt. + private static async Task GetPromptHandlerAsync(RequestContext context, Kernel kernel, CancellationToken cancellationToken) + { + // Render the prompt + string renderedPrompt = await s_promptTemplate.RenderAsync( + kernel: kernel, + arguments: context.Params?.Arguments is { } args ? new KernelArguments(args!) : null, + cancellationToken: cancellationToken); + + // Create prompt result + return new GetPromptResult() + { + Description = s_promptTemplateConfig.Description, + Messages = + [ + new PromptMessage() + { + Content = new Content() + { + Type = "text", + Text = renderedPrompt + }, + Role = Role.User + } + ] + }; + } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs new file mode 100644 index 000000000000..d4c1d410b9fa --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace MCPServer.Prompts; + +/// +/// Represents a prompt definition. +/// +internal class PromptDefinition +{ + /// + /// Gets or sets the prompt. + /// + public required Prompt Prompt { get; init; } + + /// + /// Gets or sets the handler for the prompt. + /// + public required Func, CancellationToken, Task> Handler { get; init; } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs new file mode 100644 index 000000000000..4599caaf2ec4 --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace MCPServer.Prompts; + +/// +/// Represents the prompt registry that contains the prompt definitions and provides the handlers for the prompt `List` and `Get` requests. +/// +internal static class PromptRegistry +{ + private static readonly Dictionary s_definitions = new(); + + /// + /// Registers a prompt definition. + /// + /// The prompt definition to register. + public static void RegisterPrompt(PromptDefinition definition) + { + s_definitions[definition.Prompt.Name] = definition; + } + + /// + /// Gets the handler for the `Get` prompt requests. + /// + /// The request context. + /// The cancellation token. + /// The result of the `Get` prompt request. + public static async Task GetHandlerForGetPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + { + // Make sure the prompt name is provided + if (context.Params?.Name is not string { } promptName || string.IsNullOrEmpty(promptName)) + { + throw new ArgumentException("Prompt name is required."); + } + + // Look up the prompt handler + if (!s_definitions.TryGetValue(promptName, out PromptDefinition? definition)) + { + throw new ArgumentException($"No handler found for the prompt '{promptName}'."); + } + + // Invoke the handler + return await definition.Handler(context, cancellationToken); + } + + /// + /// Gets the handler for the `List` prompt requests. + /// + /// Context of the request. + /// The cancellation token. + /// The result of the `List` prompt request. + public static Task GetHandlerForListPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + { + return Task.FromResult(new ListPromptsResult + { + Prompts = [.. s_definitions.Values.Select(d => d.Prompt)] + }); + } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Tools/DateTimeUtils.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Tools/DateTimeUtils.cs index 80fe8affa525..8b74bb6b491f 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Tools/DateTimeUtils.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Tools/DateTimeUtils.cs @@ -17,6 +17,6 @@ internal sealed class DateTimeUtils [KernelFunction, Description("Retrieves the current date time in UTC.")] public static string GetCurrentDateTimeInUtc() { - return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + return DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"); } } From f86b6f460ba52c0e801256f4d9ae4216929854ea Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 18:14:33 +0100 Subject: [PATCH 2/8] add xml comment --- .../MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs index 3bb3fff1edb0..8e4d680afeec 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs @@ -12,6 +12,11 @@ namespace MCPServer.Prompts; /// internal static class GetCurrentWeatherForCityPrompt { + /// + /// The SK handlebars prompt template is embedded in the class for the convenience and simplicity of the demo. + /// For non-demo scenarios, the JSON prompt template can be stored in a separate file so it can be versioned and updated independently. + /// The GetCurrentWeatherForCityPrompt class itself can be generalized to use with any prompt template and accept the template name as a parameter. + /// private static readonly PromptTemplateConfig s_promptTemplateConfig = PromptTemplateConfig.FromJson(""" { "name": "GetCurrentWeatherForCity", From fc4c150fd9eae01f681fa2193345ecc90ff10d34 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 18:36:21 +0100 Subject: [PATCH 3/8] make GetCurrentWeatherForCityPrompt more generic --- .../MCPServer/MCPServer.csproj | 8 +++ .../MCPServer/Program.cs | 2 +- ...eatherForCityPrompt.cs => KernelPrompt.cs} | 60 ++++++++----------- .../MCPServer/Prompts/PromptResource.cs | 32 ++++++++++ .../Prompts/getCurrentWeatherForCity.json | 13 ++++ 5 files changed, 78 insertions(+), 37 deletions(-) rename dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/{GetCurrentWeatherForCityPrompt.cs => KernelPrompt.cs} (53%) create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs create mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/getCurrentWeatherForCity.json diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj index c5c5c611b4f9..7f16c2ffb74b 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj @@ -7,6 +7,14 @@ $(NoWarn);VSTHRD111;CA2007;SKEXP0001 + + + + + + + + diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs index b73ba54d9a67..89c81dceabc3 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs @@ -14,7 +14,7 @@ Kernel kernel = kernelBuilder.Build(); // Register prompts -PromptRegistry.RegisterPrompt(GetCurrentWeatherForCityPrompt.GetDefinition(kernel)); +PromptRegistry.RegisterPrompt(KernelPrompt.GetDefinition(PromptResource.ReadAsString("getCurrentWeatherForCity.json"), kernel)); var builder = Host.CreateEmptyApplicationBuilder(settings: null); builder.Services diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs similarity index 53% rename from dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs rename to dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs index 8e4d680afeec..ec949643c370 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/GetCurrentWeatherForCityPrompt.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs @@ -8,57 +8,43 @@ namespace MCPServer.Prompts; /// -/// Represents the GetCurrentWeatherForCity prompt. +/// Represents a kernel prompt. /// -internal static class GetCurrentWeatherForCityPrompt +internal static class KernelPrompt { - /// - /// The SK handlebars prompt template is embedded in the class for the convenience and simplicity of the demo. - /// For non-demo scenarios, the JSON prompt template can be stored in a separate file so it can be versioned and updated independently. - /// The GetCurrentWeatherForCityPrompt class itself can be generalized to use with any prompt template and accept the template name as a parameter. - /// - private static readonly PromptTemplateConfig s_promptTemplateConfig = PromptTemplateConfig.FromJson(""" - { - "name": "GetCurrentWeatherForCity", - "description": "Provides current weather information for a specified city.", - "template_format": "handlebars", - "template": "What is the weather in {{city}} as of {{DateTimeUtils-GetCurrentDateTimeInUtc}}?", - "input_variables": [ - { - "name": "city", - "description": "The city for which to get the weather.", - "is_required": true - } - ] - } - """); - - private static readonly IPromptTemplate s_promptTemplate = new HandlebarsPromptTemplateFactory().Create(s_promptTemplateConfig); - /// /// Gets this prompt definition. /// + /// The JSON prompt template. /// An instance of the kernel to render the prompt. /// The prompt definition. - public static PromptDefinition GetDefinition(Kernel kernel) + public static PromptDefinition GetDefinition(string jsonPrompt, Kernel kernel) { - return new() + PromptTemplateConfig promptTemplateConfig = PromptTemplateConfig.FromJson(jsonPrompt); + + return new PromptDefinition() { - Prompt = GetPrompt(), - Handler = (context, cancellationToken) => GetPromptHandlerAsync(context, kernel, cancellationToken) + Prompt = GetPrompt(promptTemplateConfig), + Handler = (context, cancellationToken) => + { + IPromptTemplate promptTemplate = new HandlebarsPromptTemplateFactory().Create(promptTemplateConfig); + + return GetPromptHandlerAsync(context, promptTemplateConfig, promptTemplate, kernel, cancellationToken); + } }; } /// /// Creates an MCP prompt from SK prompt template. /// + /// The prompt template configuration. /// The MCP prompt. - private static Prompt GetPrompt() + private static Prompt GetPrompt(PromptTemplateConfig promptTemplateConfig) { // Create the MCP prompt arguments List? arguments = null; - foreach (var inputVariable in s_promptTemplateConfig.InputVariables) + foreach (var inputVariable in promptTemplateConfig.InputVariables) { (arguments ??= []).Add(new() { @@ -71,8 +57,8 @@ private static Prompt GetPrompt() // Create the MCP prompt return new Prompt { - Name = s_promptTemplateConfig.Name!, - Description = s_promptTemplateConfig.Description, + Name = promptTemplateConfig.Name!, + Description = promptTemplateConfig.Description, Arguments = arguments }; } @@ -81,13 +67,15 @@ private static Prompt GetPrompt() /// Handles the prompt request by rendering the prompt. /// /// The prompt request context. + /// The prompt template configuration. + /// The prompt template. /// The kernel to render the prompt. /// The cancellation token. /// The prompt. - private static async Task GetPromptHandlerAsync(RequestContext context, Kernel kernel, CancellationToken cancellationToken) + private static async Task GetPromptHandlerAsync(RequestContext context, PromptTemplateConfig promptTemplateConfig, IPromptTemplate promptTemplate, Kernel kernel, CancellationToken cancellationToken) { // Render the prompt - string renderedPrompt = await s_promptTemplate.RenderAsync( + string renderedPrompt = await promptTemplate.RenderAsync( kernel: kernel, arguments: context.Params?.Arguments is { } args ? new KernelArguments(args!) : null, cancellationToken: cancellationToken); @@ -95,7 +83,7 @@ private static async Task GetPromptHandlerAsync(RequestContext< // Create prompt result return new GetPromptResult() { - Description = s_promptTemplateConfig.Description, + Description = promptTemplateConfig.Description, Messages = [ new PromptMessage() diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs new file mode 100644 index 000000000000..00eb0803d8c3 --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Reflection; + +namespace MCPServer.Prompts; + +/// +/// Reads embedded prompt from resources. +/// +public static class PromptResource +{ + private static readonly string? s_namespace = typeof(PromptResource).Namespace; + + internal static string ReadAsString(string fileName) + { + // Get the current assembly. Note: this class is in the same assembly where the embedded resources are stored. + Assembly assembly = + typeof(PromptResource).GetTypeInfo().Assembly ?? + throw new InvalidOperationException($"[{s_namespace}] {fileName} assembly not found"); + + // Resources are mapped like types, using the namespace and appending "." (dot) and the file name + string resourceName = $"{s_namespace}." + fileName; + + Stream stream = + assembly.GetManifestResourceStream(resourceName) ?? + throw new InvalidOperationException($"{resourceName} resource not found"); + + // Return the resource content, in text format. + using StreamReader reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/getCurrentWeatherForCity.json b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/getCurrentWeatherForCity.json new file mode 100644 index 000000000000..56ec934468d9 --- /dev/null +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/getCurrentWeatherForCity.json @@ -0,0 +1,13 @@ +{ + "name": "GetCurrentWeatherForCity", + "description": "Provides current weather information for a specified city.", + "template_format": "handlebars", + "template": "What is the weather in {{city}} as of {{DateTimeUtils-GetCurrentDateTimeInUtc}}?", + "input_variables": [ + { + "name": "city", + "description": "The city for which to get the weather.", + "is_required": true + } + ] +} \ No newline at end of file From f371ebd24daf10a4b72ae2bd2d5acf74d20be684 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 18:42:00 +0100 Subject: [PATCH 4/8] simplify sample --- .../MCPServer/Program.cs | 2 +- .../MCPServer/Prompts/KernelPrompt.cs | 101 ------------------ .../MCPServer/Prompts/PromptDefinition.cs | 89 +++++++++++++++ 3 files changed, 90 insertions(+), 102 deletions(-) delete mode 100644 dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs index 89c81dceabc3..bd5280f1aa10 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs @@ -14,7 +14,7 @@ Kernel kernel = kernelBuilder.Build(); // Register prompts -PromptRegistry.RegisterPrompt(KernelPrompt.GetDefinition(PromptResource.ReadAsString("getCurrentWeatherForCity.json"), kernel)); +PromptRegistry.RegisterPrompt(PromptDefinition.Create(PromptResource.ReadAsString("getCurrentWeatherForCity.json"), kernel)); var builder = Host.CreateEmptyApplicationBuilder(settings: null); builder.Services diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs deleted file mode 100644 index ec949643c370..000000000000 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/KernelPrompt.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.PromptTemplates.Handlebars; -using ModelContextProtocol.Protocol.Types; -using ModelContextProtocol.Server; - -namespace MCPServer.Prompts; - -/// -/// Represents a kernel prompt. -/// -internal static class KernelPrompt -{ - /// - /// Gets this prompt definition. - /// - /// The JSON prompt template. - /// An instance of the kernel to render the prompt. - /// The prompt definition. - public static PromptDefinition GetDefinition(string jsonPrompt, Kernel kernel) - { - PromptTemplateConfig promptTemplateConfig = PromptTemplateConfig.FromJson(jsonPrompt); - - return new PromptDefinition() - { - Prompt = GetPrompt(promptTemplateConfig), - Handler = (context, cancellationToken) => - { - IPromptTemplate promptTemplate = new HandlebarsPromptTemplateFactory().Create(promptTemplateConfig); - - return GetPromptHandlerAsync(context, promptTemplateConfig, promptTemplate, kernel, cancellationToken); - } - }; - } - - /// - /// Creates an MCP prompt from SK prompt template. - /// - /// The prompt template configuration. - /// The MCP prompt. - private static Prompt GetPrompt(PromptTemplateConfig promptTemplateConfig) - { - // Create the MCP prompt arguments - List? arguments = null; - - foreach (var inputVariable in promptTemplateConfig.InputVariables) - { - (arguments ??= []).Add(new() - { - Name = inputVariable.Name, - Description = inputVariable.Description, - Required = inputVariable.IsRequired - }); - } - - // Create the MCP prompt - return new Prompt - { - Name = promptTemplateConfig.Name!, - Description = promptTemplateConfig.Description, - Arguments = arguments - }; - } - - /// - /// Handles the prompt request by rendering the prompt. - /// - /// The prompt request context. - /// The prompt template configuration. - /// The prompt template. - /// The kernel to render the prompt. - /// The cancellation token. - /// The prompt. - private static async Task GetPromptHandlerAsync(RequestContext context, PromptTemplateConfig promptTemplateConfig, IPromptTemplate promptTemplate, Kernel kernel, CancellationToken cancellationToken) - { - // Render the prompt - string renderedPrompt = await promptTemplate.RenderAsync( - kernel: kernel, - arguments: context.Params?.Arguments is { } args ? new KernelArguments(args!) : null, - cancellationToken: cancellationToken); - - // Create prompt result - return new GetPromptResult() - { - Description = promptTemplateConfig.Description, - Messages = - [ - new PromptMessage() - { - Content = new Content() - { - Type = "text", - Text = renderedPrompt - }, - Role = Role.User - } - ] - }; - } -} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs index d4c1d410b9fa..c3ab91c00ca1 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.SemanticKernel; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; +using Microsoft.SemanticKernel.PromptTemplates.Handlebars; namespace MCPServer.Prompts; @@ -19,4 +21,91 @@ internal class PromptDefinition /// Gets or sets the handler for the prompt. /// public required Func, CancellationToken, Task> Handler { get; init; } + + /// + /// Gets this prompt definition. + /// + /// The JSON prompt template. + /// An instance of the kernel to render the prompt. + /// The prompt definition. + public static PromptDefinition Create(string jsonPrompt, Kernel kernel) + { + PromptTemplateConfig promptTemplateConfig = PromptTemplateConfig.FromJson(jsonPrompt); + + return new PromptDefinition() + { + Prompt = GetPrompt(promptTemplateConfig), + Handler = (context, cancellationToken) => + { + IPromptTemplate promptTemplate = new HandlebarsPromptTemplateFactory().Create(promptTemplateConfig); + + return GetPromptHandlerAsync(context, promptTemplateConfig, promptTemplate, kernel, cancellationToken); + } + }; + } + + /// + /// Creates an MCP prompt from SK prompt template. + /// + /// The prompt template configuration. + /// The MCP prompt. + private static Prompt GetPrompt(PromptTemplateConfig promptTemplateConfig) + { + // Create the MCP prompt arguments + List? arguments = null; + + foreach (var inputVariable in promptTemplateConfig.InputVariables) + { + (arguments ??= []).Add(new() + { + Name = inputVariable.Name, + Description = inputVariable.Description, + Required = inputVariable.IsRequired + }); + } + + // Create the MCP prompt + return new Prompt + { + Name = promptTemplateConfig.Name!, + Description = promptTemplateConfig.Description, + Arguments = arguments + }; + } + + /// + /// Handles the prompt request by rendering the prompt. + /// + /// The prompt request context. + /// The prompt template configuration. + /// The prompt template. + /// The kernel to render the prompt. + /// The cancellation token. + /// The prompt. + private static async Task GetPromptHandlerAsync(RequestContext context, PromptTemplateConfig promptTemplateConfig, IPromptTemplate promptTemplate, Kernel kernel, CancellationToken cancellationToken) + { + // Render the prompt + string renderedPrompt = await promptTemplate.RenderAsync( + kernel: kernel, + arguments: context.Params?.Arguments is { } args ? new KernelArguments(args!) : null, + cancellationToken: cancellationToken); + + // Create prompt result + return new GetPromptResult() + { + Description = promptTemplateConfig.Description, + Messages = + [ + new PromptMessage() + { + Content = new Content() + { + Type = "text", + Text = renderedPrompt + }, + Role = Role.User + } + ] + }; + } } From e136e5cd3645c3ee3d14d358ace8983096813342 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 18:44:28 +0100 Subject: [PATCH 5/8] rename PromptResource --- .../ModelContextProtocolClientServer/MCPServer/Program.cs | 2 +- .../Prompts/{PromptResource.cs => EmbeddedResource.cs} | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/{PromptResource.cs => EmbeddedResource.cs} (81%) diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs index bd5280f1aa10..0d1db1f9b482 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs @@ -14,7 +14,7 @@ Kernel kernel = kernelBuilder.Build(); // Register prompts -PromptRegistry.RegisterPrompt(PromptDefinition.Create(PromptResource.ReadAsString("getCurrentWeatherForCity.json"), kernel)); +PromptRegistry.RegisterPrompt(PromptDefinition.Create(EmbeddedResource.ReadAsString("getCurrentWeatherForCity.json"), kernel)); var builder = Host.CreateEmptyApplicationBuilder(settings: null); builder.Services diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs similarity index 81% rename from dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs rename to dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs index 00eb0803d8c3..d5cb1c2c8fa7 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptResource.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs @@ -5,17 +5,17 @@ namespace MCPServer.Prompts; /// -/// Reads embedded prompt from resources. +/// Reads embedded resources. /// -public static class PromptResource +public static class EmbeddedResource { - private static readonly string? s_namespace = typeof(PromptResource).Namespace; + private static readonly string? s_namespace = typeof(EmbeddedResource).Namespace; internal static string ReadAsString(string fileName) { // Get the current assembly. Note: this class is in the same assembly where the embedded resources are stored. Assembly assembly = - typeof(PromptResource).GetTypeInfo().Assembly ?? + typeof(EmbeddedResource).GetTypeInfo().Assembly ?? throw new InvalidOperationException($"[{s_namespace}] {fileName} assembly not found"); // Resources are mapped like types, using the namespace and appending "." (dot) and the file name From c2df5bfb47f7308946781e1af8f0ab63a1727c4c Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 18:47:53 +0100 Subject: [PATCH 6/8] fix warnings --- .../MCPServer/Prompts/EmbeddedResource.cs | 2 +- .../MCPServer/Prompts/PromptDefinition.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs index d5cb1c2c8fa7..787e15731172 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs @@ -26,7 +26,7 @@ internal static string ReadAsString(string fileName) throw new InvalidOperationException($"{resourceName} resource not found"); // Return the resource content, in text format. - using StreamReader reader = new StreamReader(stream); + using StreamReader reader = new(stream); return reader.ReadToEnd(); } } diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs index c3ab91c00ca1..5e6f26d16688 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.PromptTemplates.Handlebars; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; -using Microsoft.SemanticKernel.PromptTemplates.Handlebars; namespace MCPServer.Prompts; /// /// Represents a prompt definition. /// -internal class PromptDefinition +internal sealed class PromptDefinition { /// /// Gets or sets the prompt. From c5f76d96bd2670518d79a34e12748f4013fb94a4 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:50:15 +0100 Subject: [PATCH 7/8] Update dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs Co-authored-by: westey <164392973+westey-m@users.noreply.github.com> --- .../MCPServer/Prompts/EmbeddedResource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs index 787e15731172..e694af06529a 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs @@ -19,7 +19,7 @@ internal static string ReadAsString(string fileName) throw new InvalidOperationException($"[{s_namespace}] {fileName} assembly not found"); // Resources are mapped like types, using the namespace and appending "." (dot) and the file name - string resourceName = $"{s_namespace}." + fileName; + string resourceName = $"{s_namespace}.{fileName}"; Stream stream = assembly.GetManifestResourceStream(resourceName) ?? From 95bd10c7a26fee862a21d9cbef453d221168ec0c Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Wed, 2 Apr 2025 21:00:22 +0100 Subject: [PATCH 8/8] Address PR review comments --- .../MCPServer/Program.cs | 4 ++-- .../MCPServer/Prompts/PromptDefinition.cs | 2 +- .../MCPServer/Prompts/PromptRegistry.cs | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs index 0d1db1f9b482..abd3a079d1e5 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs @@ -23,6 +23,6 @@ // Add all functions from the kernel plugins to the MCP server as tools .WithTools(kernel.Plugins) // Register prompt handlers - .WithListPromptsHandler(PromptRegistry.GetHandlerForListPromptRequestsAsync) - .WithGetPromptHandler(PromptRegistry.GetHandlerForGetPromptRequestsAsync); + .WithListPromptsHandler(PromptRegistry.HandlerListPromptRequestsAsync) + .WithGetPromptHandler(PromptRegistry.HandlerGetPromptRequestsAsync); await builder.Build().RunAsync(); diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs index 5e6f26d16688..f5a5e0d7959e 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs @@ -76,7 +76,7 @@ private static Prompt GetPrompt(PromptTemplateConfig promptTemplateConfig) /// /// Handles the prompt request by rendering the prompt. /// - /// The prompt request context. + /// The MCP request context. /// The prompt template configuration. /// The prompt template. /// The kernel to render the prompt. diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs index 4599caaf2ec4..062983dab3ba 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs @@ -18,16 +18,21 @@ internal static class PromptRegistry /// The prompt definition to register. public static void RegisterPrompt(PromptDefinition definition) { + if (s_definitions.ContainsKey(definition.Prompt.Name)) + { + throw new ArgumentException($"A prompt with the name '{definition.Prompt.Name}' is already registered."); + } + s_definitions[definition.Prompt.Name] = definition; } /// - /// Gets the handler for the `Get` prompt requests. + /// Handles the `Get` prompt requests. /// /// The request context. /// The cancellation token. /// The result of the `Get` prompt request. - public static async Task GetHandlerForGetPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + public static async Task HandlerGetPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) { // Make sure the prompt name is provided if (context.Params?.Name is not string { } promptName || string.IsNullOrEmpty(promptName)) @@ -46,12 +51,12 @@ public static async Task GetHandlerForGetPromptRequestsAsync(Re } /// - /// Gets the handler for the `List` prompt requests. + /// Handles the `List` prompt requests. /// /// Context of the request. /// The cancellation token. /// The result of the `List` prompt request. - public static Task GetHandlerForListPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + public static Task HandlerListPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) { return Task.FromResult(new ListPromptsResult {