// 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;
namespace MCPClient.Samples;
internal abstract class BaseSample
{
///
/// Creates an MCP client and connects it to the MCPServer server.
///
/// Optional kernel instance to use for the MCP client.
/// Optional handler for MCP sampling requests.
/// An instance of .
protected static Task CreateMcpClientAsync(
Kernel? kernel = null,
Func, CancellationToken, Task>? samplingRequestHandler = null)
{
KernelFunction? skSamplingHandler = null;
// Create and return the MCP client
return McpClient.CreateAsync(
clientTransport: new StdioClientTransport(new StdioClientTransportOptions
{
Name = "MCPServer",
Command = GetMCPServerPath(), // Path to the MCPServer executable
}),
clientOptions: samplingRequestHandler != null ? new McpClientOptions()
{
Handlers = new()
{
SamplingHandler = InvokeHandlerAsync,
},
} : null
);
async ValueTask InvokeHandlerAsync(CreateMessageRequestParams? request, IProgress progress, CancellationToken cancellationToken)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
skSamplingHandler ??= KernelFunctionFactory.CreateFromMethod(
(CreateMessageRequestParams? request, IProgress 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()!;
}
}
///
/// Creates an instance of with the OpenAI chat completion service registered.
///
/// An instance of .
protected static Kernel CreateKernelWithChatCompletionService()
{
// Load and validate configuration
IConfigurationRoot config = new ConfigurationBuilder()
.AddUserSecrets()
.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();
}
///
/// Displays the list of available MCP tools.
///
/// The list of the tools to display.
protected static void DisplayTools(IList tools)
{
Console.WriteLine("Available MCP tools:");
foreach (var tool in tools)
{
Console.WriteLine($"- Name: {tool.Name}, Description: {tool.Description}");
}
Console.WriteLine();
}
///
/// Returns the path to the MCPServer server executable.
///
/// The path to the MCPServer server executable.
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");
}
}