using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; using ModelContextProtocol.AspNetCore.Authentication; using ModelContextProtocol.AspNetCore; 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"; var allowedOrigins = builder.Configuration.GetSection("Mcp:AllowedOrigins").Get() ?? ["http://localhost:5173"]; // This sample runs the MCP server on localhost:7071, and it is intended to be callable from a // companion web app running on a different host (localhost:5173), while preventing requests from // other origins. This scenario requires enabling CORS and enabling a restrictive CORS policy. // // If your server is not meant for cross-origin browser access, leave CORS disabled. // // Only apply a lenient CORS policy if your server is intended to be callable from any browser. builder.Services.AddCors(options => { options.AddPolicy("McpBrowserClient", policy => { policy.WithOrigins(allowedOrigins) .WithMethods("POST") .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "MCP-Protocol-Version") .WithExposedHeaders(HeaderNames.WWWAuthenticate); }); }); 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 the default and recommended for servers that don't need 2025-11-25 // protocol revision server-to-client requests like sampling or elicitation. Stateless model enables // horizontal scaling without session affinity and works with clients that don't send Mcp-Session-Id. options.SessionMode = HttpServerSessionMode.Stateless; }); // 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.UseCors(); app.UseAuthentication(); app.UseAuthorization(); // Use the default MCP policy name that we've configured app.MapMcp().RequireAuthorization().RequireCors("McpBrowserClient"); 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);