Model Context Protocol (MCP)

Model Context Protocol - The USB-C port for AI applications connecting to external tools, APIs, and data sources

What is the Model Context Protocol (MCP)?

The Model Context Protocol (MCP) is an open-source standard developed by Anthropic for connecting AI applications to external systems. Think of MCP like a USB-C port for AI applications - just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external data sources, tools, and workflows.

Using MCP, AI applications like Claude, ChatGPT, or GitHub Copilot can connect to:

  • Data sources (local files, databases, cloud storage)
  • Tools (search engines, calculators, APIs)
  • Workflows (specialized prompts, automation pipelines)

This enables AI applications to access key information and perform tasks beyond their training data.

MCP Architecture

MCP follows a client-server architecture:

┌─────────────────────────────────────────────────────────────────┐
│                        MCP Host                                  │
│  (VS Code, Claude Desktop, Custom App)                          │
│                                                                   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐           │
│  │  MCP Client  │  │  MCP Client  │  │  MCP Client  │           │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘           │
└─────────┼─────────────────┼─────────────────┼───────────────────┘
          │                 │                 │
          ▼                 ▼                 ▼
   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
   │  MCP Server  │  │  MCP Server  │  │  MCP Server  │
   │  (Database)  │  │   (Search)   │  │  (Azure)     │
   └──────────────┘  └──────────────┘  └──────────────┘

Core Components

ComponentDescription
HostThe environment where the AI model runs (VS Code, Claude Desktop, custom apps)
ClientManages connections to MCP servers within the host
ServerLightweight applications exposing tools, resources, and prompts via MCP

MCP Capabilities

MCP servers can provide:

  1. Tools - Functions that the AI can call to perform actions
  2. Resources - Data sources the AI can read (files, databases, APIs)
  3. Prompts - Pre-defined prompt templates for common tasks

MCP in .NET

The official MCP C# SDK enables .NET developers to build both MCP clients and servers:

dotnet add package ModelContextProtocol --prerelease

NuGet Packages

PackagePurpose
ModelContextProtocolCore MCP SDK for .NET
ModelContextProtocol.AspNetCoreASP.NET Core integration for hosting MCP servers
Microsoft.Extensions.AIAI abstractions used by MCP
Microsoft.Extensions.AI.OpenAIOpenAI integration

Building an MCP Server in .NET

Minimal MCP Server

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;

HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(settings: null);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

await builder.Build().RunAsync();

MCP Server with Custom Tools

Create tools by decorating methods with [McpServerTool]:

using ModelContextProtocol.Server;
using System.ComponentModel;

/// <summary>
/// MCP tools for weather data retrieval.
/// </summary>
[McpServerToolType]
public sealed class WeatherTools
{
    private readonly IWeatherService _weatherService;

    /// <summary>
    /// Initializes a new instance of the <see cref="WeatherTools"/> class.
    /// </summary>
    public WeatherTools(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }

    /// <summary>
    /// Gets the current weather for a specified city.
    /// </summary>
    [McpServerTool]
    [Description("Gets the current weather conditions for a city")]
    public async Task<WeatherData> GetCurrentWeatherAsync(
        [Description("The city name, e.g., 'Berlin' or 'New York'")] string city,
        [Description("Temperature unit: 'celsius' or 'fahrenheit'")] string unit = "celsius")
    {
        WeatherData weather = await _weatherService.GetCurrentWeatherAsync(city);

        if (unit.Equals("fahrenheit", StringComparison.OrdinalIgnoreCase))
        {
            weather = weather with { Temperature = weather.Temperature * 9 / 5 + 32 };
        }

        return weather;
    }

    /// <summary>
    /// Gets the weather forecast for the next days.
    /// </summary>
    [McpServerTool]
    [Description("Gets weather forecast for the specified number of days")]
    public async Task<IReadOnlyList<WeatherForecast>> GetForecastAsync(
        [Description("The city name")] string city,
        [Description("Number of days to forecast (1-7)")] int days = 3)
    {
        int clampedDays = Math.Clamp(days, 1, 7);
        return await _weatherService.GetForecastAsync(city, clampedDays);
    }
}

/// <summary>
/// Represents weather data.
/// </summary>
public sealed record WeatherData(
    string City,
    double Temperature,
    string Condition,
    int Humidity,
    double WindSpeed);

/// <summary>
/// Represents a weather forecast entry.
/// </summary>
public sealed record WeatherForecast(
    DateOnly Date,
    double HighTemperature,
    double LowTemperature,
    string Condition);

Hosting as ASP.NET Core Application

