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() .WithHttpTransport(options => { // Stateless mode is recommended for servers that don't need server-to-client // requests like sampling or elicitation. It enables horizontal scaling without // session affinity and works with clients that don't send Mcp-Session-Id. options.Stateless = true; }); // Configure HttpClientFactory for weather.gov API builder.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 configured app.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);