Skip to content

Commit a9ef1f7

Browse files
Copilotstephentoub
andcommitted
Fix trailing blank line in Resource.cs and add authorization filter tests for null collections and primitives not in collections
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 96dc176 commit a9ef1f7

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/ModelContextProtocol.Core/Protocol/Resource.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,4 @@ public sealed class Resource : IBaseMetadata
100100
/// </remarks>
101101
[JsonPropertyName("_meta")]
102102
public JsonObject? Meta { get; set; }
103-
104103
}

tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,105 @@ log.Exception is InvalidOperationException &&
398398
log.Exception.Message.Contains("Ensure that AddAuthorizationFilters() is called"));
399399
}
400400

401+
[Fact]
402+
public async Task ListTools_WithHandlerAndNullCollection_AllToolsVisible()
403+
{
404+
// When ToolCollection is null (custom handler only), the auth filter can't look up
405+
// primitives in the collection and should not filter any tools.
406+
await using var app = await StartServerWithAuth(builder =>
407+
builder.WithListToolsHandler(static (_, _) => ValueTask.FromResult(new ListToolsResult
408+
{
409+
Tools = [new Tool { Name = "custom_tool" }]
410+
})));
411+
412+
var client = await ConnectAsync();
413+
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
414+
415+
// Tool from custom handler (not in ToolCollection) should be visible even to anonymous users
416+
Assert.Single(tools);
417+
Assert.Equal("custom_tool", tools[0].Name);
418+
}
419+
420+
[Fact]
421+
public async Task ListTools_WithMixedCollectionAndHandler_HandlerToolsNotFiltered()
422+
{
423+
// Tools in the ToolCollection are filtered based on auth metadata.
424+
// Tools returned only from a custom handler (not in ToolCollection) are not filtered.
425+
await using var app = await StartServerWithAuth(builder =>
426+
{
427+
builder.WithTools<AuthorizationTestTools>();
428+
builder.WithListToolsHandler(static (_, _) => ValueTask.FromResult(new ListToolsResult
429+
{
430+
Tools = [new Tool { Name = "handler_tool" }]
431+
}));
432+
});
433+
434+
var client = await ConnectAsync();
435+
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
436+
437+
// Anonymous user: anonymous_tool from collection + handler_tool (not in collection, so not filtered)
438+
Assert.Equal(2, tools.Count);
439+
var toolNames = tools.Select(t => t.Name).OrderBy(n => n).ToList();
440+
Assert.Equal(["anonymous_tool", "handler_tool"], toolNames);
441+
}
442+
443+
[Fact]
444+
public async Task ListPrompts_WithHandlerAndNullCollection_AllPromptsVisible()
445+
{
446+
// When PromptCollection is null (custom handler only), the auth filter can't look up
447+
// primitives in the collection and should not filter any prompts.
448+
await using var app = await StartServerWithAuth(builder =>
449+
builder.WithListPromptsHandler(static (_, _) => ValueTask.FromResult(new ListPromptsResult
450+
{
451+
Prompts = [new Prompt { Name = "custom_prompt" }]
452+
})));
453+
454+
var client = await ConnectAsync();
455+
var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
456+
457+
// Prompt from custom handler (not in PromptCollection) should be visible even to anonymous users
458+
Assert.Single(prompts);
459+
Assert.Equal("custom_prompt", prompts[0].Name);
460+
}
461+
462+
[Fact]
463+
public async Task ListResources_WithHandlerAndNullCollection_AllResourcesVisible()
464+
{
465+
// When ResourceCollection is null (custom handler only), the auth filter can't look up
466+
// primitives in the collection and should not filter any resources.
467+
await using var app = await StartServerWithAuth(builder =>
468+
builder.WithListResourcesHandler(static (_, _) => ValueTask.FromResult(new ListResourcesResult
469+
{
470+
Resources = [new Resource { Name = "custom_resource", Uri = "resource://custom" }]
471+
})));
472+
473+
var client = await ConnectAsync();
474+
var resources = await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
475+
476+
// Resource from custom handler (not in ResourceCollection) should be visible even to anonymous users
477+
Assert.Single(resources);
478+
Assert.Equal("resource://custom", resources[0].Uri);
479+
}
480+
481+
[Fact]
482+
public async Task ListResourceTemplates_WithHandlerAndNullCollection_AllResourceTemplatesVisible()
483+
{
484+
// When ResourceCollection is null (custom handler only), the auth filter can't look up
485+
// primitives in the collection and should not filter any resource templates.
486+
await using var app = await StartServerWithAuth(builder =>
487+
builder.WithListResourceTemplatesHandler(static (_, _) => ValueTask.FromResult(new ListResourceTemplatesResult
488+
{
489+
ResourceTemplates = [new ResourceTemplate { Name = "custom_template", UriTemplate = "resource://custom/{id}" }]
490+
})));
491+
492+
var client = await ConnectAsync();
493+
var templates = await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
494+
495+
// Template from custom handler (not in ResourceCollection) should be visible even to anonymous users
496+
Assert.Single(templates);
497+
Assert.Equal("resource://custom/{id}", templates[0].UriTemplate);
498+
}
499+
401500
private async Task<WebApplication> StartServerWithAuth(Action<IMcpServerBuilder> configure, string? userName = null, params string[] roles)
402501
{
403502
var mcpServerBuilder = Builder.Services.AddMcpServer().WithHttpTransport().AddAuthorizationFilters();

0 commit comments

Comments
 (0)