For remote MCP servers, use the ASP.NET Core integration:

using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Server;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add MCP server services
builder.Services.AddMcpServer()
    .WithTools<WeatherTools>()
    .WithTools<DatabaseTools>();

// Register dependencies
builder.Services.AddSingleton<IWeatherService, OpenWeatherMapService>();
builder.Services.AddScoped<IDatabaseContext, MyDatabaseContext>();

WebApplication app = builder.Build();

// Map MCP endpoint
app.MapMcp("/mcp");

// Alternatively, use Server-Sent Events transport
app.MapMcp("/mcp/sse").WithHttpTransport();

app.Run();

Database Operations as MCP Tools

using Microsoft.EntityFrameworkCore;
using ModelContextProtocol.Server;
using System.ComponentModel;

/// <summary>
/// MCP tools for CRUD operations on Todo items.
/// </summary>
[McpServerToolType]
public sealed class TodoMcpTools
{
    private readonly TodoDbContext _db;

    /// <summary>
    /// Initializes a new instance with database context.
    /// </summary>
    public TodoMcpTools(TodoDbContext db)
    {
        _db = db;
    }

    /// <summary>
    /// Creates a new todo item.
    /// </summary>
    [McpServerTool]
    [Description("Creates a new todo item with a description")]
    public async Task<TodoItem> CreateTodoAsync(
        [Description("Description of the todo")] string description,
        [Description("Due date (optional)")] DateTime? dueDate = null)
    {
        TodoItem todo = new()
        {
            Id = Guid.NewGuid(),
            Description = description,
            DueDate = dueDate,
            IsCompleted = false,
            CreatedAt = DateTime.UtcNow
        };

        _db.Todos.Add(todo);
        await _db.SaveChangesAsync();

        return todo;
    }

    /// <summary>
    /// Retrieves all todos or a specific one by ID.
    /// </summary>
    [McpServerTool]
    [Description("Gets todos - all or filtered by ID")]
    public async Task<IReadOnlyList<TodoItem>> GetTodosAsync(
        [Description("Optional: specific todo ID")] Guid? id = null,
        [Description("Filter by completion status")] bool? completed = null)
    {
        IQueryable<TodoItem> query = _db.Todos.AsNoTracking();

        if (id.HasValue)
        {
            TodoItem? todo = await query.FirstOrDefaultAsync(t => t.Id == id.Value);
            return todo is not null ? [todo] : [];
        }

        if (completed.HasValue)
        {
            query = query.Where(t => t.IsCompleted == completed.Value);
        }

        return await query.OrderBy(t => t.DueDate ?? DateTime.MaxValue).ToListAsync();
    }

    /// <summary>
    /// Marks a todo as completed.
    /// </summary>
    [McpServerTool]
    [Description("Marks a todo item as completed")]
    public async Task<string> CompleteTodoAsync(
        [Description("The ID of the todo to complete")] Guid id)
    {
        TodoItem? todo = await _db.Todos.FindAsync(id);

        if (todo is null)
        {
            return $"Todo with ID {id} not found.";
        }

        todo.IsCompleted = true;
        todo.CompletedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync();

        return $"Todo '{todo.Description}' marked as completed.";
    }

    /// <summary>
    /// Deletes a todo item.
    /// </summary>
    [McpServerTool]
    [Description("Permanently deletes a todo item")]
    public async Task<string> DeleteTodoAsync(
        [Description("The ID of the todo to delete")] Guid id)
    {
        TodoItem? todo = await _db.Todos.FindAsync(id);

        if (todo is null)
        {
            return $"Todo with ID {id} not found.";
        }

        _db.Todos.Remove(todo);
        await _db.SaveChangesAsync();

        return $"Todo '{todo.Description}' deleted.";
    }
}

/// <summary>
/// Represents a todo item.
/// </summary>
public sealed class TodoItem
{
    public Guid Id { get; set; }
    public required string Description { get; set; }
    public DateTime? DueDate { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}

Building an MCP Client in .NET

Connecting to an MCP Server

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;

// Create an AI chat client using Azure OpenAI
IChatClient chatClient = new ChatClientBuilder(
    new AzureOpenAIClient(
        new Uri("https://your/-resource.openai.azure.com"),
        new DefaultAzureCredential())
    .GetChatClient("gpt-4o")
    .AsIChatClient())
    .UseFunctionInvocation()
    .Build();

// Create MCP client connecting to a local server via stdio
IMcpClient mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new StdioClientTransportOptions
    {
        Command = "dotnet",
        Arguments = ["run", "--project", "path/to/mcp-server"],
        Name = "My MCP Server"
    }));

