Skip to content

Commit 69e4e6b

Browse files
.Net: Add MCP server/client sample (#11232)
### Motivation, Context and Description This PR adds sample to demonstrate the way 1. SK can be used on MCP server side that would expose SK functions as MCP tools and 2. SK can be used on the client side to consume the MCP tools provided by the server Contributes to: #11199
1 parent 38d891e commit 69e4e6b

File tree

11 files changed

+319
-1
lines changed

11 files changed

+319
-1
lines changed

dotnet/SK-dotnet.sln

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PineconeIntegrationTests",
508508
EndProject
509509
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocolPlugin", "samples\Demos\ModelContextProtocolPlugin\ModelContextProtocolPlugin.csproj", "{801C9CE4-53AF-D2DB-E0D6-9A6BB47E9654}"
510510
EndProject
511+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ModelContextProtocolClientServer", "ModelContextProtocolClientServer", "{879545ED-D429-49B1-96F1-2EC55FFED31D}"
512+
ProjectSection(SolutionItems) = preProject
513+
samples\Demos\ModelContextProtocolClientServer\README.md = samples\Demos\ModelContextProtocolClientServer\README.md
514+
EndProjectSection
515+
EndProject
516+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCPServer", "samples\Demos\ModelContextProtocolClientServer\MCPServer\MCPServer.csproj", "{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}"
517+
EndProject
518+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCPClient", "samples\Demos\ModelContextProtocolClientServer\MCPClient\MCPClient.csproj", "{B06770D5-2F3E-4271-9F6B-3AA9E716176F}"
519+
ProjectSection(ProjectDependencies) = postProject
520+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981} = {12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}
521+
EndProjectSection
522+
EndProject
511523
Global
512524
GlobalSection(SolutionConfigurationPlatforms) = preSolution
513525
Debug|Any CPU = Debug|Any CPU
@@ -1403,6 +1415,18 @@ Global
14031415
{801C9CE4-53AF-D2DB-E0D6-9A6BB47E9654}.Publish|Any CPU.Build.0 = Release|Any CPU
14041416
{801C9CE4-53AF-D2DB-E0D6-9A6BB47E9654}.Release|Any CPU.ActiveCfg = Release|Any CPU
14051417
{801C9CE4-53AF-D2DB-E0D6-9A6BB47E9654}.Release|Any CPU.Build.0 = Release|Any CPU
1418+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1419+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Debug|Any CPU.Build.0 = Debug|Any CPU
1420+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Publish|Any CPU.ActiveCfg = Release|Any CPU
1421+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Publish|Any CPU.Build.0 = Release|Any CPU
1422+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Release|Any CPU.ActiveCfg = Release|Any CPU
1423+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}.Release|Any CPU.Build.0 = Release|Any CPU
1424+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1425+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Debug|Any CPU.Build.0 = Debug|Any CPU
1426+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Publish|Any CPU.ActiveCfg = Release|Any CPU
1427+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Publish|Any CPU.Build.0 = Release|Any CPU
1428+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Release|Any CPU.ActiveCfg = Release|Any CPU
1429+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Release|Any CPU.Build.0 = Release|Any CPU
14061430
EndGlobalSection
14071431
GlobalSection(SolutionProperties) = preSolution
14081432
HideSolutionNode = FALSE
@@ -1594,6 +1618,9 @@ Global
15941618
{A5E6193C-8431-4C6E-B674-682CB41EAA0C} = {4F381919-F1BE-47D8-8558-3187ED04A84F}
15951619
{E9A74E0C-BC02-4DDD-A487-89847EDF8026} = {4F381919-F1BE-47D8-8558-3187ED04A84F}
15961620
{801C9CE4-53AF-D2DB-E0D6-9A6BB47E9654} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
1621+
{879545ED-D429-49B1-96F1-2EC55FFED31D} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
1622+
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981} = {879545ED-D429-49B1-96F1-2EC55FFED31D}
1623+
{B06770D5-2F3E-4271-9F6B-3AA9E716176F} = {879545ED-D429-49B1-96F1-2EC55FFED31D}
15971624
EndGlobalSection
15981625
GlobalSection(ExtensibilityGlobals) = postSolution
15991626
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
8+
<NoWarn>$(NoWarn);CA2249;CS0612;SKEXP0001;VSTHRD111;CA2007</NoWarn>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="ModelContextProtocol" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
15+
<PackageReference Include="Microsoft.Extensions.Logging" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
17+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj" />
22+
<ProjectReference Include="..\..\..\..\src\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" />
23+
<ProjectReference Include="..\..\..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.SemanticKernel;
11+
using Microsoft.SemanticKernel.Connectors.OpenAI;
12+
using ModelContextProtocol.Client;
13+
using ModelContextProtocol.Configuration;
14+
using ModelContextProtocol.Protocol.Transport;
15+
16+
namespace MCPClient;
17+
18+
internal sealed class Program
19+
{
20+
public static async Task Main(string[] args)
21+
{
22+
// Load and validate configuration
23+
var config = new ConfigurationBuilder()
24+
.AddUserSecrets<Program>()
25+
.AddEnvironmentVariables()
26+
.Build();
27+
28+
if (config["OpenAI:ApiKey"] is not { } apiKey)
29+
{
30+
Console.Error.WriteLine("Please provide a valid OpenAI:ApiKey to run this sample. See the associated README.md for more details.");
31+
return;
32+
}
33+
34+
// Create an MCP client
35+
await using var mcpClient = await McpClientFactory.CreateAsync(
36+
new McpServerConfig()
37+
{
38+
Id = "MCPServer",
39+
Name = "MCPServer",
40+
TransportType = TransportTypes.StdIo,
41+
TransportOptions = new()
42+
{
43+
// Point the client to the MCPServer server executable
44+
["command"] = GetMCPServerPath()
45+
}
46+
},
47+
new McpClientOptions()
48+
{
49+
ClientInfo = new() { Name = "MCPClient", Version = "1.0.0" }
50+
}
51+
);
52+
53+
// Retrieve and display the list of tools available on the MCP server
54+
Console.WriteLine("Available MCP tools:");
55+
var tools = await mcpClient.GetAIFunctionsAsync().ConfigureAwait(false);
56+
foreach (var tool in tools)
57+
{
58+
Console.WriteLine($"{tool.Name}: {tool.Description}");
59+
}
60+
61+
// Prepare and build kernel with the MCP tools as Kernel functions
62+
var kernelBuilder = Kernel.CreateBuilder();
63+
kernelBuilder.Plugins.AddFromFunctions("Tools", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
64+
kernelBuilder.Services
65+
.AddLogging(c => c.AddDebug().SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace))
66+
.AddOpenAIChatCompletion(serviceId: "openai", modelId: config["OpenAI:ChatModelId"] ?? "gpt-4o-mini", apiKey: apiKey);
67+
68+
Kernel kernel = kernelBuilder.Build();
69+
70+
// Enable automatic function calling
71+
OpenAIPromptExecutionSettings executionSettings = new()
72+
{
73+
Temperature = 0,
74+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
75+
};
76+
77+
// Execute a prompt using the MCP tools. The AI model will automatically call the appropriate MCP tools to answer the prompt.
78+
var prompt = "What is the likely color of the sky in Boston today?";
79+
var result = await kernel.InvokePromptAsync(prompt, new(executionSettings)).ConfigureAwait(false);
80+
Console.WriteLine($"\n\n{prompt}\n{result}");
81+
82+
// The expected output is:
83+
// What is the likely color of the sky in Boston today?
84+
// The likely color of the sky in Boston today is gray, as it is currently rainy.
85+
}
86+
87+
/// <summary>
88+
/// Returns the path to the MCPServer server executable.
89+
/// </summary>
90+
/// <returns>The path to the MCPServer server executable.</returns>
91+
private static string GetMCPServerPath()
92+
{
93+
// Determine the configuration (Debug or Release)
94+
string configuration;
95+
96+
#if DEBUG
97+
configuration = "Debug";
98+
#else
99+
configuration = "Release";
100+
#endif
101+
102+
return Path.Combine("..", "..", "..", "..", "MCPServer", "bin", configuration, "net8.0", "MCPServer.exe");
103+
}
104+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Worker">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<NoWarn>$(NoWarn);VSTHRD111;CA2007;SKEXP0001</NoWarn>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Hosting" />
12+
<PackageReference Include="ModelContextProtocol" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\..\..\src\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" />
17+
<ProjectReference Include="..\..\..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
18+
</ItemGroup>
19+
</Project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using MCPServer.Tools;
4+
using Microsoft.SemanticKernel;
5+
using ModelContextProtocol;
6+
7+
// Create a kernel builder and add plugins
8+
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
9+
kernelBuilder.Plugins.AddFromType<DateTimeUtils>();
10+
kernelBuilder.Plugins.AddFromType<WeatherUtils>();
11+
12+
// Build the kernel
13+
Kernel kernel = kernelBuilder.Build();
14+
15+
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
16+
builder.Services
17+
.AddMcpServer()
18+
.WithStdioServerTransport()
19+
// Add kernel functions to the MCP server as MCP tools
20+
.WithTools(kernel.Plugins.SelectMany(p => p.Select(f => f.AsAIFunction())));
21+
await builder.Build().RunAsync();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"MCPServer": {
5+
"commandName": "Project",
6+
"dotnetRunMessages": true,
7+
"environmentVariables": {
8+
"DOTNET_ENVIRONMENT": "Development"
9+
}
10+
}
11+
}
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ComponentModel;
4+
using Microsoft.SemanticKernel;
5+
6+
namespace MCPServer.Tools;
7+
8+
/// <summary>
9+
/// A collection of utility methods for working with date time.
10+
/// </summary>
11+
internal sealed class DateTimeUtils
12+
{
13+
/// <summary>
14+
/// Retrieves the current date time in UTC.
15+
/// </summary>
16+
/// <returns>The current date time in UTC.</returns>
17+
[KernelFunction, Description("Retrieves the current date time in UTC.")]
18+
public static string GetCurrentDateTimeInUtc()
19+
{
20+
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ComponentModel;
4+
using Microsoft.SemanticKernel;
5+
6+
namespace MCPServer.Tools;
7+
8+
/// <summary>
9+
/// A collection of utility methods for working with weather.
10+
/// </summary>
11+
internal sealed class WeatherUtils
12+
{
13+
/// <summary>
14+
/// Gets the current weather for the specified city.
15+
/// </summary>
16+
/// <param name="cityName">The name of the city.</param>
17+
/// <param name="currentDateTimeInUtc">The current date time in UTC.</param>
18+
/// <returns>The current weather for the specified city.</returns>
19+
[KernelFunction, Description("Gets the current weather for the specified city and specified date time.")]
20+
public static string GetWeatherForCity(string cityName, string currentDateTimeInUtc)
21+
{
22+
return cityName switch
23+
{
24+
"Boston" => "61 and rainy",
25+
"London" => "55 and cloudy",
26+
"Miami" => "80 and sunny",
27+
"Paris" => "60 and rainy",
28+
"Tokyo" => "50 and sunny",
29+
"Sydney" => "75 and sunny",
30+
"Tel Aviv" => "80 and sunny",
31+
_ => "31 and snowing",
32+
};
33+
}
34+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.Hosting.Lifetime": "Information"
6+
}
7+
}
8+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Model Context Protocol Client Server Sample
2+
3+
This sample demonstrates how to use Semantic Kernel with the [Model Context Protocol (MCP) C# SDK](https://github.com/modelcontextprotocol/csharp-sdk) to build an MCP server and client.
4+
5+
MCP is an open protocol that standardizes how applications provide context to LLMs. Please refer to the [documentation](https://modelcontextprotocol.io/introduction) for more information.
6+
7+
The sample shows:
8+
9+
1. How to create an MCP server powered by SK: SK plugins are exposed as MCP tools.
10+
2. How to create an MCP client and import the MCP tools to SK and use them.
11+
12+
## Configuring Secrets or Environment Variables
13+
14+
The example require credentials to access OpenAI.
15+
16+
If you have set up those credentials as secrets within Secret Manager or through environment variables for other samples from the solution in which this project is found, they will be re-used.
17+
18+
### Set Secrets with Secret Manager
19+
20+
```text
21+
cd dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient
22+
23+
dotnet user-secrets init
24+
25+
dotnet user-secrets set "OpenAI:ChatModelId" "..."
26+
dotnet user-secrets set "OpenAI:ApiKey" "..."
27+
"..."
28+
```
29+
30+
### Set Secrets with Environment Variables
31+
32+
Use these names:
33+
34+
```text
35+
# OpenAI
36+
OpenAI__ChatModelId
37+
OpenAI__ApiKey
38+
```
39+
40+
## Run the Sample
41+
42+
To run the sample, follow these steps:
43+
1. Right-click on the `MCPClient` project in Visual Studio and select `Set as Startup Project`.
44+
2. Press `F5` to run the project.

0 commit comments

Comments
 (0)