A fully featured C# implementation of Anthropic's Model Context Protocol (MCP)
Mcp.Net is a .NET implementation of the Model Context Protocol (MCP) - a standardized way for apps to talk to AI models and execute tools. Think of it as the "HTTP of AI tool usage" - a clean, consistent way for your app to give AI models the ability to:
β οΈ Pre-1.0 Notice
This is version 0.9.0 - the core is stable but some features are still in development.
See Current Status for details.
Experience MCP (with web-search, scraping, twilio, various demo tools) with OpenAI or Anthropic models in just two steps:
# 1. Start the server with demo tools
dotnet run --project Mcp.Net.Examples.SimpleServer/Mcp.Net.Examples.SimpleServer.csproj
# 2. In a new terminal, run the LLM chat app (requires OpenAI or Anthropic API key)
dotnet run --project Mcp.Net.LLM/Mcp.Net.LLM.csproj
See the LLM demo documentation for more details.
# For building a server (the thing that provides tools)
dotnet add package Mcp.Net.Server
# For building a client (the thing that talks to AI models)
dotnet add package Mcp.Net.Client
using Mcp.Net.Core.Attributes;
using Mcp.Net.Server;
// 1. Create a simple stdio server
var server = new McpServer(
new ServerInfo { Name = "QuickStart Server", Version = "1.0" }
);
// 2. Define tools using simple attributes and POCOs
[McpTool("Calculator", "Math operations")]
public class CalculatorTools
{
// Simple synchronous tool that returns a plain string
[McpTool("add", "Add two numbers")]
public string Add(
[McpParameter(required: true, description: "First number")] double a,
[McpParameter(required: true, description: "Second number")] double b)
{
return $"The sum of {a} and {b} is {a + b}";
}
// Async tool with a POCO return type - easiest approach!
[McpTool("getWeather", "Get weather for a location")]
public async Task<WeatherResponse> GetWeatherAsync(
[McpParameter(required: true, description: "Location")] string location)
{
// Simulate API call
await Task.Delay(100);
// Just return a POCO - no need to deal with ToolCallResult!
return new WeatherResponse
{
Location = location,
Temperature = "72Β°F",
Conditions = "Sunny",
Forecast = new[] { "Clear", "Partly cloudy", "Clear" }
};
}
}
// Simple POCO class
public class WeatherResponse
{
public string Location { get; set; }
public string Temperature { get; set; }
public string Conditions { get; set; }
public string[] Forecast { get; set; }
}
// 3. Register all tools from assembly in one line
server.RegisterToolsFromAssembly(Assembly.GetExecutingAssembly(), serviceProvider);
// 4. Connect to stdio transport and start
await server.ConnectAsync(new StdioTransport());
// Server is now running and ready to process requests!
For more control, you can also register tools directly:
using System.Text.Json;
using Mcp.Net.Core.Models.Content;
using Mcp.Net.Core.Models.Tools;
using Mcp.Net.Server;
// Create server
var server = new McpServer(
new ServerInfo { Name = "Manual Server", Version = "1.0" }
);
// Register tool with explicit schema and handler
server.RegisterTool(
name: "multiply",
description: "Multiply two numbers",
inputSchema: JsonDocument.Parse(@"
{
""type"": ""object"",
""properties"": {
""x"": { ""type"": ""number"" },
""y"": { ""type"": ""number"" }
},
""required"": [""x"", ""y""]
}
").RootElement,
handler: async (args) =>
{
var x = args?.GetProperty("x").GetDouble() ?? 0;
var y = args?.GetProperty("y").GetDouble() ?? 0;
var result = x * y;
// For full control, you can explicitly use ToolCallResult
return new ToolCallResult
{
Content = new[] { new TextContent { Text = $"{x} * {y} = {result}" } }
};
}
);
using Mcp.Net.Client;
// Connect to a stdio server (like Claude or a local MCP server)
var client = new StdioMcpClient("MyApp", "1.0");
await client.Initialize();
// List available tools
var tools = await client.ListTools();
Console.WriteLine($"Available tools: {string.Join(", ", tools.Select(t => t.Name))}");
// Call the add tool
var result = await client.CallTool("add", new { a = 5, b = 3 });
Console.WriteLine(((TextContent)result.Content.First()).Text); // "The sum is 8"
// Call the weather tool
var weatherResult = await client.CallTool("getWeather", new { location = "San Francisco" });
Console.WriteLine(((TextContent)weatherResult.Content.First()).Text);
// "The weather in San Francisco is sunny and 72Β°F"
The MCP server provides multiple ways to configure your server, especially for controlling network settings when using the SSE transport:
// Configure the server with the builder pattern
var builder = new McpServerBuilder()
.WithName("My MCP Server")
.WithVersion("1.0.0")
.WithInstructions("This server provides helpful tools")
// Configure network settings
.UsePort(8080) // Default is 5000
.UseHostname("0.0.0.0") // Default is localhost
// Configure transport mode
.UseSseTransport(); // Uses the port and hostname configured above
When running the server from the command line:
# Run with custom port and hostname
dotnet run --project Mcp.Net.Server --port 8080 --hostname 0.0.0.0
# For cloud environments, binding to 0.0.0.0 is usually required
dotnet run --project Mcp.Net.Server --hostname 0.0.0.0
# Run with stdio transport instead of SSE
dotnet run --project Mcp.Net.Server --stdio
# or use the shorthand
dotnet run --project Mcp.Net.Server -s
# Enable debug-level logging
dotnet run --project Mcp.Net.Server --debug
# or use the shorthand
dotnet run --project Mcp.Net.Server -d
# Specify a custom log file path
dotnet run --project Mcp.Net.Server --log-path /path/to/logfile.log
# Use a specific URL scheme (http or https)
dotnet run --project Mcp.Net.Server --scheme https
# Combine multiple options
dotnet run --project Mcp.Net.Server --stdio --debug --port 8080 --hostname 0.0.0.0
The ServerConfiguration and CommandLineOptions classes handle these arguments:
// CommandLineOptions.cs parses command-line arguments
public static CommandLineOptions Parse(string[] args)
{
var options = new CommandLineOptions(args)
{
UseStdio = args.Contains("--stdio") || args.Contains("-s"),
DebugMode = args.Contains("--debug") || args.Contains("-d"),
LogPath = GetArgumentValue(args, "--log-path") ?? "mcp-server.log",
Port = GetArgumentValue(args, "--port"),
Hostname = GetArgumentValue(args, "--hostname"),
Scheme = GetArgumentValue(args, "--scheme")
};
return options;
}
# Set standard environment variables before running
export MCP_SERVER_PORT=8080
export MCP_SERVER_HOSTNAME=0.0.0.0
export MCP_SERVER_SCHEME=http
# Cloud platform compatibility - many cloud platforms use PORT
export PORT=8080
dotnet run --project Mcp.Net.Server
The ServerConfiguration class handles these environment variables with a priority-based approach:
// ServerConfiguration.cs handles environment variables:
private void LoadFromEnvironmentVariables()
{
// Standard MCP hostname variable
string? envHostname = Environment.GetEnvironmentVariable("MCP_SERVER_HOSTNAME");
if (!string.IsNullOrEmpty(envHostname))
{
Hostname = envHostname;
}
// Cloud platform compatibility - PORT is standard on platforms like Google Cloud Run
string? cloudRunPort = Environment.GetEnvironmentVariable("PORT");
if (!string.IsNullOrEmpty(cloudRunPort) && int.TryParse(cloudRunPort, out int parsedCloudPort))
{
Port = parsedCloudPort;
}
else
{
// Fall back to MCP-specific environment variable
string? envPort = Environment.GetEnvironmentVariable("MCP_SERVER_PORT");
if (!string.IsNullOrEmpty(envPort) && int.TryParse(envPort, out int parsedEnvPort))
{
Port = parsedEnvPort;
}
}
// HTTPS configuration
string? envScheme = Environment.GetEnvironmentVariable("MCP_SERVER_SCHEME");
if (!string.IsNullOrEmpty(envScheme))
{
Scheme = envScheme.ToLowerInvariant();
}
}
The server also reads settings from appsettings.json:
{
"Server": {
"Port": 8080,
"Hostname": "0.0.0.0",
"Scheme": "http"
}
}
The configuration is loaded with a tiered priority approach:
// SseServerBuilder automatically loads from configuration files:
private void ConfigureAppSettings(WebApplicationBuilder builder, string[] args)
{
// Add configuration from multiple sources with priority:
// 1. Command line args (highest)
// 2. Environment variables
// 3. appsettings.json (lowest)
builder.Configuration.AddJsonFile("appsettings.json", optional: true);
builder.Configuration.AddEnvironmentVariables("MCP_");
builder.Configuration.AddCommandLine(args);
}
The server uses this priority order when resolving configuration:
This allows for flexible deployment in various environments, from local development to cloud platforms.
The SSE server includes built-in health check endpoints:
/health
- Overall health status/health/ready
- Readiness check for load balancers/health/live
- Liveness check for container orchestratorsPerfect for web applications, the SSE transport:
Ideal for CLI tools and AI model integration:
var builder = WebApplication.CreateBuilder(args);
// Add MCP server to services
builder.Services.AddMcpServer(b =>
{
b.WithName("My MCP Server")
.WithVersion("1.0.0")
.WithInstructions("Server providing math and weather tools")
.UsePort(8080) // Configure port (default: 5000)
.UseHostname("0.0.0.0") // Configure hostname (default: localhost)
.UseSseTransport(); // Uses the port and hostname configured above
});
// Configure middleware
var app = builder.Build();
app.UseCors(); // If needed
app.UseMcpServer();
await app.RunAsync();
// Return both text and an image
return new ToolCallResult
{
Content = new IContent[]
{
new TextContent { Text = "Here's the chart you requested:" },
new ImageContent
{
MimeType = "image/png",
Data = Convert.ToBase64String(imageBytes)
}
}
};
This implementation is currently at version 0.9.0:
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by Sam Fold