44using Microsoft . Extensions . Options ;
55using ModelContextProtocol . Authentication ;
66using System . Text . Encodings . Web ;
7- using System . Text . Json ;
87
98namespace ModelContextProtocol . AspNetCore . Authentication ;
109
@@ -34,11 +33,11 @@ public async Task<bool> HandleRequestAsync()
3433 // Check if the request is for the resource metadata endpoint
3534 string requestPath = Request . Path . Value ?? string . Empty ;
3635
37- string expectedMetadataPath = this . Options . ResourceMetadataUri ? . ToString ( ) ?? string . Empty ;
38- if ( this . Options . ResourceMetadataUri != null && ! this . Options . ResourceMetadataUri . IsAbsoluteUri )
36+ string expectedMetadataPath = Options . ResourceMetadataUri ? . ToString ( ) ?? string . Empty ;
37+ if ( Options . ResourceMetadataUri != null && ! Options . ResourceMetadataUri . IsAbsoluteUri )
3938 {
4039 // For relative URIs, it's just the path component.
41- expectedMetadataPath = this . Options . ResourceMetadataUri . OriginalString ;
40+ expectedMetadataPath = Options . ResourceMetadataUri . OriginalString ;
4241 }
4342
4443 // If the path doesn't match, let the request continue through the pipeline
@@ -62,8 +61,7 @@ public async Task<bool> HandleRequestAsync()
6261 /// </summary>
6362 private string GetAbsoluteResourceMetadataUri ( )
6463 {
65- var options = this . Options ;
66- var resourceMetadataUri = options . ResourceMetadataUri ;
64+ var resourceMetadataUri = Options . ResourceMetadataUri ;
6765
6866 string currentPath = resourceMetadataUri ? . ToString ( ) ?? string . Empty ;
6967
@@ -88,47 +86,33 @@ private string GetAbsoluteResourceMetadataUri()
8886 /// Handles the resource metadata request.
8987 /// </summary>
9088 /// <param name="cancellationToken">A token to cancel the operation.</param>
91- private Task HandleResourceMetadataRequestAsync ( CancellationToken cancellationToken = default )
89+ private async Task HandleResourceMetadataRequestAsync ( CancellationToken cancellationToken = default )
9290 {
93- var options = this . Options ;
94- var resourceMetadata = options . GetResourceMetadata ( Request . HttpContext ) ;
91+ var resourceMetadata = Options . ResourceMetadata ;
9592
96- // Create a copy to avoid modifying the original
97- var metadata = new ProtectedResourceMetadata
93+ if ( Options . Events . OnResourceMetadataRequest is not null )
9894 {
99- Resource = resourceMetadata . Resource ?? new Uri ( GetBaseUrl ( ) ) ,
100- AuthorizationServers = [ .. resourceMetadata . AuthorizationServers ] ,
101- BearerMethodsSupported = [ .. resourceMetadata . BearerMethodsSupported ] ,
102- ScopesSupported = [ .. resourceMetadata . ScopesSupported ] ,
103- ResourceDocumentation = resourceMetadata . ResourceDocumentation
104- } ;
105-
106- Response . StatusCode = StatusCodes . Status200OK ;
107- Response . ContentType = "application/json" ;
95+ var context = new ResourceMetadataRequestContext ( Request . HttpContext , Scheme , Options )
96+ {
97+ ResourceMetadata = CloneResourceMetadata ( resourceMetadata ) ,
98+ } ;
10899
109- var json = JsonSerializer . Serialize (
110- metadata ,
111- McpJsonUtilities . DefaultOptions . GetTypeInfo ( typeof ( ProtectedResourceMetadata ) ) ) ;
100+ await Options . Events . OnResourceMetadataRequest ( context ) ;
101+ }
112102
113- return Response . WriteAsync ( json , cancellationToken ) ;
114- }
115103
116- /// <inheritdoc />
117- protected override async Task < AuthenticateResult > HandleAuthenticateAsync ( )
118- {
119- // If ForwardAuthenticate is set, forward the authentication to the specified scheme
120- if ( ! string . IsNullOrEmpty ( Options . ForwardAuthenticate ) &&
121- Options . ForwardAuthenticate != Scheme . Name )
104+ if ( resourceMetadata == null )
122105 {
123- // Simply forward the authentication request to the specified scheme and return its result
124- // This ensures we don't interfere with the authentication process
125- return await Context . AuthenticateAsync ( Options . ForwardAuthenticate ) ;
106+ throw new InvalidOperationException ( "ResourceMetadata has not been configured. Please set McpAuthenticationOptions.ResourceMetadata." ) ;
126107 }
127108
128- // If no forwarding is configured, this handler doesn't perform authentication
129- return AuthenticateResult . NoResult ( ) ;
109+ await Results . Json ( resourceMetadata , McpJsonUtilities . DefaultOptions . GetTypeInfo ( typeof ( ProtectedResourceMetadata ) ) ) . ExecuteAsync ( Context ) ;
130110 }
131111
112+ /// <inheritdoc />
113+ // If no forwarding is configured, this handler doesn't perform authentication
114+ protected override async Task < AuthenticateResult > HandleAuthenticateAsync ( ) => AuthenticateResult . NoResult ( ) ;
115+
132116 /// <inheritdoc />
133117 protected override Task HandleChallengeAsync ( AuthenticationProperties properties )
134118 {
@@ -146,4 +130,31 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
146130
147131 return base . HandleChallengeAsync ( properties ) ;
148132 }
133+
134+ internal ProtectedResourceMetadata ? CloneResourceMetadata ( ProtectedResourceMetadata ? resourceMetadata )
135+ {
136+ if ( resourceMetadata is null )
137+ {
138+ return null ;
139+ }
140+
141+ return new ProtectedResourceMetadata
142+ {
143+ Resource = resourceMetadata . Resource ,
144+ AuthorizationServers = [ .. resourceMetadata . AuthorizationServers ] ,
145+ BearerMethodsSupported = [ .. resourceMetadata . BearerMethodsSupported ] ,
146+ ScopesSupported = [ .. resourceMetadata . ScopesSupported ] ,
147+ JwksUri = resourceMetadata . JwksUri ,
148+ ResourceSigningAlgValuesSupported = resourceMetadata . ResourceSigningAlgValuesSupported is not null ? [ .. resourceMetadata . ResourceSigningAlgValuesSupported ] : null ,
149+ ResourceName = resourceMetadata . ResourceName ,
150+ ResourceDocumentation = resourceMetadata . ResourceDocumentation ,
151+ ResourcePolicyUri = resourceMetadata . ResourcePolicyUri ,
152+ ResourceTosUri = resourceMetadata . ResourceTosUri ,
153+ TlsClientCertificateBoundAccessTokens = resourceMetadata . TlsClientCertificateBoundAccessTokens ,
154+ AuthorizationDetailsTypesSupported = resourceMetadata . AuthorizationDetailsTypesSupported is not null ? [ .. resourceMetadata . AuthorizationDetailsTypesSupported ] : null ,
155+ DpopSigningAlgValuesSupported = resourceMetadata . DpopSigningAlgValuesSupported is not null ? [ .. resourceMetadata . DpopSigningAlgValuesSupported ] : null ,
156+ DpopBoundAccessTokensRequired = resourceMetadata . DpopBoundAccessTokensRequired
157+ } ;
158+ }
159+
149160}
0 commit comments