22using Microsoft . AspNetCore . Http ;
33using Microsoft . Extensions . Logging ;
44using Microsoft . Extensions . Options ;
5+ using ModelContextProtocol . Auth . Types ;
6+ using ModelContextProtocol . Utils . Json ;
57using System . Text . Encodings . Web ;
68
79namespace ModelContextProtocol . AspNetCore . Auth ;
810
911/// <summary>
10- /// Authentication handler for MCP protocol that adds resource metadata to challenge responses.
12+ /// Authentication handler for MCP protocol that adds resource metadata to challenge responses
13+ /// and handles resource metadata endpoint requests.
1114/// </summary>
12- public class McpAuthenticationHandler : AuthenticationHandler < McpAuthenticationOptions >
15+ public class McpAuthenticationHandler : AuthenticationHandler < McpAuthenticationOptions > , IAuthenticationRequestHandler
1316{
17+ private readonly IOptionsMonitor < McpAuthenticationOptions > _optionsMonitor ;
18+
1419 /// <summary>
1520 /// Initializes a new instance of the <see cref="McpAuthenticationHandler"/> class.
1621 /// </summary>
@@ -20,6 +25,60 @@ public McpAuthenticationHandler(
2025 UrlEncoder encoder )
2126 : base ( options , logger , encoder )
2227 {
28+ _optionsMonitor = options ;
29+ }
30+
31+ /// <inheritdoc />
32+ public async Task < bool > HandleRequestAsync ( )
33+ {
34+ // Check if the request is for the resource metadata endpoint
35+ string requestPath = Request . Path . Value ?? string . Empty ;
36+ var options = _optionsMonitor . CurrentValue ;
37+ string resourceMetadataPath = options . ResourceMetadataUri . ToString ( ) ;
38+
39+ // If the path doesn't match, let the request continue through the pipeline
40+ if ( ! string . Equals ( requestPath , resourceMetadataPath , StringComparison . OrdinalIgnoreCase ) )
41+ {
42+ return false ;
43+ }
44+
45+ // This is a request for resource metadata - handle it
46+ await HandleResourceMetadataRequestAsync ( ) ;
47+ return true ;
48+ }
49+
50+ /// <summary>
51+ /// Handles the resource metadata request.
52+ /// </summary>
53+ private async Task HandleResourceMetadataRequestAsync ( )
54+ {
55+ // Get a copy of the resource metadata from options to avoid modifying the original
56+ var options = _optionsMonitor . CurrentValue ;
57+ var metadata = new ProtectedResourceMetadata
58+ {
59+ AuthorizationServers = [ .. options . ResourceMetadata . AuthorizationServers ] ,
60+ BearerMethodsSupported = [ .. options . ResourceMetadata . BearerMethodsSupported ] ,
61+ ScopesSupported = [ .. options . ResourceMetadata . ScopesSupported ] ,
62+ ResourceDocumentation = options . ResourceMetadata . ResourceDocumentation
63+ } ;
64+
65+ // Set default resource if not set
66+ if ( metadata . Resource == null )
67+ {
68+ var request = Request ;
69+ var hostString = request . Host . Value ;
70+ var scheme = request . Scheme ;
71+ metadata . Resource = new Uri ( $ "{ scheme } ://{ hostString } ") ;
72+ }
73+
74+ Response . StatusCode = StatusCodes . Status200OK ;
75+ Response . ContentType = "application/json" ;
76+
77+ var json = System . Text . Json . JsonSerializer . Serialize (
78+ metadata ,
79+ McpJsonUtilities . DefaultOptions . GetTypeInfo ( typeof ( ProtectedResourceMetadata ) ) ) ;
80+
81+ await Response . WriteAsync ( json ) ;
2382 }
2483
2584 /// <inheritdoc />
@@ -36,18 +95,29 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
3695 // Set the response status code
3796 Response . StatusCode = StatusCodes . Status401Unauthorized ;
3897
98+ // Get the current options to ensure we have the latest values
99+ var options = _optionsMonitor . CurrentValue ;
100+
39101 // Generate the full resource metadata URL based on the current request
40102 var baseUrl = $ "{ Request . Scheme } ://{ Request . Host } ";
41103
42- // Properly parse and validate the ResourceMetadataUri
43- if ( ! Uri . TryCreate ( Options . ResourceMetadataUri . ToString ( ) , UriKind . Absolute , out var prmDocumentUri ) )
44- throw new InvalidOperationException ( "Invalid ResourceMetadataUri in options." ) ;
45-
46- // Verify that the URI scheme starts with "http"
47- if ( ! prmDocumentUri . Scheme . StartsWith ( "http" , StringComparison . OrdinalIgnoreCase ) )
48- throw new InvalidOperationException ( "ResourceMetadataUri must use HTTP or HTTPS scheme." ) ;
49-
50- var rawPrmDocumentUri = prmDocumentUri . ToString ( ) ;
104+ string resourceMetadataUriString = options . ResourceMetadataUri . ToString ( ) ;
105+ string rawPrmDocumentUri ;
106+
107+ // Check if the URI is relative or absolute
108+ if ( options . ResourceMetadataUri . IsAbsoluteUri )
109+ {
110+ rawPrmDocumentUri = resourceMetadataUriString ;
111+ }
112+ else
113+ {
114+ // For relative URIs, combine with the base URL
115+ if ( ! Uri . TryCreate ( baseUrl + resourceMetadataUriString , UriKind . Absolute , out var absoluteUri ) )
116+ {
117+ throw new InvalidOperationException ( "Could not create absolute URI for resource metadata." ) ;
118+ }
119+ rawPrmDocumentUri = absoluteUri . ToString ( ) ;
120+ }
51121
52122 // Initialize properties if null
53123 properties ??= new AuthenticationProperties ( ) ;
0 commit comments