-
Notifications
You must be signed in to change notification settings - Fork 4k
.Net: Demo showing how to integrate MCP tools with Semantic Kernel #10779
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
markwallace-microsoft
merged 7 commits into
microsoft:main
from
markwallace-microsoft:users/markwallace/mcp_demo
Mar 4, 2025
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
954a7b6
Demo showing how to integrate MCP tools with Semantic Kernel
markwallace-microsoft c0b9d0a
Fix warning
markwallace-microsoft 6ddfd11
Switch to use the GitHub MCP server
markwallace-microsoft bfbebf7
Merge branch 'main' into users/markwallace/mcp_demo
markwallace-microsoft 3ba76a3
Merge branch 'main' into users/markwallace/mcp_demo
markwallace-microsoft 1b41289
Address code review feedback
markwallace-microsoft eb6f8d2
Add more comments
markwallace-microsoft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
dotnet/samples/Demos/ModelContextProtocol/McpDotNetExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using McpDotNet.Client; | ||
using McpDotNet.Configuration; | ||
using McpDotNet.Protocol.Types; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
using Microsoft.SemanticKernel; | ||
|
||
namespace ModelContextProtocol; | ||
|
||
/// <summary> | ||
/// Extension methods for McpDotNet | ||
/// </summary> | ||
internal static class McpDotNetExtensions | ||
{ | ||
/// <summary> | ||
/// Retrieve an <see cref="IMcpClient"/> instance configured to connect to a GitHub server running on stdio. | ||
/// </summary> | ||
internal static async Task<IMcpClient> GetGitHubToolsAsync() | ||
{ | ||
McpClientOptions options = new() | ||
{ | ||
ClientInfo = new() { Name = "GitHub", Version = "1.0.0" } | ||
}; | ||
|
||
var config = new McpServerConfig | ||
{ | ||
Id = "github", | ||
Name = "GitHub", | ||
TransportType = "stdio", | ||
TransportOptions = new Dictionary<string, string> | ||
{ | ||
["command"] = "npx", | ||
["arguments"] = "-y @modelcontextprotocol/server-github", | ||
} | ||
}; | ||
|
||
var factory = new McpClientFactory( | ||
[config], | ||
options, | ||
NullLoggerFactory.Instance | ||
); | ||
|
||
return await factory.GetClientAsync(config.Id).ConfigureAwait(false); | ||
} | ||
|
||
/// <summary> | ||
/// Map the tools exposed on this <see cref="IMcpClient"/> to a collection of <see cref="KernelFunction"/> instances for use with the Semantic Kernel. | ||
/// </summary> | ||
internal static async Task<IEnumerable<KernelFunction>> MapToFunctionsAsync(this IMcpClient mcpClient) | ||
{ | ||
var tools = await mcpClient.ListToolsAsync().ConfigureAwait(false); | ||
return tools.Tools.Select(t => t.ToKernelFunction(mcpClient)).ToList(); | ||
} | ||
|
||
#region private | ||
private static KernelFunction ToKernelFunction(this Tool tool, IMcpClient mcpClient) | ||
{ | ||
async Task<string> InvokeToolAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
// Convert arguments to dictionary format expected by mcpdotnet | ||
Dictionary<string, object> mcpArguments = []; | ||
foreach (var arg in arguments) | ||
{ | ||
if (arg.Value is not null) | ||
{ | ||
mcpArguments[arg.Key] = function.ToArgumentValue(arg.Key, arg.Value); | ||
} | ||
} | ||
|
||
// Call the tool through mcpdotnet | ||
var result = await mcpClient.CallToolAsync( | ||
tool.Name, | ||
mcpArguments, | ||
cancellationToken: cancellationToken | ||
).ConfigureAwait(false); | ||
|
||
// Extract the text content from the result | ||
return string.Join("\n", result.Content | ||
.Where(c => c.Type == "text") | ||
.Select(c => c.Text)); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Console.Error.WriteLine($"Error invoking tool '{tool.Name}': {ex.Message}"); | ||
|
||
// Rethrowing to allow the kernel to handle the exception | ||
throw; | ||
} | ||
} | ||
|
||
return KernelFunctionFactory.CreateFromMethod( | ||
method: InvokeToolAsync, | ||
functionName: tool.Name, | ||
description: tool.Description, | ||
parameters: tool.ToParameters(), | ||
returnParameter: ToReturnParameter() | ||
); | ||
} | ||
|
||
private static object ToArgumentValue(this KernelFunction function, string name, object value) | ||
{ | ||
var parameter = function.Metadata.Parameters.FirstOrDefault(p => p.Name == name); | ||
return parameter?.ParameterType switch | ||
{ | ||
Type t when Nullable.GetUnderlyingType(t) == typeof(int) => Convert.ToInt32(value), | ||
Type t when Nullable.GetUnderlyingType(t) == typeof(double) => Convert.ToDouble(value), | ||
Type t when Nullable.GetUnderlyingType(t) == typeof(bool) => Convert.ToBoolean(value), | ||
Type t when t == typeof(List<string>) => (value as IEnumerable<object>)?.ToList(), | ||
Type t when t == typeof(Dictionary<string, object>) => (value as Dictionary<string, object>)?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), | ||
_ => value, | ||
} ?? value; | ||
} | ||
|
||
private static List<KernelParameterMetadata>? ToParameters(this Tool tool) | ||
{ | ||
var inputSchema = tool.InputSchema; | ||
var properties = inputSchema?.Properties; | ||
if (properties == null) | ||
{ | ||
return null; | ||
} | ||
|
||
HashSet<string> requiredProperties = new(inputSchema!.Required ?? []); | ||
return properties.Select(kvp => | ||
new KernelParameterMetadata(kvp.Key) | ||
{ | ||
Description = kvp.Value.Description, | ||
ParameterType = ConvertParameterDataType(kvp.Value, requiredProperties.Contains(kvp.Key)), | ||
IsRequired = requiredProperties.Contains(kvp.Key) | ||
}).ToList(); | ||
} | ||
|
||
private static KernelReturnParameterMetadata? ToReturnParameter() | ||
{ | ||
return new KernelReturnParameterMetadata() | ||
{ | ||
ParameterType = typeof(string), | ||
}; | ||
} | ||
private static Type ConvertParameterDataType(JsonSchemaProperty property, bool required) | ||
{ | ||
var type = property.Type switch | ||
{ | ||
"string" => typeof(string), | ||
"integer" => typeof(int), | ||
"number" => typeof(double), | ||
"boolean" => typeof(bool), | ||
"array" => typeof(List<string>), | ||
"object" => typeof(Dictionary<string, object>), | ||
_ => typeof(object) | ||
}; | ||
|
||
return !required && type.IsValueType ? typeof(Nullable<>).MakeGenericType(type) : type; | ||
} | ||
#endregion | ||
} |
33 changes: 33 additions & 0 deletions
33
dotnet/samples/Demos/ModelContextProtocol/ModelContextProtocol.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId> | ||
<NoWarn>$(NoWarn);CA2249;CS0612</NoWarn> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="mcpdotnet" /> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" /> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Console" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="SimpleToolsConsole.json"> | ||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\..\src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj" /> | ||
<ProjectReference Include="..\..\..\src\SemanticKernel.Abstractions\SemanticKernel.Abstractions.csproj" /> | ||
<ProjectReference Include="..\..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Connectors.OpenAI; | ||
using ModelContextProtocol; | ||
|
||
var config = new ConfigurationBuilder() | ||
.AddUserSecrets<Program>() | ||
.AddEnvironmentVariables() | ||
.Build(); | ||
|
||
// Prepare and build kernel | ||
var builder = Kernel.CreateBuilder(); | ||
builder.Services.AddLogging(c => c.AddDebug().SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace)); | ||
|
||
if (config["OpenAI:ApiKey"] is not null) | ||
{ | ||
builder.Services.AddOpenAIChatCompletion( | ||
serviceId: "openai", | ||
modelId: config["OpenAI:ChatModelId"] ?? "gpt-4o", | ||
apiKey: config["OpenAI:ApiKey"]!); | ||
} | ||
else | ||
{ | ||
Console.Error.WriteLine("Please provide a valid OpenAI:ApiKey to run this sample. See the associated README.md for more details."); | ||
return; | ||
} | ||
|
||
Kernel kernel = builder.Build(); | ||
|
||
// Add the MCP simple tools as Kernel functions | ||
var mcpClient = await McpDotNetExtensions.GetGitHubToolsAsync().ConfigureAwait(false); | ||
var functions = await mcpClient.MapToFunctionsAsync().ConfigureAwait(false); | ||
|
||
foreach (var function in functions) | ||
{ | ||
Console.WriteLine($"{function.Name}: {function.Description}"); | ||
} | ||
|
||
kernel.Plugins.AddFromFunctions("GitHub", functions); | ||
|
||
// Enable automatic function calling | ||
var executionSettings = new OpenAIPromptExecutionSettings | ||
{ | ||
Temperature = 0, | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() | ||
}; | ||
|
||
// Test using GitHub tools | ||
var prompt = "Summarize the last four commits to the microsoft/semantic-kernel repository?"; | ||
var result = await kernel.InvokePromptAsync(prompt, new(executionSettings)).ConfigureAwait(false); | ||
Console.WriteLine($"\n\n{prompt}\n{result}"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Model Context Protocol Sample | ||
|
||
This example demonstrates how to use Model Context Protocol tools with Semantic Kernel. | ||
|
||
MCP is an open protocol that standardizes how applications provide context to LLMs. | ||
|
||
For for information on Model Context Protocol (MCP) please refer to the [documentation](https://modelcontextprotocol.io/introduction). | ||
|
||
This sample uses [mcpdotnet](https://www.nuget.org/packages/mcpdotnet) was heavily influenced by the [samples](https://github.com/PederHP/mcpdotnet/tree/main/samples) from that repository. | ||
|
||
The sample shows: | ||
|
||
1. How to connect to an MCP Server using [mcpdotnet](https://www.nuget.org/packages/mcpdotnet) | ||
2. Retrieve the list of tools the MCP Server makes available | ||
3. Convert the MCP tools to Semantic Kernel functions so they can be added to a Kernel instance | ||
4. Invoke the tools from Semantic Kernel using function calling | ||
|
||
## Configuring Secrets | ||
|
||
The example require credentials to access OpenAI. | ||
|
||
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. | ||
|
||
### To set your secrets with Secret Manager: | ||
|
||
```text | ||
cd dotnet/samples/Demos/ModelContextProtocol | ||
|
||
dotnet user-secrets init | ||
|
||
dotnet user-secrets set "OpenAI:ChatModelId" "..." | ||
dotnet user-secrets set "OpenAI:ApiKey" "..." | ||
"..." | ||
``` | ||
|
||
### To set your secrets with environment variables | ||
|
||
Use these names: | ||
|
||
```text | ||
# OpenAI | ||
OpenAI__ChatModelId | ||
OpenAI__ApiKey | ||
``` |
17 changes: 17 additions & 0 deletions
17
dotnet/samples/Demos/ModelContextProtocol/SimpleToolsConsole.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"Options": { | ||
"ClientInfo": { | ||
"Name": "SimpleToolsConsole", | ||
"Version": "1.0.0" | ||
} | ||
}, | ||
"Config": { | ||
"Id": "everything", | ||
"Name": "Everything", | ||
"TransportType": "stdio", | ||
"TransportOptions": { | ||
"command": "npx", | ||
"arguments": "-y @modelcontextprotocol/server-everything" | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.