// List available tools from the MCP server
Console.WriteLine("Available MCP tools:");
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
foreach (McpClientTool tool in tools)
{
    Console.WriteLine($"  - {tool.Name}: {tool.Description}");
}

// Use tools in a conversational loop
List<ChatMessage> messages = [];

while (true)
{
    Console.Write("\nYou: ");
    string? input = Console.ReadLine();

    if (string.IsNullOrWhiteSpace(input))
    {
        break;
    }

    messages.Add(new ChatMessage(ChatRole.User, input));

    // Get AI response with tool access
    ChatOptions options = new() { Tools = [.. tools] };

    List<ChatResponseUpdate> updates = [];
    await foreach (ChatResponseUpdate update in chatClient.GetStreamingResponseAsync(messages, options))
    {
        Console.Write(update.Text);
        updates.Add(update);
    }
    Console.WriteLine();

    messages.AddMessages(updates);
}

Connecting to Remote MCP Servers

For remote MCP servers using Server-Sent Events (SSE):

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;

IServiceProvider services = new ServiceCollection()
    .AddLogging(logging => logging.AddConsole())
    .BuildServiceProvider();

ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>();

// Configure SSE transport with authentication
SseClientTransportOptions transportOptions = new()
{
    Endpoint = new Uri("https://mcp/-server.example.com/mcp"),
    AdditionalHeaders = new Dictionary<string, string>
    {
        ["Authorization"] = "Bearer your-jwt-token",
        ["X-API-Key"] = "your-api-key"
    }
};

SseClientTransport transport = new(transportOptions, loggerFactory);

McpClientOptions clientOptions = new()
{
    ClientInfo = new Implementation
    {
        Name = "My MCP Client",
        Version = "1.0.0"
    }
};

IMcpClient client = await McpClientFactory.CreateAsync(transport, clientOptions, loggerFactory);

// Use the client
IList<McpClientTool> tools = await client.ListToolsAsync();

Azure MCP Server

Microsoft provides an official Azure MCP Server for connecting AI agents to Azure services:

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;

IChatClient client = new ChatClientBuilder(
    new AzureOpenAIClient(
        new Uri("https://your/-resource.openai.azure.com"),
        new DefaultAzureCredential())
    .GetChatClient("gpt-4o")
    .AsIChatClient())
    .UseFunctionInvocation()
    .Build();

// Connect to Azure MCP Server
IMcpClient mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new StdioClientTransportOptions
    {
        Command = "npx",
        Arguments = ["-y", "@azure/mcp@latest", "server", "start"],
        Name = "Azure MCP"
    }));

// List Azure tools
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();

foreach (McpClientTool tool in tools)
{
    Console.WriteLine($"{tool.Name}: {tool.Description}");
}

// Now your AI can interact with Azure resources!

The Azure MCP Server provides tools for:

  • Azure Storage (Blob operations)
  • Azure Cosmos DB (document queries)
  • Azure Resource Management
  • And many more Azure services

Exposing an Agent as an MCP Tool

You can wrap existing AI agents as MCP tools:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;

// Assume 'agent' is your existing AI agent
MyAIAgent agent = CreateAgent();

// Wrap the agent as an MCP tool
McpServerTool tool = McpServerTool.Create(agent.AsAIFunction());

// Host it via MCP
HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(settings: null);
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools([tool]);

await builder.Build().RunAsync();

MCP vs REST APIs

AspectMCPREST API
PurposeAI tool integrationGeneral-purpose data access
DiscoveryAutomatic tool discoveryManual endpoint documentation
TypingStrongly typed with descriptionsSchema-based (OpenAPI)
ContextSession-aware, maintains contextStateless by default
AI OptimizationDesigned for LLM function callingRequires adapter layer
TransportStdio, SSE, HTTPHTTP only
✅ MCP Advantages

  • Standardized AI integration pattern
  • Automatic tool discovery and documentation
  • Built-in context management
  • Works with multiple AI providers
  • Growing ecosystem of prebuilt servers

⚠️ MCP Considerations

  • Still in preview for .NET
  • Limited to AI/LLM use cases
  • Requires MCP-compatible client
  • Learning curve for new paradigm

Microsoft Products Supporting MCP

MCP is supported by major Microsoft products:

  • GitHub Copilot - Agent mode in VS Code
  • Copilot Studio - Custom copilot builders
  • Visual Studio Code - MCP server hosting
  • Azure Functions - Remote MCP servers
  • Azure App Service - MCP server deployment

Resources