How to create MCP Client for your MCP Graph API Server in C#

MCP Server

Recently, I've showed you how to easily create MCP Server for the Microsoft Graph API in C# and how to use Claude for Desktop to communicate with MCP Server.

Claude for Desktop is more or less MCP Host that instantiates a MCP Client and communicates with the MCP Server.

With your own MCP Host you have more flexibility to call any AI models like Claude Sonnet or GPT. Let's see how to call Anthropic Claude API and Azure OpenAI programmatically.

Prerequisites

To be able to communicate with Anthropic Claude API and Azure OpenAI, you need API keys.

Anthropic Claude API

Go to the Anthropic Console and enter your email. You will receive an email with the secure link to log in to Anthropic Console.

Go to API keys settings and create a new API key.

Azure OpenAI service

Go to the Azure portal

Create a new Azure OpenAI resource. Once the resource is created, you can find the API key in the Keys and Endpoint section of the resource.

Go to Azure AI Foundry portal and deploy new AI model.

MCP Host

Now, create a new console application in C# and add the following NuGet packages:

  • Anthropic.SDK - interacts with the Anthropic Claude API
  • Azure.AI.OpenAI - interact with the Azure OpenAI service
  • Azure.Identity - for Azure authentication
  • Microsoft.Extensions.AI - utilities for AI components
  • Microsoft.Extensions.Hosting - contains host builders
  • ModelContextProtocol - the official C# SDK for the Model Context Protocol
  • Microsoft.Extensions.AI.OpenAI - provides an implementation of the IChatClient interface for the OpenAI package and OpenAI-compatible endpoints.

Use latest preview versions for Azure.AI.OpenAI, Microsoft.Extensions.AI, ModelContextProtocol and Microsoft.Extensions.AI.OpenAI

AI services

Let's create services for the Anthropic Claude API and Azure OpenAI service.

internal abstract class AIService : IDisposable
{
    protected IChatClient _client;
    protected ChatOptions _options;
    private bool disposedValue;

    public async Task<ChatResponse> GetResponseAsync(string chatMessage)
    {
        return await _client.GetResponseAsync(chatMessage, _options);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                _client?.Dispose();
            }

            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

The base class AIService implements IDisposable interface and contains the GetResponseAsync method that will be used to send messages to AI model.

The IChatClient and ChatOptions are part of the Microsoft.Extensions.AI namespace. The IChatClient interface is used to send requests to AI model and the ChatOptions class contains options for the requests.

Now, create a service class for the Anthropic Claude API.

internal class AnthropicService : AIService
{
    public AnthropicService(IList<McpClientTool> tools)
    {
        _client = new AnthropicClient(new APIAuthentication(GetAnthropicApiKey()))
            .Messages
            .AsBuilder()
            .UseFunctionInvocation()
            .Build();

        _options = new ChatOptions
        {
            MaxOutputTokens = 1000,
            ModelId = "claude-3-5-sonnet-20241022",
            Tools = [.. tools],
            ToolMode = ChatToolMode.RequireAny
        };
    }

    private static string GetAnthropicApiKey()
    {
        return "<anthropic_api_key>";
    }
}

The AnthropicService class inherits from the AIService class and implements the constructor that creates a new instance of the AnthropicClient class which is converted to the IChatClient.

The parameter tools is a list of tools exposed by the MCP Server(s).

A service class for the Azure OpenAI service is similar to the AnthropicService class.

internal class AzureOpenAIService : AIService
{
    public AzureOpenAIService(IList<McpClientTool> tools)
    {
        var endpoint = GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? "https://<your_endpoint>.openai.azure.com/";
        var key = GetEnvironmentVariable("AZURE_OPENAI_KEY") ?? GetOpenAIApiKey();
        var credential = new AzureKeyCredential(key);

        var azureClient = new AzureOpenAIClient(new Uri(endpoint), credential);
        _client = azureClient.GetOpenAIResponseClient("<deployment_name>")
            .AsIChatClient()
            .AsBuilder()
            .UseFunctionInvocation()
            .Build();

        _options = new ChatOptions
        {
            Temperature = (float)0.7,
            MaxOutputTokens = 800,
            TopP = (float)0.95,
            FrequencyPenalty = (float)0,
            PresencePenalty = (float)0,
            Tools = [.. tools],
            ToolMode = ChatToolMode.RequireAny
        };
    }

    private static string GetOpenAIApiKey()
    {
        return "<open_api_key>";
    }
}

MCP Client

The last part is to create a MCP Client that will communicate with the MCP Server.

var clientTransport = new StdioClientTransport(new()
{
    Name = "my-tenant-local",
    Command = "dotnet",
    Arguments = ["run", "--project", "path\to\mcp\server\console\project\folder", "--no-build"],
    EnvironmentVariables = new Dictionary<string, string>()
    {
        { "TENANT_ID", "<tenant_id>" },
        { "CLIENT_ID", "<client_id>" },
        { "CLIENT_SECRET", "<client_secret>" },
        { "NATIONAL_CLOUD", "Global" }
    }
});

await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport, new()
{
    ClientInfo = new Implementation
    {
        Name = "MCP Console App Client",
        Version = "1.0.0"
    }
});

Console.WriteLine($"Connected to server: {mcpClient.ServerInfo.Name} ({mcpClient.ServerInfo.Version})");

Because the server is running locally, we can use the StdioClientTransport class, so the MCP client will communicate with the MCP Server via stdio.

If you want to communicate with the MCP Server via SSE, you can use the SseClientTransport class.

Now, we can list available tools exposed by the MCP Server and instantiates the AnthropicService and AzureOpenAIService services.

var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
    Console.WriteLine($"Tool: {tool.Name}, {tool.Description}");
}

using var anthropicService = new AnthropicService(tools);
using var azureService = new AzureOpenAIService(tools);

And the main loop

Console.WriteLine("Enter 1 for anthropic, 2 for azure (or 'exit' to quit):");

while (Console.ReadLine() is string input && !"exit".Equals(input, StringComparison.OrdinalIgnoreCase))
{
    Console.WriteLine("Enter a message (or 'service' to change service or 'exit' to quit):");

    while (Console.ReadLine() is string message && !"service".Equals(message, StringComparison.OrdinalIgnoreCase))
    {
        if (message.Equals("exit", StringComparison.OrdinalIgnoreCase))
        {
            input = "exit";
                break;
        }

        if (!string.IsNullOrWhiteSpace(message))
        {
            try
            {
                if (input == "1")
                {
                    var chatResponse = await anthropicService.GetResponseAsync(message);
                    Console.Write(chatResponse);
                }
                else if (input == "2")
                {
                    var chatResponse = await azureService.GetResponseAsync(message);
                    Console.Write(chatResponse);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
            Console.WriteLine();
        }

        Console.WriteLine("Enter a message (or 'service' to change service):");
    }

    if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
    {
        break;
    }

    Console.WriteLine("Enter 1 for anthropic, 2 for azure (or 'exit' to quit):");
}

You can find the full code in my GitHub repository.

Conclusion

In this article, I showed you how to create a simple MCP Client that communicates with the MCP Server for the Microsoft Graph API and how to use it with the Anthropic Claude API and Azure OpenAI service.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x