Model Context Protocol (MCP)
8 minute read
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
| Component | Description |
|---|---|
| Host | The environment where the AI model runs (VS Code, Claude Desktop, custom apps) |
| Client | Manages connections to MCP servers within the host |
| Server | Lightweight applications exposing tools, resources, and prompts via MCP |
MCP Capabilities
MCP servers can provide:
- Tools - Functions that the AI can call to perform actions
- Resources - Data sources the AI can read (files, databases, APIs)
- 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
| Package | Purpose |
|---|---|
ModelContextProtocol | Core MCP SDK for .NET |
ModelContextProtocol.AspNetCore | ASP.NET Core integration for hosting MCP servers |
Microsoft.Extensions.AI | AI abstractions used by MCP |
Microsoft.Extensions.AI.OpenAI | OpenAI 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
| Aspect | MCP | REST API |
|---|---|---|
| Purpose | AI tool integration | General-purpose data access |
| Discovery | Automatic tool discovery | Manual endpoint documentation |
| Typing | Strongly typed with descriptions | Schema-based (OpenAPI) |
| Context | Session-aware, maintains context | Stateless by default |
| AI Optimization | Designed for LLM function calling | Requires adapter layer |
| Transport | Stdio, SSE, HTTP | HTTP only |
- Standardized AI integration pattern
- Automatic tool discovery and documentation
- Built-in context management
- Works with multiple AI providers
- Growing ecosystem of prebuilt servers
- 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