diff --git a/src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs b/src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs
index a549ec384..3f5870700 100644
--- a/src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs
+++ b/src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs
@@ -43,31 +43,39 @@ public void PostConfigure(string? name, McpServerOptions options)
private void ConfigureListToolsFilter(McpServerOptions options)
{
- options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListToolsFilters.Add(next =>
{
- context.Items[AuthorizationFilterInvokedKey] = true;
-
- var result = await next(context, cancellationToken);
- await FilterAuthorizedItemsAsync(
- result.Tools, static tool => tool.McpServerTool,
- context.User, context.Services, context);
- return result;
+ var toolCollection = options.ToolCollection;
+ return async (context, cancellationToken) =>
+ {
+ context.Items[AuthorizationFilterInvokedKey] = true;
+
+ var result = await next(context, cancellationToken);
+ await FilterAuthorizedItemsAsync(
+ result.Tools, tool => toolCollection is not null && toolCollection.TryGetPrimitive(tool.Name, out var serverTool) ? serverTool : null,
+ context.User, context.Services, context);
+ return result;
+ };
});
}
private static void CheckListToolsFilter(McpServerOptions options)
{
- options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListToolsFilters.Add(next =>
{
- var result = await next(context, cancellationToken);
-
- if (HasAuthorizationMetadata(result.Tools.Select(static tool => tool.McpServerTool))
- && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ var toolCollection = options.ToolCollection;
+ return async (context, cancellationToken) =>
{
- throw new InvalidOperationException("Authorization filter was not invoked for tools/list operation, but authorization metadata was found on the tools. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
- }
+ var result = await next(context, cancellationToken);
+
+ if (HasAuthorizationMetadata(result.Tools.Select(tool => toolCollection is not null && toolCollection.TryGetPrimitive(tool.Name, out var serverTool) ? serverTool : null))
+ && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ {
+ throw new InvalidOperationException("Authorization filter was not invoked for tools/list operation, but authorization metadata was found on the tools. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
+ }
- return result;
+ return result;
+ };
});
}
@@ -103,61 +111,77 @@ private static void CheckCallToolFilter(McpServerOptions options)
private void ConfigureListResourcesFilter(McpServerOptions options)
{
- options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListResourcesFilters.Add(next =>
{
- context.Items[AuthorizationFilterInvokedKey] = true;
-
- var result = await next(context, cancellationToken);
- await FilterAuthorizedItemsAsync(
- result.Resources, static resource => resource.McpServerResource,
- context.User, context.Services, context);
- return result;
+ var resourceCollection = options.ResourceCollection;
+ return async (context, cancellationToken) =>
+ {
+ context.Items[AuthorizationFilterInvokedKey] = true;
+
+ var result = await next(context, cancellationToken);
+ await FilterAuthorizedItemsAsync(
+ result.Resources, resource => resourceCollection is not null && resourceCollection.TryGetPrimitive(resource.Uri, out var serverResource) ? serverResource : null,
+ context.User, context.Services, context);
+ return result;
+ };
});
}
private static void CheckListResourcesFilter(McpServerOptions options)
{
- options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListResourcesFilters.Add(next =>
{
- var result = await next(context, cancellationToken);
-
- if (HasAuthorizationMetadata(result.Resources.Select(static resource => resource.McpServerResource))
- && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ var resourceCollection = options.ResourceCollection;
+ return async (context, cancellationToken) =>
{
- throw new InvalidOperationException("Authorization filter was not invoked for resources/list operation, but authorization metadata was found on the resources. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
- }
+ var result = await next(context, cancellationToken);
+
+ if (HasAuthorizationMetadata(result.Resources.Select(resource => resourceCollection is not null && resourceCollection.TryGetPrimitive(resource.Uri, out var serverResource) ? serverResource : null))
+ && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ {
+ throw new InvalidOperationException("Authorization filter was not invoked for resources/list operation, but authorization metadata was found on the resources. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
+ }
- return result;
+ return result;
+ };
});
}
private void ConfigureListResourceTemplatesFilter(McpServerOptions options)
{
- options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListResourceTemplatesFilters.Add(next =>
{
- context.Items[AuthorizationFilterInvokedKey] = true;
-
- var result = await next(context, cancellationToken);
- await FilterAuthorizedItemsAsync(
- result.ResourceTemplates, static resourceTemplate => resourceTemplate.McpServerResource,
- context.User, context.Services, context);
- return result;
+ var resourceCollection = options.ResourceCollection;
+ return async (context, cancellationToken) =>
+ {
+ context.Items[AuthorizationFilterInvokedKey] = true;
+
+ var result = await next(context, cancellationToken);
+ await FilterAuthorizedItemsAsync(
+ result.ResourceTemplates, resourceTemplate => resourceCollection is not null && resourceCollection.TryGetPrimitive(resourceTemplate.UriTemplate, out var serverResource) ? serverResource : null,
+ context.User, context.Services, context);
+ return result;
+ };
});
}
private static void CheckListResourceTemplatesFilter(McpServerOptions options)
{
- options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListResourceTemplatesFilters.Add(next =>
{
- var result = await next(context, cancellationToken);
-
- if (HasAuthorizationMetadata(result.ResourceTemplates.Select(static resourceTemplate => resourceTemplate.McpServerResource))
- && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ var resourceCollection = options.ResourceCollection;
+ return async (context, cancellationToken) =>
{
- throw new InvalidOperationException("Authorization filter was not invoked for resources/templates/list operation, but authorization metadata was found on the resource templates. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
- }
+ var result = await next(context, cancellationToken);
+
+ if (HasAuthorizationMetadata(result.ResourceTemplates.Select(resourceTemplate => resourceCollection is not null && resourceCollection.TryGetPrimitive(resourceTemplate.UriTemplate, out var serverResource) ? serverResource : null))
+ && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ {
+ throw new InvalidOperationException("Authorization filter was not invoked for resources/templates/list operation, but authorization metadata was found on the resource templates. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
+ }
- return result;
+ return result;
+ };
});
}
@@ -193,31 +217,39 @@ private static void CheckReadResourceFilter(McpServerOptions options)
private void ConfigureListPromptsFilter(McpServerOptions options)
{
- options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListPromptsFilters.Add(next =>
{
- context.Items[AuthorizationFilterInvokedKey] = true;
-
- var result = await next(context, cancellationToken);
- await FilterAuthorizedItemsAsync(
- result.Prompts, static prompt => prompt.McpServerPrompt,
- context.User, context.Services, context);
- return result;
+ var promptCollection = options.PromptCollection;
+ return async (context, cancellationToken) =>
+ {
+ context.Items[AuthorizationFilterInvokedKey] = true;
+
+ var result = await next(context, cancellationToken);
+ await FilterAuthorizedItemsAsync(
+ result.Prompts, prompt => promptCollection is not null && promptCollection.TryGetPrimitive(prompt.Name, out var serverPrompt) ? serverPrompt : null,
+ context.User, context.Services, context);
+ return result;
+ };
});
}
private static void CheckListPromptsFilter(McpServerOptions options)
{
- options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
+ options.Filters.Request.ListPromptsFilters.Add(next =>
{
- var result = await next(context, cancellationToken);
-
- if (HasAuthorizationMetadata(result.Prompts.Select(static prompt => prompt.McpServerPrompt))
- && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ var promptCollection = options.PromptCollection;
+ return async (context, cancellationToken) =>
{
- throw new InvalidOperationException("Authorization filter was not invoked for prompts/list operation, but authorization metadata was found on the prompts. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
- }
+ var result = await next(context, cancellationToken);
+
+ if (HasAuthorizationMetadata(result.Prompts.Select(prompt => promptCollection is not null && promptCollection.TryGetPrimitive(prompt.Name, out var serverPrompt) ? serverPrompt : null))
+ && !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
+ {
+ throw new InvalidOperationException("Authorization filter was not invoked for prompts/list operation, but authorization metadata was found on the prompts. Ensure that AddAuthorizationFilters() is called on the IMcpServerBuilder to configure authorization filters.");
+ }
- return result;
+ return result;
+ };
});
}
diff --git a/src/ModelContextProtocol.Core/Protocol/Prompt.cs b/src/ModelContextProtocol.Core/Protocol/Prompt.cs
index 9f5534a31..bc4624f1d 100644
--- a/src/ModelContextProtocol.Core/Protocol/Prompt.cs
+++ b/src/ModelContextProtocol.Core/Protocol/Prompt.cs
@@ -1,7 +1,6 @@
using System.Diagnostics;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@@ -72,12 +71,6 @@ public sealed class Prompt : IBaseMetadata
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
- ///
- /// Gets or sets the callable server prompt corresponding to this metadata if any.
- ///
- [JsonIgnore]
- public McpServerPrompt? McpServerPrompt { get; set; }
-
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
diff --git a/src/ModelContextProtocol.Core/Protocol/Resource.cs b/src/ModelContextProtocol.Core/Protocol/Resource.cs
index be2a4aa60..42bf22c57 100644
--- a/src/ModelContextProtocol.Core/Protocol/Resource.cs
+++ b/src/ModelContextProtocol.Core/Protocol/Resource.cs
@@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@@ -101,10 +100,4 @@ public sealed class Resource : IBaseMetadata
///
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
-
- ///
- /// Gets or sets the callable server resource corresponding to this metadata if any.
- ///
- [JsonIgnore]
- public McpServerResource? McpServerResource { get; set; }
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs b/src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs
index af37a3164..09edb09fc 100644
--- a/src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs
@@ -1,6 +1,5 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@@ -94,12 +93,6 @@ public sealed class ResourceTemplate : IBaseMetadata
[JsonIgnore]
public bool IsTemplated => UriTemplate.Contains('{');
- ///
- /// Gets or sets the callable server resource corresponding to this metadata, if any.
- ///
- [JsonIgnore]
- public McpServerResource? McpServerResource { get; set; }
-
/// Converts the into a .
/// A if is ; otherwise, .
public Resource? AsResource()
@@ -119,7 +112,6 @@ public sealed class ResourceTemplate : IBaseMetadata
Annotations = Annotations,
Icons = Icons,
Meta = Meta,
- McpServerResource = McpServerResource,
};
}
}
diff --git a/src/ModelContextProtocol.Core/Protocol/Tool.cs b/src/ModelContextProtocol.Core/Protocol/Tool.cs
index dfce0eaa1..f70a1d260 100644
--- a/src/ModelContextProtocol.Core/Protocol/Tool.cs
+++ b/src/ModelContextProtocol.Core/Protocol/Tool.cs
@@ -1,4 +1,3 @@
-using ModelContextProtocol.Server;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
@@ -158,12 +157,6 @@ public ToolExecution? Execution
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
- ///
- /// Gets or sets the callable server tool corresponding to this metadata if any.
- ///
- [JsonIgnore]
- public McpServerTool? McpServerTool { get; set; }
-
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay
{
diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs
index c55c76465..c755cd9e5 100644
--- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs
+++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs
@@ -182,7 +182,6 @@ private AIFunctionMcpServerPrompt(AIFunction function, Prompt prompt, IReadOnlyL
{
AIFunction = function;
ProtocolPrompt = prompt;
- ProtocolPrompt.McpServerPrompt = this;
_metadata = metadata;
}
diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs
index 469ba5d37..3413d7038 100644
--- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs
+++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs
@@ -294,7 +294,6 @@ private AIFunctionMcpServerResource(AIFunction function, ResourceTemplate resour
{
AIFunction = function;
ProtocolResourceTemplate = resourceTemplate;
- ProtocolResourceTemplate.McpServerResource = this;
ProtocolResource = resourceTemplate.AsResource();
_metadata = metadata;
diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
index f72e5a483..f29d8fef1 100644
--- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
+++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
@@ -234,7 +234,6 @@ private AIFunctionMcpServerTool(AIFunction function, Tool tool, IServiceProvider
AIFunction = function;
ProtocolTool = tool;
- ProtocolTool.McpServerTool = this;
_structuredOutputRequiresWrapping = structuredOutputRequiresWrapping;
_metadata = metadata;
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs
index 33535cc09..76a7201d8 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs
@@ -398,6 +398,105 @@ log.Exception is InvalidOperationException &&
log.Exception.Message.Contains("Ensure that AddAuthorizationFilters() is called"));
}
+ [Fact]
+ public async Task ListTools_WithHandlerAndNullCollection_AllToolsVisible()
+ {
+ // When ToolCollection is null (custom handler only), the auth filter can't look up
+ // primitives in the collection and should not filter any tools.
+ await using var app = await StartServerWithAuth(builder =>
+ builder.WithListToolsHandler(static (_, _) => ValueTask.FromResult(new ListToolsResult
+ {
+ Tools = [new Tool { Name = "custom_tool" }]
+ })));
+
+ var client = await ConnectAsync();
+ var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+
+ // Tool from custom handler (not in ToolCollection) should be visible even to anonymous users
+ Assert.Single(tools);
+ Assert.Equal("custom_tool", tools[0].Name);
+ }
+
+ [Fact]
+ public async Task ListTools_WithMixedCollectionAndHandler_HandlerToolsNotFiltered()
+ {
+ // Tools in the ToolCollection are filtered based on auth metadata.
+ // Tools returned only from a custom handler (not in ToolCollection) are not filtered.
+ await using var app = await StartServerWithAuth(builder =>
+ {
+ builder.WithTools();
+ builder.WithListToolsHandler(static (_, _) => ValueTask.FromResult(new ListToolsResult
+ {
+ Tools = [new Tool { Name = "handler_tool" }]
+ }));
+ });
+
+ var client = await ConnectAsync();
+ var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+
+ // Anonymous user: anonymous_tool from collection + handler_tool (not in collection, so not filtered)
+ Assert.Equal(2, tools.Count);
+ var toolNames = tools.Select(t => t.Name).OrderBy(n => n).ToList();
+ Assert.Equal(["anonymous_tool", "handler_tool"], toolNames);
+ }
+
+ [Fact]
+ public async Task ListPrompts_WithHandlerAndNullCollection_AllPromptsVisible()
+ {
+ // When PromptCollection is null (custom handler only), the auth filter can't look up
+ // primitives in the collection and should not filter any prompts.
+ await using var app = await StartServerWithAuth(builder =>
+ builder.WithListPromptsHandler(static (_, _) => ValueTask.FromResult(new ListPromptsResult
+ {
+ Prompts = [new Prompt { Name = "custom_prompt" }]
+ })));
+
+ var client = await ConnectAsync();
+ var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
+
+ // Prompt from custom handler (not in PromptCollection) should be visible even to anonymous users
+ Assert.Single(prompts);
+ Assert.Equal("custom_prompt", prompts[0].Name);
+ }
+
+ [Fact]
+ public async Task ListResources_WithHandlerAndNullCollection_AllResourcesVisible()
+ {
+ // When ResourceCollection is null (custom handler only), the auth filter can't look up
+ // primitives in the collection and should not filter any resources.
+ await using var app = await StartServerWithAuth(builder =>
+ builder.WithListResourcesHandler(static (_, _) => ValueTask.FromResult(new ListResourcesResult
+ {
+ Resources = [new Resource { Name = "custom_resource", Uri = "resource://custom" }]
+ })));
+
+ var client = await ConnectAsync();
+ var resources = await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
+
+ // Resource from custom handler (not in ResourceCollection) should be visible even to anonymous users
+ Assert.Single(resources);
+ Assert.Equal("resource://custom", resources[0].Uri);
+ }
+
+ [Fact]
+ public async Task ListResourceTemplates_WithHandlerAndNullCollection_AllResourceTemplatesVisible()
+ {
+ // When ResourceCollection is null (custom handler only), the auth filter can't look up
+ // primitives in the collection and should not filter any resource templates.
+ await using var app = await StartServerWithAuth(builder =>
+ builder.WithListResourceTemplatesHandler(static (_, _) => ValueTask.FromResult(new ListResourceTemplatesResult
+ {
+ ResourceTemplates = [new ResourceTemplate { Name = "custom_template", UriTemplate = "resource://custom/{id}" }]
+ })));
+
+ var client = await ConnectAsync();
+ var templates = await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
+
+ // Template from custom handler (not in ResourceCollection) should be visible even to anonymous users
+ Assert.Single(templates);
+ Assert.Equal("resource://custom/{id}", templates[0].UriTemplate);
+ }
+
private async Task StartServerWithAuth(Action configure, string? userName = null, params string[] roles)
{
var mcpServerBuilder = Builder.Services.AddMcpServer().WithHttpTransport().AddAuthorizationFilters();