Featured image of post Using Model Context Protocol in agents - Pro-code agents with Semantic Kernel

Using Model Context Protocol in agents - Pro-code agents with Semantic Kernel

In this post, we'll see how to use the Model Context Protocol (MCP) with agents created with Semantic Kernel.

Welcome back to another post in our series on Model Context Protocol (MCP) and its applications in agents! In the first post we explored the basic concepts and we implemented a simple MCP server using the stdio transport layer. In the second post, we reimplemented our server using the HTTP Streaming / SSE transport layer and we used the exposed tools in a low code agent created with Copilot Studio. Today, we’ll move into the pro-code world and we’ll see how to use the same MCP server with Semantic Kernel, the AI orchestration SDK from Microsoft which you should be already familiar with if you are following my blog.

Connecting to an MCP server with C#

The starting point of our sample client doesn’t even require using Semantic Kernel. The capabilities to connect to an MCP server and access to the available tools, in fact, is provided directly by the MCP SDK for C#, the same one we have used in the previous posts to implement the server. We’ll start with a very simple console application:

1
dotnet new console -n MCP.Client

Then, we must install the MCP SDK for C# using NuGet:

1
dotnet add package ModelContextProtocol --prerelease

Now we can head over to the Program.cs file and start writing some code to connect to our client. The SDK supports both transportation layers we have seen so far. Let’s start tby seeing how to connect to the MCP server using the stdio transport layer. The code is very simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var projectPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "MCPServer", "Stdio", "MCPServer.Server.Stdio.csproj"));

await using var mcpClient = await McpClientFactory.CreateAsync(

    new StdioClientTransport(new () {
        Name = "MyFirstMCP",
        Command = "dotnet",
        Arguments = ["run", "--project", projectPath],
    })
);

We create a new connection using the CreateAsync() method of the McpClientFactory class and we pass a new instance of the StdioClientTransport class. The constructor of this class takes a configuration object with the same properties that we have seen when we have configured Visual Studio Code to connect to our MCP server:

  • Name: the name of the connection.
  • Command: the command to run the server. In this case, since our server is based on a .NET console application, we are simply using dotnet to leverage the dotnet CLI.
  • Arguments: the arguments to pass to the command. In this case, we are passing run and --project followed by the path of the project we want to run. This is the same command we would use to run the console application from the command line.

If we want to use the HTTP Streaming / SSE transport layer, we can do it by using the same initialization code, but passing this time a SseClientTransport object as parameter to the CreateAsync() method:

1
2
3
4
5
6
await using var mcpClient = await McpClientFactory.CreateAsync(
    new SseClientTransport(new () {
        Name = "MyFirstMCP",
        Endpoint = "http://localhost:5248"
    })
);

In this case, we simply pass the name of the connection and the endpoint of the MCP server as parameters. In the implementation we did in the previous post, we used ASP.NET Core to host the MCP server, so the default endpoint is http://localhost:5248.

Once the connection is established, we can use the mcpClient to get access to the tools that our MCP server is exposing:

1
2
3
4
5
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
    Console.WriteLine($"Tool: {tool.Name} - {tool.Description}");
}

In this case, we’re simply listing the tools that are available in the MCP server. If we run the code we have written so far, we should see the following output:

1
2
3
Tool: GetAllEmployees - Get the list of employees with their number of vacation days left
Tool: ChargeVacationDays - Charge vacation days for a given employee.
Tool: GetVacationDaysLeft - Get the vacation days left for a given employee.

These are the three tools we have implemented in the MCP server in the first post to manage the vacation days of our employees. Now itโ€™s time to see how these tools can be used in a Semantic Kernel agent.

Using MCP in a Semantic Kernel agent

As first step, we need to install Semantic Kernel in our project. We can do it by running the following command:

1
2
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core

Now we must create a new instance of the Kernel class, which is required to interact with the AI services. In my case, I’m going to use Azure OpenAI, so I’m going to use the following code to initialize the chat completion service:

1
2
3
4
5
6
7
var endpoint = "AzureOpenAI:Endpoint";
var apiKey = "AzureOpenAI:ApiKey";
var deploymentName = "AzureOpenAI:DeploymentName";

var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(deploymentName, endpoint, apiKey)
.Build();

The values of endpoint, apiKey and deploymentName are just placeholders - you will need to replace them with the actual values of your Azure OpenAI service.

For the sake of simplicity, I’m setting the configuration values directly in the code. In a real application, you should use a configuration file, environment variables or a service like Azure Key Vault to store these values.

Now that we have the kernel, we can use one of the new features added in the latest version of Semantic Kernel, which is the ability to convert MCP tools into Semantic Kernel functions, that can be loaded into a plugin:

1
kernel.Plugins.AddFromFunctions("MyFirstMCP", tools.Select(x => x.AsKernelFunction()));s

