Use this file to discover all available pages before exploring further.
The ProtectedMcpServer example demonstrates how to secure an MCP server using OAuth 2.0 authentication with JWT bearer tokens. This is essential for production deployments where you need to control access to your MCP tools and resources.
Set up JWT bearer authentication and MCP-specific OAuth configuration.
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.IdentityModel.Tokens;using ModelContextProtocol.AspNetCore.Authentication;using ProtectedMcpServer.Tools;using System.Net.Http.Headers;using System.Security.Claims;var builder = WebApplication.CreateBuilder(args);var serverUrl = "http://localhost:7071/";var inMemoryOAuthServerUrl = "https://localhost:7029";builder.Services.AddAuthentication(options =>{ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(options =>{ // Configure to validate tokens from our in-memory OAuth server options.Authority = inMemoryOAuthServerUrl; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = serverUrl, // Validate that the audience matches the resource metadata as suggested in RFC 8707 ValidIssuer = inMemoryOAuthServerUrl, NameClaimType = "name", RoleClaimType = "roles" }; options.Events = new JwtBearerEvents { OnTokenValidated = context => { var name = context.Principal?.Identity?.Name ?? "unknown"; var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown"; Console.WriteLine($"Token validated for: {name} ({email})"); return Task.CompletedTask; }, OnAuthenticationFailed = context => { Console.WriteLine($"Authentication failed: {context.Exception.Message}"); return Task.CompletedTask; }, OnChallenge = context => { Console.WriteLine($"Challenging client to authenticate with Entra ID"); return Task.CompletedTask; } };}).AddMcp(options =>{ options.ResourceMetadata = new() { ResourceDocumentation = "https://docs.example.com/api/weather", AuthorizationServers = { inMemoryOAuthServerUrl }, ScopesSupported = ["mcp:tools"], };});builder.Services.AddAuthorization();builder.Services.AddHttpContextAccessor();builder.Services.AddMcpServer() .WithTools<WeatherTools>() .WithHttpTransport();// Configure HttpClientFactory for weather.gov APIbuilder.Services.AddHttpClient("WeatherApi", client =>{ client.BaseAddress = new Uri("https://api.weather.gov"); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));});var app = builder.Build();app.UseAuthentication();app.UseAuthorization();// Use the default MCP policy name that we've configuredapp.MapMcp().RequireAuthorization();Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");Console.WriteLine($"Using in-memory OAuth server at {inMemoryOAuthServerUrl}");Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");Console.WriteLine("Press Ctrl+C to stop the server");app.Run(serverUrl);
The .AddMcp() extension configures MCP-specific OAuth resource metadata that clients can discover.
2
Implement the WeatherTools
Protected tools that require authentication to access.
Tools/WeatherTools.cs
using ModelContextProtocol;using ModelContextProtocol.Server;using System.ComponentModel;using System.Globalization;using System.Text.Json;namespace ProtectedMcpServer.Tools;[McpServerToolType]public sealed class WeatherTools{ private readonly IHttpClientFactory _httpClientFactory; public WeatherTools(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [McpServerTool, Description("Get weather alerts for a US state.")] public async Task<string> GetAlerts( [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state) { var client = _httpClientFactory.CreateClient("WeatherApi"); using var jsonDocument = await client.GetFromJsonAsync<JsonDocument>($"/alerts/active/area/{state}") ?? throw new McpException("No JSON returned from alerts endpoint"); var alerts = jsonDocument.RootElement.GetProperty("features").EnumerateArray(); if (!alerts.Any()) { return "No active alerts for this state."; } return string.Join("\n--\n", alerts.Select(alert => { JsonElement properties = alert.GetProperty("properties"); return $""" Event: {properties.GetProperty("event").GetString()} Area: {properties.GetProperty("areaDesc").GetString()} Severity: {properties.GetProperty("severity").GetString()} Description: {properties.GetProperty("description").GetString()} Instruction: {properties.GetProperty("instruction").GetString()} """; })); } [McpServerTool, Description("Get weather forecast for a location.")] public async Task<string> GetForecast( [Description("Latitude of the location.")] double latitude, [Description("Longitude of the location.")] double longitude) { var client = _httpClientFactory.CreateClient("WeatherApi"); var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}"); using var locationDocument = await client.GetFromJsonAsync<JsonDocument>(pointUrl); var forecastUrl = locationDocument?.RootElement.GetProperty("properties").GetProperty("forecast").GetString() ?? throw new McpException($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}"); using var forecastDocument = await client.GetFromJsonAsync<JsonDocument>(forecastUrl); var periods = forecastDocument?.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray() ?? throw new McpException("No JSON returned from forecast endpoint"); return string.Join("\n---\n", periods.Select(period => $""" {period.GetProperty("name").GetString()} Temperature: {period.GetProperty("temperature").GetInt32()}°F Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} Forecast: {period.GetProperty("detailedForecast").GetString()} """)); }}
These tools are identical to unprotected versions - authentication is handled at the HTTP middleware level, not in the tool code.