Skip to content

Commit 583de65

Browse files
committed
Update server configuration
1 parent b37a7b9 commit 583de65

File tree

3 files changed

+62
-17
lines changed

3 files changed

+62
-17
lines changed

samples/ProtectedMCPServer/Program.cs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.AspNetCore.Authentication.JwtBearer;
22
using Microsoft.IdentityModel.Tokens;
33
using ModelContextProtocol.AspNetCore.Auth;
4+
using ModelContextProtocol.Auth.Types;
45
using ProtectedMCPServer.Tools;
56
using System.Net.Http.Headers;
67
using System.Security.Claims;
@@ -67,11 +68,21 @@
6768
})
6869
.AddMcp(options =>
6970
{
70-
// Configure the MCP authentication with the same Entra ID server
71-
options.ResourceMetadata.AuthorizationServers.Add(new Uri($"{instance}{tenantId}/v2.0"));
72-
options.ResourceMetadata.BearerMethodsSupported.Add("header");
73-
options.ResourceMetadata.ScopesSupported.AddRange(["api://167b4284-3f92-4436-92ed-38b38f83ae08/weather.read"]);
74-
options.ResourceMetadata.ResourceDocumentation = new Uri("https://docs.example.com/api/weather");
71+
options.ResourceMetadataProvider = context =>
72+
{
73+
var metadata = new ProtectedResourceMetadata
74+
{
75+
BearerMethodsSupported = { "header" },
76+
ResourceDocumentation = new Uri("https://docs.example.com/api/weather"),
77+
AuthorizationServers = { new Uri($"{instance}{tenantId}/v2.0") }
78+
};
79+
80+
metadata.ScopesSupported.AddRange(new[] {
81+
"api://167b4284-3f92-4436-92ed-38b38f83ae08/weather.read"
82+
});
83+
84+
return metadata;
85+
};
7586
});
7687

7788
// Add authorization services
@@ -104,15 +115,18 @@
104115

105116
Console.WriteLine("Starting MCP server with authorization at http://localhost:7071");
106117
Console.WriteLine("PRM Document URL: http://localhost:7071/.well-known/oauth-protected-resource");
118+
Console.WriteLine(" - This endpoint returns different metadata based on the client type!");
119+
Console.WriteLine(" - Try with different User-Agent headers or add ?mobile query parameter");
107120

108121
Console.WriteLine();
109122
Console.WriteLine("Entra ID (Azure AD) JWT token validation is configured");
110123
Console.WriteLine();
111-
Console.WriteLine("To test the server:");
112-
Console.WriteLine("1. Use an MCP client that supports OAuth flow with Microsoft Entra ID");
113-
Console.WriteLine("2. The client should obtain a token for audience: api://weather-api");
114-
Console.WriteLine("3. The token should be issued by Microsoft Entra ID tenant: " + tenantId);
115-
Console.WriteLine("4. Include this token in the Authorization header of requests");
124+
Console.WriteLine("To test the server with different client types:");
125+
Console.WriteLine("1. Standard client: No special headers needed");
126+
Console.WriteLine("2. Mobile client: Add 'mobile' in User-Agent or use ?mobile query parameter");
127+
Console.WriteLine("3. Partner client: Include 'partner' in User-Agent or add X-Partner-API header");
128+
Console.WriteLine();
129+
Console.WriteLine("Each client type will receive different authorization requirements!");
116130
Console.WriteLine();
117131
Console.WriteLine("Press Ctrl+C to stop the server");
118132

src/ModelContextProtocol.AspNetCore/Auth/McpAuthenticationHandler.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,18 @@ private string GetAbsoluteResourceMetadataUri()
8282
/// </summary>
8383
private async Task HandleResourceMetadataRequestAsync()
8484
{
85-
// Get a copy of the resource metadata from options to avoid modifying the original
85+
// Get resource metadata from options, using the dynamic provider if available
8686
var options = _optionsMonitor.CurrentValue;
87+
var resourceMetadata = options.GetResourceMetadata(Request.HttpContext);
88+
89+
// Create a copy to avoid modifying the original
8790
var metadata = new ProtectedResourceMetadata
8891
{
89-
AuthorizationServers = [.. options.ResourceMetadata.AuthorizationServers],
90-
BearerMethodsSupported = [.. options.ResourceMetadata.BearerMethodsSupported],
91-
ScopesSupported = [.. options.ResourceMetadata.ScopesSupported],
92-
ResourceDocumentation = options.ResourceMetadata.ResourceDocumentation
92+
AuthorizationServers = [.. resourceMetadata.AuthorizationServers],
93+
BearerMethodsSupported = [.. resourceMetadata.BearerMethodsSupported],
94+
ScopesSupported = [.. resourceMetadata.ScopesSupported],
95+
ResourceDocumentation = resourceMetadata.ResourceDocumentation,
96+
Resource = resourceMetadata.Resource
9397
};
9498

9599
// Set default resource if not set
@@ -130,7 +134,7 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
130134

131135
// Set the WWW-Authenticate header with the resource_metadata
132136
string headerValue = $"Bearer realm=\"{Scheme.Name}\"";
133-
headerValue += $", resource_metadata=\"{Uri.EscapeDataString(rawPrmDocumentUri)}\"";
137+
headerValue += $", resource_metadata=\"{rawPrmDocumentUri}\"";
134138

135139
Response.Headers["WWW-Authenticate"] = headerValue;
136140

src/ModelContextProtocol.AspNetCore/Auth/McpAuthenticationOptions.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.AspNetCore.Authentication;
2+
using Microsoft.AspNetCore.Http;
23
using ModelContextProtocol.Auth.Types;
34

45
namespace ModelContextProtocol.AspNetCore.Auth;
@@ -28,11 +29,37 @@ public class McpAuthenticationOptions : AuthenticationSchemeOptions
2829
public Uri ResourceMetadataUri { get; set; } = DefaultResourceMetadataUri;
2930

3031
/// <summary>
31-
/// Gets or sets the protected resource metadata.
32+
/// Gets or sets the static protected resource metadata.
3233
/// </summary>
3334
/// <remarks>
3435
/// This contains the OAuth metadata for the protected resource, including authorization servers,
3536
/// supported scopes, and other information needed for clients to authenticate.
37+
/// This property is used when <see cref="ResourceMetadataProvider"/> is not set.
3638
/// </remarks>
3739
public ProtectedResourceMetadata ResourceMetadata { get; set; } = new ProtectedResourceMetadata();
40+
41+
/// <summary>
42+
/// Gets or sets a delegate that dynamically provides resource metadata based on the HTTP context.
43+
/// </summary>
44+
/// <remarks>
45+
/// When set, this delegate will be called to generate resource metadata for each request,
46+
/// allowing dynamic customization based on the caller or other contextual information.
47+
/// This takes precedence over the static <see cref="ResourceMetadata"/> property.
48+
/// </remarks>
49+
public Func<HttpContext, ProtectedResourceMetadata>? ResourceMetadataProvider { get; set; }
50+
51+
/// <summary>
52+
/// Gets the resource metadata for the current request.
53+
/// </summary>
54+
/// <param name="context">The HTTP context for the current request.</param>
55+
/// <returns>The resource metadata to use for the current request.</returns>
56+
internal ProtectedResourceMetadata GetResourceMetadata(HttpContext context)
57+
{
58+
if (ResourceMetadataProvider != null)
59+
{
60+
return ResourceMetadataProvider(context);
61+
}
62+
63+
return ResourceMetadata;
64+
}
3865
}

0 commit comments

Comments
 (0)