We assign a name to the plugin - in this case MyFirstMCP - and we pass the list of tools we have retrieved from the MCP server. The AsKernelFunction() method is an extension method that converts the MCP tool into a Semantic Kernel function. By default, the code will give raise an error. The reason is that this method is marked as experimental and, as such, you must suppress the warning to be able to compile the code. You can do this in code or in the project’s configuration. We’ll go with the second option, since later we’re going to write another piece of code that is also marked as experimental. To do this, we need to edit the .csproj file and add the following property inside the PropertyGroup section:

1
 <NoWarn>$(NoWarn);SKEXP0001</NoWarn>

Now that the MCP tools are loaded into a Semantic Kernel plugin, we can use them in our agents in the same way we would use any other Semantic Kernel plugin. First, we need to define the execution settings so the agent will automatically call the functions in the plugin when needed, based on the prompt:

1
2
3
4
OpenAIPromptExecutionSettings executionSettings = new()
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
};

This is the same settings we would have used in a normal Semantic Kernel agent, except for the RetainArgumentTypes property, which is set to true. This is required to make sure that the agent will pass the arguments to the functions in the plugin in the same way they are defined in the MCP server. This is important because the MCP server is expecting the arguments to be passed in a specific format, and if we don’t set this property to true, the agent will pass the arguments in a different format, which will cause the MCP server to fail. This is a property that was added specifically to support the MCP integration, so it’s marked as experimental as well.

Now we can define the agent, using the ChatCompletionAgent class, which is the default agent in Semantic Kernel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ChatCompletionAgent agent = new ChatCompletionAgent()
{
    Name = "LeaveRequestAgent",
    Instructions = @"You are an agent that can help employees with their tasks about leave requests. You can answer to
questions like:
- How many days of leave do I have left?
- Charge 3 days to my leave balance.",
    Kernel = kernel,
    Arguments = new(executionSettings)
};

The agent is configured with a name and a set of instructions, plus the kernel and the execution settings we have just defined. In the instructions, we define the tasks that the agent can help with, which are aligned with the tools we have defined in the MCP server. The agent will use these instructions to understand what the user is asking for and to decide which tool to use to fulfill the request.

Finally, we can execute the agent providing a prompt as input:

1
2
3
4
string prompt = "Give me the list of the employees with their vacation days left";

var response = await agent.InvokeAsync(prompt).FirstAsync();
Console.WriteLine(response.Message.Content);

We’re calling the InvokeAsync() method of the agent, passing the prompt as input, which will return a collection of responses. In this case, since it’s a single agent, there will be only a single one, so we’re simply taking it and printing the content of the message.

If we run the code, we should see the following output, which is aligned to what we have seen in the first post (when we called the MCP server from Visual Studio Code) or in the second post (when we called it from the Copilot Studio agent):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Here is the list of employees along with their vacation days left:

1. **Alice Johnson** - 15 days
2. **Bob Smith** - 7 days
3. **Charlie Lee** - 14 days
4. **Diana Evans** - 19 days
5. **Ethan Brown** - 24 days
6. **Fiona Clark** - 22 days
7. **George Miller** - 12 days
8. **Hannah Davis** - 8 days
9. **Ian Wilson** - 18 days
10. **Julia Adams** - 23 days

Now you have a fully fledged Semantic Kernel agent, which could be used as a standalone or, as we have seen in other posts, it could be added into a group chat with other agents to tackle more complex scenarios that requires multi-agent collaboration.

Wrapping up

In this post, we have seen the final MCP implementation of our journey: using an MCP server from a pro-code agent created with Semantic Kernel. Now we have a complete picture of the MCP ecosystem and we can use it in different scenarios, from low code to pro code. On GitHub you’ll find the complete code of the solution we have built so far, with a slightly different implementation: the pro-code client isn’t a console application, but a web application built with Blazor. The code is exactly the same, however the agent invocation is a little bit more interactive: the user can type the prompt in a text box and then click on a button to get a response. When the button is clicked, the Blazor application executes the same exact code we have seen in the console application to bootstrap the agent, connect it to the MCP server and process the prompt.

Additionally, the whole solution is wrapped in a .NET Aspire project, which is an amazing platform by Microsoft to simplify the orchestration of complex .NET applications during the development experience. Thanks to .NET Aspire, we can connect multiple projects together and easily connect them. .NET Aspire supports the most common tools and libraries that are adopted by client-server scenarios: we can easily add a Docker image, a Redis cache, a Service Bus instance, etc. And when we connect them together, a lot of magical things happens automatically under the hood, like:

  • Service discovery: the services can discover each other automatically, without the need to configure any connection string or endpoint. .NET Aspire automatically injects the connections strings as environment variables, that can be picked up by the other projects.
  • Health checks: .NET Aspire automatically configures the health checks for the services, so we can easily monitor their status and see if they are up and running.
  • Analytics: .NET Aspire automatically configures the logging, monitoring and tracing for the services.

Thanks to .NET Aspire, I was able to combine the MCP SSE server and the Blazor client in a single solution, which is very easy to run and debug. And, as a bonus point, you get also logging, metrics and tracing out of the box ๐Ÿ˜Š

You can refer to the instructions in the repository to see the steps required to run the solution.

Happy coding!

Built with Hugo
Theme Stack designed by Jimmy