Skip to content

.Net: Create a file/class per MCP client sample #11620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using ModelContextProtocol.Client;

namespace MCPClient.Samples;

/// <summary>
/// Demonstrates how to use SK agent available as MCP tool.
/// </summary>
internal sealed class AgentAvailableAsMCPToolSample : BaseSample
{
/// <summary>
/// Demonstrates how to use SK agent available as MCP tool.
/// 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 the `Agents_SalesAssistant` function, which calls the MCP tool that calls the SK agent on the server.
/// 6. The agent calls the `OrderProcessingUtils-PlaceOrder` function to place the order for the `Grande Mug`.
/// 7. The agent calls the `OrderProcessingUtils-ReturnOrder` function to return the `Wide Rim Mug`.
/// 8. The agent summarizes the transactions and returns the result as part of the `Agents_SalesAssistant` function call.
/// 9. Having received the result from the `Agents_SalesAssistant`, the AI model returns the answer to the prompt.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine($"Running the {nameof(AgentAvailableAsMCPToolSample)} sample.");

// Create an MCP client
await using IMcpClient mcpClient = await CreateMcpClientAsync();

// Retrieve and display the list provided by the MCP server
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
DisplayTools(tools);

// 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 })
};

string prompt = "I'd like to order the 'Grande Mug' and return the 'Wide Rim Mug' bought last week.";
Console.WriteLine(prompt);

// Execute a prompt using the MCP tools. The AI model will automatically call the appropriate MCP tools to answer the prompt.
FunctionResult result = await kernel.InvokePromptAsync(prompt, new(executionSettings));

Console.WriteLine(result);
Console.WriteLine();

// The expected output is: The order for the "Grande Mug" has been successfully placed.
// Additionally, the return process for the "Wide Rim Mug" has been successfully initiated.
// If you have any further questions or need assistance with anything else, feel free to ask!
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using ModelContextProtocol.Client;

namespace MCPClient.Samples;

/// <summary>
/// Demonstrates how to use <see cref="AzureAIAgent"/> with MCP tools represented as Kernel functions.
/// </summary>
internal sealed class AzureAIAgentWithMCPToolsSample : BaseSample
{
/// <summary>
/// Demonstrates how to use <see cref="AzureAIAgent"/> with MCP tools represented as Kernel functions.
/// 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. Defines Azure AI agent with instructions, name, kernel, and arguments.
/// 5. Invokes the agent with a prompt.
/// 6. The agent sends the prompt to the AI model, together with the MCP tools represented as Kernel functions.
/// 7. The AI model calls DateTimeUtils-GetCurrentDateTimeInUtc function to get the current date time in UTC required as an argument for the next function.
/// 8. 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.
/// 9. Having received the weather information from the function call, the AI model returns the answer to the agent and the agent returns the answer to the user.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine($"Running the {nameof(AzureAIAgentWithMCPToolsSample)} sample.");

// Create an MCP client
await using IMcpClient mcpClient = await CreateMcpClientAsync();

// Retrieve and display the list provided by the MCP server
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
DisplayTools(tools);

// Create a kernel and register the MCP tools as Kernel functions
Kernel kernel = new();
kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));

// Define the agent using the kernel with registered MCP tools
AzureAIAgent agent = await CreateAzureAIAgentAsync(
name: "WeatherAgent",
instructions: "Answer questions about the weather.",
kernel: kernel
);

// Invokes agent with a prompt
string prompt = "What is the likely color of the sky in Boston today?";
Console.WriteLine(prompt);

AgentResponseItem<ChatMessageContent> response = await agent.InvokeAsync(message: prompt).FirstAsync();
Console.WriteLine(response.Message);
Console.WriteLine();

// The expected output is: Today in Boston, the weather is 61°F and rainy. Due to the rain, the likely color of the sky will be gray.

// Delete the agent thread after use
await response!.Thread.DeleteAsync();

// Delete the agent after use
await agent.Client.DeleteAgentAsync(agent.Id);
}

