Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
366 changes: 211 additions & 155 deletions docs/concepts/filters.md

Large diffs are not rendered by default.

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 List<McpMessageFilter> IncomingFilters { get; } = [];
Comment thread
halter73 marked this conversation as resolved.
Outdated

/// <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 List<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 List<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];
Comment thread
halter73 marked this conversation as resolved.
Outdated

/// <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 List<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 List<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 List<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 List<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 List<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 List<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 List<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 List<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 List<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 List<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
}
Loading
Loading