Skip to content

Commit 7202e4a

Browse files
committed
Split configuration of request and message filters
This adds WithMessageFilters and WithRequestFilters extension methods to IMcpServerBuilder which expose an IMcpMessageFilterBuilder and IMcpRequestFilterBuilder to their respective callbacks. This avoids having to many similar looking, poorly sorted extension methods on IMcpServerBuilder. This also keeps incoming and outgoing message filters more closely associated.
1 parent 703d842 commit 7202e4a

18 files changed

+856
-680
lines changed

docs/concepts/filters.md

Lines changed: 211 additions & 155 deletions
Large diffs are not rendered by default.

src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void PostConfigure(string? name, McpServerOptions options)
4343

4444
private void ConfigureListToolsFilter(McpServerOptions options)
4545
{
46-
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
46+
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
4747
{
4848
context.Items[AuthorizationFilterInvokedKey] = true;
4949

@@ -57,7 +57,7 @@ await FilterAuthorizedItemsAsync(
5757

5858
private static void CheckListToolsFilter(McpServerOptions options)
5959
{
60-
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
60+
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
6161
{
6262
var result = await next(context, cancellationToken);
6363

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

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

9090
private static void CheckCallToolFilter(McpServerOptions options)
9191
{
92-
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
92+
options.Filters.Request.CallToolFilters.Add(next => async (context, cancellationToken) =>
9393
{
9494
if (HasAuthorizationMetadata(context.MatchedPrimitive)
9595
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
@@ -103,7 +103,7 @@ private static void CheckCallToolFilter(McpServerOptions options)
103103

104104
private void ConfigureListResourcesFilter(McpServerOptions options)
105105
{
106-
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
106+
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
107107
{
108108
context.Items[AuthorizationFilterInvokedKey] = true;
109109

@@ -117,7 +117,7 @@ await FilterAuthorizedItemsAsync(
117117

118118
private static void CheckListResourcesFilter(McpServerOptions options)
119119
{
120-
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
120+
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
121121
{
122122
var result = await next(context, cancellationToken);
123123

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

134134
private void ConfigureListResourceTemplatesFilter(McpServerOptions options)
135135
{
136-
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
136+
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
137137
{
138138
context.Items[AuthorizationFilterInvokedKey] = true;
139139

@@ -147,7 +147,7 @@ await FilterAuthorizedItemsAsync(
147147

148148
private static void CheckListResourceTemplatesFilter(McpServerOptions options)
149149
{
150-
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
150+
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
151151
{
152152
var result = await next(context, cancellationToken);
153153

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

164164
private void ConfigureReadResourceFilter(McpServerOptions options)
165165
{
166-
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
166+
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
167167
{
168168
context.Items[AuthorizationFilterInvokedKey] = true;
169169

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

180180
private static void CheckReadResourceFilter(McpServerOptions options)
181181
{
182-
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
182+
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
183183
{
184184
if (HasAuthorizationMetadata(context.MatchedPrimitive)
185185
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
@@ -193,7 +193,7 @@ private static void CheckReadResourceFilter(McpServerOptions options)
193193

194194
private void ConfigureListPromptsFilter(McpServerOptions options)
195195
{
196-
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
196+
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
197197
{
198198
context.Items[AuthorizationFilterInvokedKey] = true;
199199

@@ -207,7 +207,7 @@ await FilterAuthorizedItemsAsync(
207207

208208
private static void CheckListPromptsFilter(McpServerOptions options)
209209
{
210-
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
210+
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
211211
{
212212
var result = await next(context, cancellationToken);
213213

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

224224
private void ConfigureGetPromptFilter(McpServerOptions options)
225225
{
226-
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
226+
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
227227
{
228228
context.Items[AuthorizationFilterInvokedKey] = true;
229229

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

240240
private static void CheckGetPromptFilter(McpServerOptions options)
241241
{
242-
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
242+
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
243243
{
244244
if (HasAuthorizationMetadata(context.MatchedPrimitive)
245245
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using ModelContextProtocol.Protocol;
2+
3+
namespace ModelContextProtocol.Server;
4+
5+
/// <summary>
6+
/// Provides grouped message filter collections.
7+
/// </summary>
8+
public sealed class McpMessageFilters
9+
{
10+
/// <summary>
11+
/// Gets the filters for all incoming JSON-RPC messages.
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// These filters intercept all incoming JSON-RPC messages before they are processed by the server,
16+
/// including requests, notifications, responses, and errors. The filters can perform logging,
17+
/// authentication, rate limiting, or other cross-cutting concerns that apply to all message types.
18+
/// </para>
19+
/// <para>
20+
/// Message filters are applied before request-specific filters. If a message filter does not call
21+
/// the next handler in the pipeline, the default handlers will not be executed.
22+
/// </para>
23+
/// </remarks>
24+
public List<McpMessageFilter> IncomingFilters { get; } = [];
25+
26+
/// <summary>
27+
/// Gets the filters for all outgoing JSON-RPC messages.
28+
/// </summary>
29+
/// <remarks>
30+
/// <para>
31+
/// These filters intercept all outgoing JSON-RPC messages before they are sent to the client,
32+
/// including responses, notifications, and errors. The filters can perform logging,
33+
/// redaction, auditing, or other cross-cutting concerns that apply to all message types.
34+
/// </para>
35+
/// <para>
36+
/// If a message filter does not call the next handler in the pipeline, the message will not be sent.
37+
/// Filters may also call the next handler multiple times with different messages to emit additional
38+
/// server-to-client messages.
39+
/// </para>
40+
/// </remarks>
41+
public List<McpMessageFilter> OutgoingFilters { get; } = [];
42+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using ModelContextProtocol.Protocol;
2+
3+
namespace ModelContextProtocol.Server;
4+
5+
/// <summary>
6+
/// Provides grouped request-specific filter collections.
7+
/// </summary>
8+
public sealed class McpRequestFilters
9+
{
10+
/// <summary>
11+
/// Gets the filters for the list-tools handler pipeline.
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// These filters wrap handlers that return a list of available tools when requested by a client.
16+
/// The filters can modify, log, or perform additional operations on requests and responses for
17+
/// <see cref="RequestMethods.ToolsList"/> requests. It supports pagination through the cursor mechanism,
18+
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more tools.
19+
/// </para>
20+
/// <para>
21+
/// These filters work alongside any tools defined in the <see cref="McpServerTool"/> collection.
22+
/// Tools from both sources will be combined when returning results to clients.
23+
/// </para>
24+
/// </remarks>
25+
public List<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];
26+
27+
/// <summary>
28+
/// Gets the filters for the call-tool handler pipeline.
29+
/// </summary>
30+
/// <remarks>
31+
/// 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.
32+
/// The filters can modify, log, or perform additional operations on requests and responses for
33+
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
34+
/// </remarks>
35+
public List<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = [];
36+
37+
/// <summary>
38+
/// Gets the filters for the list-prompts handler pipeline.
39+
/// </summary>
40+
/// <remarks>
41+
/// <para>
42+
/// These filters wrap handlers that return a list of available prompts when requested by a client.
43+
/// The filters can modify, log, or perform additional operations on requests and responses for
44+
/// <see cref="RequestMethods.PromptsList"/> requests. It supports pagination through the cursor mechanism,
45+
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more prompts.
46+
/// </para>
47+
/// <para>
48+
/// These filters work alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
49+
/// Prompts from both sources will be combined when returning results to clients.
50+
/// </para>
51+
/// </remarks>
52+
public List<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = [];
53+
54+
/// <summary>
55+
/// Gets the filters for the get-prompt handler pipeline.
56+
/// </summary>
57+
/// <remarks>
58+
/// 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.
59+
/// The filters can modify, log, or perform additional operations on requests and responses for
60+
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
61+
/// </remarks>
62+
public List<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = [];
63+
64+
/// <summary>
65+
/// Gets the filters for the list-resource-templates handler pipeline.
66+
/// </summary>
67+
/// <remarks>
68+
/// These filters wrap handlers that return a list of available resource templates when requested by a client.
69+
/// The filters can modify, log, or perform additional operations on requests and responses for
70+
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. It supports pagination through the cursor mechanism,
71+
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resource templates.
72+
/// </remarks>
73+
public List<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = [];
74+
75+
/// <summary>
76+
/// Gets the filters for the list-resources handler pipeline.
77+
/// </summary>
78+
/// <remarks>
79+
/// These filters wrap handlers that return a list of available resources when requested by a client.
80+
/// The filters can modify, log, or perform additional operations on requests and responses for
81+
/// <see cref="RequestMethods.ResourcesList"/> requests. It supports pagination through the cursor mechanism,
82+
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resources.
83+
/// </remarks>
84+
public List<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = [];
85+
86+
/// <summary>
87+
/// Gets the filters for the read-resource handler pipeline.
88+
/// </summary>
89+
/// <remarks>
90+
/// These filters wrap handlers that are invoked when a client requests the content of a specific resource identified by its URI.
91+
/// The filters can modify, log, or perform additional operations on requests and responses for
92+
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
93+
/// </remarks>
94+
public List<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = [];
95+
96+
/// <summary>
97+
/// Gets the filters for the complete-handler pipeline.
98+
/// </summary>
99+
/// <remarks>
100+
/// These filters wrap handlers that provide auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
101+
/// The filters can modify, log, or perform additional operations on requests and responses for
102+
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
103+
/// reference type and current argument value.
104+
/// </remarks>
105+
public List<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = [];
106+
107+
/// <summary>
108+
/// Gets the filters for the subscribe-to-resources handler pipeline.
109+
/// </summary>
110+
/// <remarks>
111+
/// <para>
112+
/// These filters wrap handlers that are invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
113+
/// The filters can modify, log, or perform additional operations on requests and responses for
114+
/// <see cref="RequestMethods.ResourcesSubscribe"/> requests. The handler should implement logic to register the client's interest in the specified resources
115+
/// and set up the necessary infrastructure to send notifications when those resources change.
116+
/// </para>
117+
/// <para>
118+
/// After a successful subscription, the server should send resource change notifications to the client
119+
/// whenever a relevant resource is created, updated, or deleted.
120+
/// </para>
121+
/// </remarks>
122+
public List<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = [];
123+
124+
/// <summary>
125+
/// Gets the filters for the unsubscribe-from-resources handler pipeline.
126+
/// </summary>
127+
/// <remarks>
128+
/// <para>
129+
/// These filters wrap handlers that are invoked when a client wants to stop receiving notifications about previously subscribed resources.
130+
/// The filters can modify, log, or perform additional operations on requests and responses for
131+
/// <see cref="RequestMethods.ResourcesUnsubscribe"/> requests. The handler should implement logic to remove the client's subscriptions to the specified resources
132+
/// and clean up any associated resources.
133+
/// </para>
134+
/// <para>
135+
/// After a successful unsubscription, the server should no longer send resource change notifications
136+
/// to the client for the specified resources.
137+
/// </para>
138+
/// </remarks>
139+
public List<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = [];
140+
141+
/// <summary>
142+
/// Gets the filters for the set-logging-level handler pipeline.
143+
/// </summary>
144+
/// <remarks>
145+
/// <para>
146+
/// These filters wrap handlers that process <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
147+
/// clients to control which log messages they receive by specifying a minimum severity threshold.
148+
/// The filters can modify, log, or perform additional operations on requests and responses for
149+
/// <see cref="RequestMethods.LoggingSetLevel"/> requests.
150+
/// </para>
151+
/// <para>
152+
/// After handling a level change request, the server typically begins sending log messages
153+
/// at or above the specified level to the client as notifications/message notifications.
154+
/// </para>
155+
/// </remarks>
156+
public List<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
157+
}

0 commit comments

Comments
 (0)