Skip to content

Commit 9b44b1c

Browse files
.Net: Create a file/class per MCP client sample (#11620)
### Motivation and Context This PR moves MCP client samples into their own files/classes, as pointed out in one of the previous PR review comments. No other changes affecting the logic or functionality of the code have been made. Contributes to: #11463
1 parent 79a29c0 commit 9b44b1c

File tree

10 files changed

+833
-666
lines changed

10 files changed

+833
-666
lines changed

dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Program.cs

Lines changed: 11 additions & 666 deletions
Large diffs are not rendered by default.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.SemanticKernel;
8+
using Microsoft.SemanticKernel.Connectors.OpenAI;
9+
using ModelContextProtocol.Client;
10+
11+
namespace MCPClient.Samples;
12+
13+
/// <summary>
14+
/// Demonstrates how to use SK agent available as MCP tool.
15+
/// </summary>
16+
internal sealed class AgentAvailableAsMCPToolSample : BaseSample
17+
{
18+
/// <summary>
19+
/// Demonstrates how to use SK agent available as MCP tool.
20+
/// The code in this method:
21+
/// 1. Creates an MCP client.
22+
/// 2. Retrieves the list of tools provided by the MCP server.
23+
/// 3. Creates a kernel and registers the MCP tools as Kernel functions.
24+
/// 4. Sends the prompt to AI model together with the MCP tools represented as Kernel functions.
25+
/// 5. The AI model calls the `Agents_SalesAssistant` function, which calls the MCP tool that calls the SK agent on the server.
26+
/// 6. The agent calls the `OrderProcessingUtils-PlaceOrder` function to place the order for the `Grande Mug`.
27+
/// 7. The agent calls the `OrderProcessingUtils-ReturnOrder` function to return the `Wide Rim Mug`.
28+
/// 8. The agent summarizes the transactions and returns the result as part of the `Agents_SalesAssistant` function call.
29+
/// 9. Having received the result from the `Agents_SalesAssistant`, the AI model returns the answer to the prompt.
30+
/// </summary>
31+
public static async Task RunAsync()
32+
{
33+
Console.WriteLine($"Running the {nameof(AgentAvailableAsMCPToolSample)} sample.");
34+
35+
// Create an MCP client
36+
await using IMcpClient mcpClient = await CreateMcpClientAsync();
37+
38+
// Retrieve and display the list provided by the MCP server
39+
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
40+
DisplayTools(tools);
41+
42+
// Create a kernel and register the MCP tools
43+
Kernel kernel = CreateKernelWithChatCompletionService();
44+
kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
45+
46+
// Enable automatic function calling
47+
OpenAIPromptExecutionSettings executionSettings = new()
48+
{
49+
Temperature = 0,
50+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
51+
};
52+
53+
string prompt = "I'd like to order the 'Grande Mug' and return the 'Wide Rim Mug' bought last week.";
54+
Console.WriteLine(prompt);
55+
56+
// Execute a prompt using the MCP tools. The AI model will automatically call the appropriate MCP tools to answer the prompt.
57+
FunctionResult result = await kernel.InvokePromptAsync(prompt, new(executionSettings));
58+
59+
Console.WriteLine(result);
60+
Console.WriteLine();
61+
62+
// The expected output is: The order for the "Grande Mug" has been successfully placed.
63+
// Additionally, the return process for the "Wide Rim Mug" has been successfully initiated.
64+
// If you have any further questions or need assistance with anything else, feel free to ask!
65+
}
66+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Azure.AI.Projects;
8+
using Azure.Identity;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.SemanticKernel;
11+
using Microsoft.SemanticKernel.Agents;
12+
using Microsoft.SemanticKernel.Agents.AzureAI;
13+
using ModelContextProtocol.Client;
14+
15+
namespace MCPClient.Samples;
16+
17+
/// <summary>
18+
/// Demonstrates how to use <see cref="AzureAIAgent"/> with MCP tools represented as Kernel functions.
19+
/// </summary>
20+
internal sealed class AzureAIAgentWithMCPToolsSample : BaseSample
21+
{
22+
/// <summary>
23+
/// Demonstrates how to use <see cref="AzureAIAgent"/> with MCP tools represented as Kernel functions.
24+
/// The code in this method:
25+
/// 1. Creates an MCP client.
26+
/// 2. Retrieves the list of tools provided by the MCP server.
27+
/// 3. Creates a kernel and registers the MCP tools as Kernel functions.
28+
/// 4. Defines Azure AI agent with instructions, name, kernel, and arguments.
29+
/// 5. Invokes the agent with a prompt.
30+
/// 6. The agent sends the prompt to the AI model, together with the MCP tools represented as Kernel functions.
31+
/// 7. The AI model calls DateTimeUtils-GetCurrentDateTimeInUtc function to get the current date time in UTC required as an argument for the next function.
32+
/// 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.
33+
/// 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.
34+
/// </summary>
35+
public static async Task RunAsync()
36+
{
37+
Console.WriteLine($"Running the {nameof(AzureAIAgentWithMCPToolsSample)} sample.");
38+
39+
// Create an MCP client
40+
await using IMcpClient mcpClient = await CreateMcpClientAsync();
41+
42+
// Retrieve and display the list provided by the MCP server
43+
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
44+
DisplayTools(tools);
45+
46+
// Create a kernel and register the MCP tools as Kernel functions
47+
Kernel kernel = new();
48+
kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
49+
50+
// Define the agent using the kernel with registered MCP tools
51+
AzureAIAgent agent = await CreateAzureAIAgentAsync(
52+
name: "WeatherAgent",
53+
instructions: "Answer questions about the weather.",
54+
kernel: kernel
55+
);
56+
57+
// Invokes agent with a prompt
58+
string prompt = "What is the likely color of the sky in Boston today?";
59+
Console.WriteLine(prompt);
60+
61+
AgentResponseItem<ChatMessageContent> response = await agent.InvokeAsync(message: prompt).FirstAsync();
62+
Console.WriteLine(response.Message);
63+
Console.WriteLine();
64+
65+
// 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.
66+
67+
// Delete the agent thread after use
68+
await response!.Thread.DeleteAsync();
69+
70+
// Delete the agent after use
71+
await agent.Client.DeleteAgentAsync(agent.Id);
72+
}
73+
74+
/// <summary>
75+
/// Creates an instance of <see cref="AzureAIAgent"/> with the specified name and instructions.
76+
/// </summary>
77+
/// <param name="kernel">The kernel instance.</param>
78+
/// <param name="name">The name of the agent.</param>
79+
/// <param name="instructions">The instructions for the agent.</param>
80+
/// <returns>An instance of <see cref="AzureAIAgent"/>.</returns>
81+
private static async Task<AzureAIAgent> CreateAzureAIAgentAsync(Kernel kernel, string name, string instructions)
82+
{
83+
// Load and validate configuration
84+
IConfigurationRoot config = new ConfigurationBuilder()
85+
.AddUserSecrets<Program>()
86+
.AddEnvironmentVariables()
87+
.Build();
88+
89+
if (config["AzureAI:ConnectionString"] is not { } connectionString)
90+
{
91+
const string Message = "Please provide a valid `AzureAI:ConnectionString` secret to run this sample. See the associated README.md for more details.";
92+
Console.Error.WriteLine(Message);
93+
throw new InvalidOperationException(Message);
94+
}
95+
96+
string modelId = config["AzureAI:ChatModelId"] ?? "gpt-4o-mini";
97+
98+
// Create the Azure AI Agent
99+
AIProjectClient projectClient = AzureAIAgent.CreateAzureAIClient(connectionString, new AzureCliCredential());
100+
101+
AgentsClient agentsClient = projectClient.GetAgentsClient();
102+
103+
Azure.AI.Projects.Agent agent = await agentsClient.CreateAgentAsync(modelId, name, null, instructions);
104+
105+
return new AzureAIAgent(agent, agentsClient)
106+
{
107+
Kernel = kernel
108+
};
109+
}
110+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.SemanticKernel;
10+
using ModelContextProtocol;
11+
using ModelContextProtocol.Client;
12+
using ModelContextProtocol.Protocol.Transport;
13+
using ModelContextProtocol.Protocol.Types;
14+
15+
namespace MCPClient.Samples;
16+
17+
internal abstract class BaseSample
18+
{
19+
/// <summary>
20+
/// Creates an MCP client and connects it to the MCPServer server.
21+
/// </summary>
22+
/// <param name="kernel">Optional kernel instance to use for the MCP client.</param>
23+
/// <param name="samplingRequestHandler">Optional handler for MCP sampling requests.</param>
24+
/// <returns>An instance of <see cref="IMcpClient"/>.</returns>
25+
protected static Task<IMcpClient> CreateMcpClientAsync(
26+
Kernel? kernel = null,
27+
Func<Kernel, CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, Task<CreateMessageResult>>? samplingRequestHandler = null)
28+
{
29+
KernelFunction? skSamplingHandler = null;
30+
31+
// Create and return the MCP client
32+
return McpClientFactory.CreateAsync(
33+
clientTransport: new StdioClientTransport(new StdioClientTransportOptions
34+
{
35+
Name = "MCPServer",
36+
Command = GetMCPServerPath(), // Path to the MCPServer executable
37+
}),
38+
clientOptions: samplingRequestHandler != null ? new McpClientOptions()
39+
{
40+
Capabilities = new ClientCapabilities
41+
{
42+
Sampling = new SamplingCapability
43+
{
44+
SamplingHandler = InvokeHandlerAsync
45+
},
46+
},
47+
} : null
48+
);
49+
50+
async ValueTask<CreateMessageResult> InvokeHandlerAsync(CreateMessageRequestParams? request, IProgress<ProgressNotificationValue> progress, CancellationToken cancellationToken)
51+
{
52+
if (request is null)
53+
{
54+
throw new ArgumentNullException(nameof(request));
55+
}
56+
57+
skSamplingHandler ??= KernelFunctionFactory.CreateFromMethod(
58+
(CreateMessageRequestParams? request, IProgress<ProgressNotificationValue> progress, CancellationToken ct) =>
59+
{
60+
return samplingRequestHandler(kernel!, request, progress, ct);
61+
},
62+
"MCPSamplingHandler"
63+
);
64+
65+
// The argument names must match the parameter names of the delegate the SK Function is created from
66+
KernelArguments kernelArguments = new()
67+
{
68+
["request"] = request,
69+
["progress"] = progress
70+
};
71+
72+
FunctionResult functionResult = await skSamplingHandler.InvokeAsync(kernel!, kernelArguments, cancellationToken);
73+
74+
return functionResult.GetValue<CreateMessageResult>()!;
75+
}
76+
}
77+
78+
/// <summary>
79+
/// Creates an instance of <see cref="Kernel"/> with the OpenAI chat completion service registered.
80+
/// </summary>
81+
/// <returns>An instance of <see cref="Kernel"/>.</returns>
82+
protected static Kernel CreateKernelWithChatCompletionService()
83+
{
84+
// Load and validate configuration
85+
IConfigurationRoot config = new ConfigurationBuilder()
86+
.AddUserSecrets<Program>()
87+
.AddEnvironmentVariables()
88+
.Build();
89+
90+
if (config["OpenAI:ApiKey"] is not { } apiKey)
91+
{
92+
const string Message = "Please provide a valid OpenAI:ApiKey to run this sample. See the associated README.md for more details.";
93+
Console.Error.WriteLine(Message);
94+
throw new InvalidOperationException(Message);
95+
}
96+
97+
string modelId = config["OpenAI:ChatModelId"] ?? "gpt-4o-mini";
98+
99+
// Create kernel
100+
var kernelBuilder = Kernel.CreateBuilder();
101+
kernelBuilder.Services.AddOpenAIChatCompletion(modelId: modelId, apiKey: apiKey);
102+
103+
return kernelBuilder.Build();
104+
}
105+
106+
/// <summary>
107+
/// Displays the list of available MCP tools.
108+
/// </summary>
109+
/// <param name="tools">The list of the tools to display.</param>
110+
protected static void DisplayTools(IList<McpClientTool> tools)
111+
{
112+
Console.WriteLine("Available MCP tools:");
113+
foreach (var tool in tools)
114+
{
115+
Console.WriteLine($"- Name: {tool.Name}, Description: {tool.Description}");
116+
}
117+
Console.WriteLine();
118+
}
119+
120+
/// <summary>
121+
/// Returns the path to the MCPServer server executable.
122+
/// </summary>
123+
/// <returns>The path to the MCPServer server executable.</returns>
124+
private static string GetMCPServerPath()
125+
{
126+
// Determine the configuration (Debug or Release)
127+
string configuration;
128+
129+
#if DEBUG
130+
configuration = "Debug";
131+
#else
132+
configuration = "Release";
133+
#endif
134+
135+
return Path.Combine("..", "..", "..", "..", "MCPServer", "bin", configuration, "net8.0", "MCPServer.exe");
136+
}
137+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.SemanticKernel;
8+
using Microsoft.SemanticKernel.Agents;
9+
using Microsoft.SemanticKernel.Connectors.OpenAI;
10+
using ModelContextProtocol.Client;
11+
12+
namespace MCPClient.Samples;
13+
14+
/// <summary>
15+
/// Demonstrates how to use <see cref="ChatCompletionAgent"/> with MCP tools represented as Kernel functions.
16+
/// </summary>
17+
internal sealed class ChatCompletionAgentWithMCPToolsSample : BaseSample
18+
{
19+
/// <summary>
20+
/// Demonstrates how to use <see cref="ChatCompletionAgent"/> with MCP tools represented as Kernel functions.
21+
/// The code in this method:
22+
/// 1. Creates an MCP client.
23+
/// 2. Retrieves the list of tools provided by the MCP server.
24+
/// 3. Creates a kernel and registers the MCP tools as Kernel functions.
25+
/// 4. Defines chat completion agent with instructions, name, kernel, and arguments.
26+
/// 5. Invokes the agent with a prompt.
27+
/// 6. The agent sends the prompt to the AI model, together with the MCP tools represented as Kernel functions.
28+
/// 7. The AI model calls DateTimeUtils-GetCurrentDateTimeInUtc function to get the current date time in UTC required as an argument for the next function.
29+
/// 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.
30+
/// 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.
31+
/// </summary>
32+
public static async Task RunAsync()
33+
{
34+
Console.WriteLine($"Running the {nameof(ChatCompletionAgentWithMCPToolsSample)} sample.");
35+
36+
// Create an MCP client
37+
await using IMcpClient mcpClient = await CreateMcpClientAsync();
38+
39+
// Retrieve and display the list provided by the MCP server
40+
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
41+
DisplayTools(tools);
42+
43+
// Create a kernel and register the MCP tools as kernel functions
44+
Kernel kernel = CreateKernelWithChatCompletionService();
45+
kernel.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
46+
47+
// Enable automatic function calling
48+
OpenAIPromptExecutionSettings executionSettings = new()
49+
{
50+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
51+
};
52+
53+
string prompt = "What is the likely color of the sky in Boston today?";
54+
Console.WriteLine(prompt);
55+
56+
// Define the agent
57+
ChatCompletionAgent agent = new()
58+
{
59+
Instructions = "Answer questions about the weather.",
60+
Name = "WeatherAgent",
61+
Kernel = kernel,
62+
Arguments = new KernelArguments(executionSettings),
63+
};
64+
65+
// Invokes agent with a prompt
66+
ChatMessageContent response = await agent.InvokeAsync(prompt).FirstAsync();
67+
68+
Console.WriteLine(response);
69+
Console.WriteLine();
70+
71+
// The expected output is: The sky in Boston today is likely gray due to rainy weather.
72+
}
73+
}

0 commit comments

Comments
 (0)