/// <summary>
/// Creates an instance of <see cref="AzureAIAgent"/> with the specified name and instructions.
/// </summary>
/// <param name="kernel">The kernel instance.</param>
/// <param name="name">The name of the agent.</param>
/// <param name="instructions">The instructions for the agent.</param>
/// <returns>An instance of <see cref="AzureAIAgent"/>.</returns>
private static async Task<AzureAIAgent> CreateAzureAIAgentAsync(Kernel kernel, string name, string instructions)
{
// Load and validate configuration
IConfigurationRoot config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();

if (config["AzureAI:ConnectionString"] is not { } connectionString)
{
const string Message = "Please provide a valid `AzureAI:ConnectionString` secret to run this sample. See the associated README.md for more details.";
Console.Error.WriteLine(Message);
throw new InvalidOperationException(Message);
}

string modelId = config["AzureAI:ChatModelId"] ?? "gpt-4o-mini";

// Create the Azure AI Agent
AIProjectClient projectClient = AzureAIAgent.CreateAzureAIClient(connectionString, new AzureCliCredential());

AgentsClient agentsClient = projectClient.GetAgentsClient();

Azure.AI.Projects.Agent agent = await agentsClient.CreateAgentAsync(modelId, name, null, instructions);

return new AzureAIAgent(agent, agentsClient)
{
Kernel = kernel
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;

namespace MCPClient.Samples;

internal class BaseSample
{
/// <summary>
/// Creates an MCP client and connects it to the MCPServer server.
/// </summary>
/// <param name="kernel">Optional kernel instance to use for the MCP client.</param>
/// <param name="samplingRequestHandler">Optional handler for MCP sampling requests.</param>
/// <returns>An instance of <see cref="IMcpClient"/>.</returns>
protected static Task<IMcpClient> CreateMcpClientAsync(
Kernel? kernel = null,
Func<Kernel, CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, Task<CreateMessageResult>>? samplingRequestHandler = null)
{
KernelFunction? skSamplingHandler = null;

// Create and return the MCP client
return McpClientFactory.CreateAsync(
clientTransport: new StdioClientTransport(new StdioClientTransportOptions
{
Name = "MCPServer",
Command = GetMCPServerPath(), // Path to the MCPServer executable
}),
clientOptions: samplingRequestHandler != null ? new McpClientOptions()
{
Capabilities = new ClientCapabilities
{
Sampling = new SamplingCapability
{
SamplingHandler = InvokeHandlerAsync
},
},
} : null
);

async ValueTask<CreateMessageResult> InvokeHandlerAsync(CreateMessageRequestParams? request, IProgress<ProgressNotificationValue> progress, CancellationToken cancellationToken)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

skSamplingHandler ??= KernelFunctionFactory.CreateFromMethod(
(CreateMessageRequestParams? request, IProgress<ProgressNotificationValue> progress, CancellationToken ct) =>
{
return samplingRequestHandler(kernel!, request, progress, ct);
},
"MCPSamplingHandler"
);

// The argument names must match the parameter names of the delegate the SK Function is created from
KernelArguments kernelArguments = new()
{
["request"] = request,
["progress"] = progress
};

FunctionResult functionResult = await skSamplingHandler.InvokeAsync(kernel!, kernelArguments, cancellationToken);

return functionResult.GetValue<CreateMessageResult>()!;
}
}

/// <summary>
/// Creates an instance of <see cref="Kernel"/> with the OpenAI chat completion service registered.
/// </summary>
/// <returns>An instance of <see cref="Kernel"/>.</returns>
protected static Kernel CreateKernelWithChatCompletionService()
{
// Load and validate configuration
IConfigurationRoot config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();

if (config["OpenAI:ApiKey"] is not { } apiKey)
{
const string Message = "Please provide a valid OpenAI:ApiKey to run this sample. See the associated README.md for more details.";
Console.Error.WriteLine(Message);
throw new InvalidOperationException(Message);
}

string modelId = config["OpenAI:ChatModelId"] ?? "gpt-4o-mini";

// Create kernel
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddOpenAIChatCompletion(modelId: modelId, apiKey: apiKey);

return kernelBuilder.Build();
}

/// <summary>
/// Displays the list of available MCP tools.
/// </summary>
/// <param name="tools">The list of the tools to display.</param>
protected static void DisplayTools(IList<McpClientTool> tools)
{
Console.WriteLine("Available MCP tools:");
foreach (var tool in tools)
{
Console.WriteLine($"- Name: {tool.Name}, Description: {tool.Description}");
}
Console.WriteLine();
}

/// <summary>
/// Returns the path to the MCPServer server executable.
/// </summary>
/// <returns>The path to the MCPServer server executable.</returns>
private static string GetMCPServerPath()
{
// Determine the configuration (Debug or Release)
string configuration;

#if DEBUG
configuration = "Debug";
#else
configuration = "Release";
#endif

return Path.Combine("..", "..", "..", "..", "MCPServer", "bin", configuration, "net8.0", "MCPServer.exe");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using ModelContextProtocol.Client;

namespace MCPClient.Samples;

/// <summary>
/// Demonstrates how to use <see cref="ChatCompletionAgent"/> with MCP tools represented as Kernel functions.
/// </summary>
internal sealed class ChatCompletionAgentWithMCPToolsSample : BaseSample
{
/// <summary>
/// Demonstrates how to use <see cref="ChatCompletionAgent"/> with MCP tools represented as Kernel functions.
/// 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. Defines chat completion agent with instructions, name, kernel, and arguments.
/// 5. Invokes the agent with a prompt.
/// 6. The agent sends the prompt to the AI model, together with the MCP tools represented as Kernel functions.
/// 7. The AI model calls DateTimeUtils-GetCurrentDateTimeInUtc function to get the current date time in UTC required as an argument for the next function.
/// 8. 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.
/// 9. Having received the weather information from the function call, the AI model returns the answer to the agent and the agent returns the answer to the user.
/// </summary>
public static async Task RunAsync()
{
Console.WriteLine($"Running the {nameof(ChatCompletionAgentWithMCPToolsSample)} sample.");

// Create an MCP client
await using IMcpClient mcpClient = await CreateMcpClientAsync();

// Retrieve and display the list provided by the MCP server
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
DisplayTools(tools);

// Create a kernel and register the MCP tools as kernel functions
Kernel kernel = CreateKernelWithChatCompletionService();
kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));

// Enable automatic function calling
OpenAIPromptExecutionSettings executionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
};

string prompt = "What is the likely color of the sky in Boston today?";
Console.WriteLine(prompt);

// Define the agent
ChatCompletionAgent agent = new()
{
Instructions = "Answer questions about the weather.",
Name = "WeatherAgent",
Kernel = kernel,
Arguments = new KernelArguments(executionSettings),
};

// Invokes agent with a prompt
ChatMessageContent response = await agent.InvokeAsync(prompt).FirstAsync();

Console.WriteLine(response);
Console.WriteLine();

// The expected output is: The sky in Boston today is likely gray due to rainy weather.
}
}
Loading
Loading