Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
371 changes: 215 additions & 156 deletions docs/concepts/filters.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/Common/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ internal static class Obsoletions
public const string LegacyTitledEnumSchema_DiagnosticId = "MCP9001";
public const string LegacyTitledEnumSchema_Message = "The EnumSchema and LegacyTitledEnumSchema APIs are deprecated as of specification version 2025-11-25 and will be removed in a future major version. See SEP-1330 for more information.";
public const string LegacyTitledEnumSchema_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330";

public const string MessageAndRequestFilter_DiagnosticId = "MCP9002";
public const string MessageAndRequestFilter_Url = "https://github.com/modelcontextprotocol/csharp-sdk/pull/1308";
public const string MessageFilter_Message = "Use WithMessageFilters() instead.";
public const string RequestFilter_Message = "Use WithRequestFilters() instead.";
}
28 changes: 14 additions & 14 deletions src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void PostConfigure(string? name, McpServerOptions options)

private void ConfigureListToolsFilter(McpServerOptions options)
{
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -57,7 +57,7 @@ await FilterAuthorizedItemsAsync(

private static void CheckListToolsFilter(McpServerOptions options)
{
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
{
var result = await next(context, cancellationToken);

Expand All @@ -73,7 +73,7 @@ private static void CheckListToolsFilter(McpServerOptions options)

private void ConfigureCallToolFilter(McpServerOptions options)
{
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.CallToolFilters.Add(next => async (context, cancellationToken) =>
{
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
if (!authResult.Succeeded)
Expand All @@ -89,7 +89,7 @@ private void ConfigureCallToolFilter(McpServerOptions options)

private static void CheckCallToolFilter(McpServerOptions options)
{
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.CallToolFilters.Add(next => async (context, cancellationToken) =>
{
if (HasAuthorizationMetadata(context.MatchedPrimitive)
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
Expand All @@ -103,7 +103,7 @@ private static void CheckCallToolFilter(McpServerOptions options)

private void ConfigureListResourcesFilter(McpServerOptions options)
{
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -117,7 +117,7 @@ await FilterAuthorizedItemsAsync(

private static void CheckListResourcesFilter(McpServerOptions options)
{
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
{
var result = await next(context, cancellationToken);

Expand All @@ -133,7 +133,7 @@ private static void CheckListResourcesFilter(McpServerOptions options)

private void ConfigureListResourceTemplatesFilter(McpServerOptions options)
{
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -147,7 +147,7 @@ await FilterAuthorizedItemsAsync(

private static void CheckListResourceTemplatesFilter(McpServerOptions options)
{
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
{
var result = await next(context, cancellationToken);

Expand All @@ -163,7 +163,7 @@ private static void CheckListResourceTemplatesFilter(McpServerOptions options)

private void ConfigureReadResourceFilter(McpServerOptions options)
{
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -179,7 +179,7 @@ private void ConfigureReadResourceFilter(McpServerOptions options)

private static void CheckReadResourceFilter(McpServerOptions options)
{
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
{
if (HasAuthorizationMetadata(context.MatchedPrimitive)
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
Expand All @@ -193,7 +193,7 @@ private static void CheckReadResourceFilter(McpServerOptions options)

private void ConfigureListPromptsFilter(McpServerOptions options)
{
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -207,7 +207,7 @@ await FilterAuthorizedItemsAsync(

private static void CheckListPromptsFilter(McpServerOptions options)
{
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
{
var result = await next(context, cancellationToken);

Expand All @@ -223,7 +223,7 @@ private static void CheckListPromptsFilter(McpServerOptions options)

private void ConfigureGetPromptFilter(McpServerOptions options)
{
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
{
context.Items[AuthorizationFilterInvokedKey] = true;

Expand All @@ -239,7 +239,7 @@ private void ConfigureGetPromptFilter(McpServerOptions options)

private static void CheckGetPromptFilter(McpServerOptions options)
{
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
{
if (HasAuthorizationMetadata(context.MatchedPrimitive)
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
Expand Down
42 changes: 42 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpMessageFilters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides grouped message filter collections.
/// </summary>
public sealed class McpMessageFilters
{
/// <summary>
/// Gets the filters for all incoming JSON-RPC messages.
/// </summary>
/// <remarks>
/// <para>
/// These filters intercept all incoming JSON-RPC messages before they are processed by the server,
/// including requests, notifications, responses, and errors. The filters can perform logging,
/// authentication, rate limiting, or other cross-cutting concerns that apply to all message types.
/// </para>
/// <para>
/// Message filters are applied before request-specific filters. If a message filter does not call
/// the next handler in the pipeline, the default handlers will not be executed.
/// </para>
/// </remarks>
public IList<McpMessageFilter> IncomingFilters { get; } = [];

/// <summary>
/// Gets the filters for all outgoing JSON-RPC messages.
/// </summary>
/// <remarks>
/// <para>
/// These filters intercept all outgoing JSON-RPC messages before they are sent to the client,
/// including responses, notifications, and errors. The filters can perform logging,
/// redaction, auditing, or other cross-cutting concerns that apply to all message types.
/// </para>
/// <para>
/// If a message filter does not call the next handler in the pipeline, the message will not be sent.
/// Filters may also call the next handler multiple times with different messages to emit additional
/// server-to-client messages.
/// </para>
/// </remarks>
public IList<McpMessageFilter> OutgoingFilters { get; } = [];
}
157 changes: 157 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpRequestFilters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol.Server;

/// <summary>
/// Provides grouped request-specific filter collections.
/// </summary>
public sealed class McpRequestFilters
{
/// <summary>
/// Gets the filters for the list-tools handler pipeline.
/// </summary>
/// <remarks>
/// <para>
/// These filters wrap handlers that return a list of available tools when requested by a client.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ToolsList"/> requests. It supports pagination through the cursor mechanism,
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more tools.
/// </para>
/// <para>
/// These filters work alongside any tools defined in the <see cref="McpServerTool"/> collection.
/// Tools from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public IList<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];

/// <summary>
/// Gets the filters for the call-tool handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that are invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
/// </remarks>
public IList<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = [];

/// <summary>
/// Gets the filters for the list-prompts handler pipeline.
/// </summary>
/// <remarks>
/// <para>
/// These filters wrap handlers that return a list of available prompts when requested by a client.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.PromptsList"/> requests. It supports pagination through the cursor mechanism,
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more prompts.
/// </para>
/// <para>
/// These filters work alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
/// Prompts from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public IList<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = [];

/// <summary>
/// Gets the filters for the get-prompt handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that are invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
/// </remarks>
public IList<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = [];

/// <summary>
/// Gets the filters for the list-resource-templates handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that return a list of available resource templates when requested by a client.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. It supports pagination through the cursor mechanism,
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resource templates.
/// </remarks>
public IList<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = [];

/// <summary>
/// Gets the filters for the list-resources handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that return a list of available resources when requested by a client.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesList"/> requests. It supports pagination through the cursor mechanism,
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resources.
/// </remarks>
public IList<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = [];

/// <summary>
/// Gets the filters for the read-resource handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that are invoked when a client requests the content of a specific resource identified by its URI.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
/// </remarks>
public IList<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = [];

/// <summary>
/// Gets the filters for the complete-handler pipeline.
/// </summary>
/// <remarks>
/// These filters wrap handlers that provide auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
/// reference type and current argument value.
/// </remarks>
public IList<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = [];

/// <summary>
/// Gets the filters for the subscribe-to-resources handler pipeline.
/// </summary>
/// <remarks>
/// <para>
/// These filters wrap handlers that are invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesSubscribe"/> requests. The handler should implement logic to register the client's interest in the specified resources
/// and set up the necessary infrastructure to send notifications when those resources change.
/// </para>
/// <para>
/// After a successful subscription, the server should send resource change notifications to the client
/// whenever a relevant resource is created, updated, or deleted.
/// </para>
/// </remarks>
public IList<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = [];

/// <summary>
/// Gets the filters for the unsubscribe-from-resources handler pipeline.
/// </summary>
/// <remarks>
/// <para>
/// These filters wrap handlers that are invoked when a client wants to stop receiving notifications about previously subscribed resources.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.ResourcesUnsubscribe"/> requests. The handler should implement logic to remove the client's subscriptions to the specified resources
/// and clean up any associated resources.
/// </para>
/// <para>
/// After a successful unsubscription, the server should no longer send resource change notifications
/// to the client for the specified resources.
/// </para>
/// </remarks>
public IList<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = [];

/// <summary>
/// Gets the filters for the set-logging-level handler pipeline.
/// </summary>
/// <remarks>
/// <para>
/// These filters wrap handlers that process <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
/// clients to control which log messages they receive by specifying a minimum severity threshold.
/// The filters can modify, log, or perform additional operations on requests and responses for
/// <see cref="RequestMethods.LoggingSetLevel"/> requests.
/// </para>
/// <para>
/// After handling a level change request, the server typically begins sending log messages
/// at or above the specified level to the client as notifications/message notifications.
/// </para>
/// </remarks>
public IList<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
}
Loading
Loading