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..7f16c2ffb74b 100644
--- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/MCPServer.csproj
@@ -7,12 +7,21 @@
$(NoWarn);VSTHRD111;CA2007;SKEXP0001
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs
index 79aa59a5a180..abd3a079d1e5 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(PromptDefinition.Create(EmbeddedResource.ReadAsString("getCurrentWeatherForCity.json"), 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.HandlerListPromptRequestsAsync)
+ .WithGetPromptHandler(PromptRegistry.HandlerGetPromptRequestsAsync);
await builder.Build().RunAsync();
diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs
new file mode 100644
index 000000000000..e694af06529a
--- /dev/null
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/EmbeddedResource.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Reflection;
+
+namespace MCPServer.Prompts;
+
+///
+/// Reads embedded resources.
+///
+public static class EmbeddedResource
+{
+ 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(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
+ 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(stream);
+ return reader.ReadToEnd();
+ }
+}
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..f5a5e0d7959e
--- /dev/null
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptDefinition.cs
@@ -0,0 +1,111 @@
+// 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 prompt definition.
+///
+internal sealed 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; }
+
+ ///
+ /// 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 MCP 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/PromptRegistry.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs
new file mode 100644
index 000000000000..062983dab3ba
--- /dev/null
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs
@@ -0,0 +1,66 @@
+// 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)
+ {
+ 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;
+ }
+
+ ///
+ /// Handles the `Get` prompt requests.
+ ///
+ /// The request context.
+ /// The cancellation token.
+ /// The result of the `Get` prompt request.
+ 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))
+ {
+ 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);
+ }
+
+ ///
+ /// Handles the `List` prompt requests.
+ ///
+ /// Context of the request.
+ /// The cancellation token.
+ /// The result of the `List` prompt request.
+ public static Task HandlerListPromptRequestsAsync(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/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
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");
}
}