diff --git a/samples/ProtectedMcpServer/README.md b/samples/ProtectedMcpServer/README.md index ecbfee633..466ced15d 100644 --- a/samples/ProtectedMcpServer/README.md +++ b/samples/ProtectedMcpServer/README.md @@ -10,6 +10,7 @@ The Protected MCP Server sample shows how to: - Implement protected MCP tools and resources - Integrate with ASP.NET Core authentication and authorization - Provide OAuth resource metadata for client discovery +- Access the authenticated caller directly in MCP tools using `ClaimsPrincipal` parameter injection ## Prerequisites @@ -78,6 +79,19 @@ The server is configured to: - Require tokens to have appropriate scopes (`mcp:tools`) - Provide OAuth resource metadata for client discovery +### Accessing the Authenticated User + +MCP tools can access the authenticated caller by directly injecting a +`ClaimsPrincipal` parameter: + +```csharp +public async Task GetAlerts(ClaimsPrincipal? user, string state) +{ + var userName = user?.Identity?.Name ?? "anonymous"; + // ... +} +``` + ## Architecture The server uses: diff --git a/samples/ProtectedMcpServer/Tools/WeatherTools.cs b/samples/ProtectedMcpServer/Tools/WeatherTools.cs index 94cc03892..3df165172 100644 --- a/samples/ProtectedMcpServer/Tools/WeatherTools.cs +++ b/samples/ProtectedMcpServer/Tools/WeatherTools.cs @@ -2,6 +2,7 @@ using ModelContextProtocol.Server; using System.ComponentModel; using System.Globalization; +using System.Security.Claims; using System.Text.Json; namespace ProtectedMcpServer.Tools; @@ -17,7 +18,7 @@ public WeatherTools(IHttpClientFactory httpClientFactory) } [McpServerTool, Description("Get weather alerts for a US state.")] - public async Task GetAlerts( + public async Task GetAlerts(ClaimsPrincipal? user, [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"); @@ -25,16 +26,18 @@ public async Task GetAlerts( ?? throw new McpException("No JSON returned from alerts endpoint"); var alerts = jsonDocument.RootElement.GetProperty("features").EnumerateArray(); + string username = user?.Identity?.Name ?? "Anonymous"; if (!alerts.Any()) { - return "No active alerts for this state."; + return $"Hi {username}, no active alerts for this state."; } return string.Join("\n--\n", alerts.Select(alert => { JsonElement properties = alert.GetProperty("properties"); return $""" + Hi {username}, the alert details are as below: Event: {properties.GetProperty("event").GetString()} Area: {properties.GetProperty("areaDesc").GetString()} Severity: {properties.GetProperty("severity").GetString()} @@ -45,7 +48,7 @@ public async Task GetAlerts( } [McpServerTool, Description("Get weather forecast for a location.")] - public async Task GetForecast( + public async Task GetForecast(ClaimsPrincipal? user, [Description("Latitude of the location.")] double latitude, [Description("Longitude of the location.")] double longitude) { @@ -60,7 +63,10 @@ public async Task GetForecast( var periods = forecastDocument?.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray() ?? throw new McpException("No JSON returned from forecast endpoint"); + string username = user?.Identity?.Name ?? "Anonymous"; + return string.Join("\n---\n", periods.Select(period => $""" + Hi {username}, the forecast details are as below: {period.GetProperty("name").GetString()} Temperature: {period.GetProperty("temperature").GetInt32()}°F Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}