Skip to content

Commit d21aa9e

Browse files
halter73Copilot
andauthored
Split configuration of request and message filters (#1308)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 27a3196 commit d21aa9e

21 files changed

+1288
-1078
lines changed

docs/concepts/filters.md

Lines changed: 215 additions & 156 deletions
Large diffs are not rendered by default.

src/Common/Obsoletions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ internal static class Obsoletions
2222
public const string LegacyTitledEnumSchema_DiagnosticId = "MCP9001";
2323
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.";
2424
public const string LegacyTitledEnumSchema_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330";
25+
26+
public const string MessageAndRequestFilter_DiagnosticId = "MCP9002";
27+
public const string MessageAndRequestFilter_Url = "https://github.com/modelcontextprotocol/csharp-sdk/pull/1308";
28+
public const string MessageFilter_Message = "Use WithMessageFilters() instead.";
29+
public const string RequestFilter_Message = "Use WithRequestFilters() instead.";
2530
}

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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<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 IList<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
157+
}

0 commit comments

Comments
 (0)