Skip to content

Commit cc163e1

Browse files
committed
Handle unsuccessful OnResourceMetadataRequest events
1 parent 984aa9a commit cc163e1

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed

src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenticationHandler.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,7 @@ private string GetAbsoluteResourceMetadataUri()
7878
return absoluteUri.ToString();
7979
}
8080

81-
/// <summary>
82-
/// Handles the resource metadata request.
83-
/// </summary>
84-
private async Task HandleResourceMetadataRequestAsync()
81+
private async Task<bool> HandleResourceMetadataRequestAsync()
8582
{
8683
var resourceMetadata = Options.ResourceMetadata;
8784

@@ -93,6 +90,23 @@ private async Task HandleResourceMetadataRequestAsync()
9390
};
9491

9592
await Options.Events.OnResourceMetadataRequest(context);
93+
94+
if (context.Result is not null)
95+
{
96+
if (context.Result.Handled)
97+
{
98+
return true;
99+
}
100+
else if (context.Result.Skipped)
101+
{
102+
return false;
103+
}
104+
else if (context.Result.Failure is not null)
105+
{
106+
throw new AuthenticationFailureException("An error occurred from the OnResourceMetadataRequest event.", context.Result.Failure);
107+
}
108+
}
109+
96110
resourceMetadata = context.ResourceMetadata;
97111
}
98112

@@ -104,6 +118,7 @@ private async Task HandleResourceMetadataRequestAsync()
104118
}
105119

106120
await Results.Json(resourceMetadata, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ProtectedResourceMetadata))).ExecuteAsync(Context);
121+
return true;
107122
}
108123

109124
/// <inheritdoc />

tests/ModelContextProtocol.AspNetCore.Tests/AuthEventTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,87 @@ public async Task ResourceMetadataEndpoint_ThrowsException_WhenNoMetadataProvide
289289
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
290290
}
291291

292+
[Fact]
293+
public async Task ResourceMetadataEndpoint_HandlesResponse_WhenHandleResponseCalled()
294+
{
295+
Builder.Services.AddMcpServer().WithHttpTransport();
296+
297+
// Override the configuration to test HandleResponse behavior
298+
Builder.Services.Configure<McpAuthenticationOptions>(
299+
McpAuthenticationDefaults.AuthenticationScheme,
300+
options =>
301+
{
302+
options.ResourceMetadata = null;
303+
options.Events.OnResourceMetadataRequest = async context =>
304+
{
305+
// Call HandleResponse() to discontinue processing and return to client
306+
context.HandleResponse();
307+
await Task.CompletedTask;
308+
};
309+
}
310+
);
311+
312+
await using var app = Builder.Build();
313+
314+
app.MapMcp().RequireAuthorization();
315+
316+
await app.StartAsync(TestContext.Current.CancellationToken);
317+
318+
// Make a direct request to the resource metadata endpoint
319+
using var response = await HttpClient.GetAsync(
320+
"/.well-known/oauth-protected-resource",
321+
TestContext.Current.CancellationToken
322+
);
323+
324+
// The request should be handled by the event handler without returning metadata
325+
// Since HandleResponse() was called, the handler should have taken responsibility
326+
// for generating the response, which in this case means an empty response
327+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
328+
329+
// The response should be empty since the event handler called HandleResponse()
330+
// but didn't write any content to the response
331+
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
332+
Assert.Empty(content);
333+
}
334+
335+
[Fact]
336+
public async Task ResourceMetadataEndpoint_SkipsHandler_WhenSkipHandlerCalled()
337+
{
338+
Builder.Services.AddMcpServer().WithHttpTransport();
339+
340+
// Override the configuration to test SkipHandler behavior
341+
Builder.Services.Configure<McpAuthenticationOptions>(
342+
McpAuthenticationDefaults.AuthenticationScheme,
343+
options =>
344+
{
345+
options.ResourceMetadata = null;
346+
options.Events.OnResourceMetadataRequest = async context =>
347+
{
348+
// Call SkipHandler() to discontinue processing in the current handler
349+
context.SkipHandler();
350+
await Task.CompletedTask;
351+
};
352+
}
353+
);
354+
355+
await using var app = Builder.Build();
356+
357+
app.MapMcp().RequireAuthorization();
358+
359+
await app.StartAsync(TestContext.Current.CancellationToken);
360+
361+
// Make a direct request to the resource metadata endpoint
362+
using var response = await HttpClient.GetAsync(
363+
"/.well-known/oauth-protected-resource",
364+
TestContext.Current.CancellationToken
365+
);
366+
367+
// When SkipHandler() is called, the authentication handler should skip processing
368+
// and let other handlers in the pipeline handle the request. Since there are no
369+
// other handlers configured for this endpoint, this should result in a 404
370+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
371+
}
372+
292373
private async Task<string?> HandleAuthorizationUrlAsync(
293374
Uri authorizationUri,
294375
Uri redirectUri,

0 commit comments

Comments
 (0)