Skip to content

Commit 58dc73a

Browse files
committed
Add middleware and authz support for server-side handlers
1 parent f2dc714 commit 58dc73a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2374
-360
lines changed

docs/concepts/filters.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
title: Filters
3+
author: halter73
4+
description: MCP Server Handler Filters
5+
uid: filters
6+
---
7+
8+
# MCP Server Handler Filters
9+
10+
This document describes the filter functionality in the MCP Server, which allows you to add middleware-style filters to handler pipelines.
11+
12+
## Overview
13+
14+
For each handler type in the MCP Server, there are corresponding `AddXXXFilter` methods in `McpServerBuilderExtensions.cs` that allow you to add filters to the handler pipeline. The filters are stored in `McpServerOptions.Filters` and applied during server configuration.
15+
16+
## Available Filter Methods
17+
18+
The following filter methods are available:
19+
20+
- `AddListResourceTemplatesFilter` - Filter for list resource templates handlers
21+
- `AddListToolsFilter` - Filter for list tools handlers
22+
- `AddCallToolFilter` - Filter for call tool handlers
23+
- `AddListPromptsFilter` - Filter for list prompts handlers
24+
- `AddGetPromptFilter` - Filter for get prompt handlers
25+
- `AddListResourcesFilter` - Filter for list resources handlers
26+
- `AddReadResourceFilter` - Filter for read resource handlers
27+
- `AddCompleteFilter` - Filter for completion handlers
28+
- `AddSubscribeToResourcesFilter` - Filter for resource subscription handlers
29+
- `AddUnsubscribeFromResourcesFilter` - Filter for resource unsubscription handlers
30+
- `AddSetLoggingLevelFilter` - Filter for logging level handlers
31+
32+
## Usage
33+
34+
Filters are functions that take a handler and return a new handler, allowing you to wrap the original handler with additional functionality:
35+
36+
```csharp
37+
services.AddMcpServer()
38+
.WithListToolsHandler(async (context, cancellationToken) =>
39+
{
40+
// Your base handler logic
41+
return new ListToolsResult { Tools = GetTools() };
42+
})
43+
.AddListToolsFilter(next => async (context, cancellationToken) =>
44+
{
45+
// Pre-processing logic
46+
Console.WriteLine("Before handler execution");
47+
48+
var result = await next(context, cancellationToken);
49+
50+
// Post-processing logic
51+
Console.WriteLine("After handler execution");
52+
return result;
53+
});
54+
```
55+
56+
## Filter Execution Order
57+
58+
```csharp
59+
services.AddMcpServer()
60+
.WithListToolsHandler(baseHandler)
61+
.AddListToolsFilter(filter1) // Executes first (outermost)
62+
.AddListToolsFilter(filter2) // Executes second
63+
.AddListToolsFilter(filter3); // Executes third (closest to handler)
64+
```
65+
66+
Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filter2 -> filter1`
67+
68+
## Common Use Cases
69+
70+
### Logging
71+
```csharp
72+
.AddListToolsFilter(next => async (context, cancellationToken) =>
73+
{
74+
Console.WriteLine($"Processing request from {context.Meta.ProgressToken}");
75+
var result = await next(context, cancellationToken);
76+
Console.WriteLine($"Returning {result.Tools?.Count ?? 0} tools");
77+
return result;
78+
});
79+
```
80+
81+
### Error Handling
82+
```csharp
83+
.AddCallToolFilter(next => async (context, cancellationToken) =>
84+
{
85+
try
86+
{
87+
return await next(context, cancellationToken);
88+
}
89+
catch (Exception ex)
90+
{
91+
return new CallToolResult
92+
{
93+
Content = new[] { new TextContent { Type = "text", Text = $"Error: {ex.Message}" } },
94+
IsError = true
95+
};
96+
}
97+
});
98+
```
99+
100+
### Performance Monitoring
101+
```csharp
102+
.AddListToolsFilter(next => async (context, cancellationToken) =>
103+
{
104+
var stopwatch = Stopwatch.StartNew();
105+
var result = await next(context, cancellationToken);
106+
stopwatch.Stop();
107+
Console.WriteLine($"Handler took {stopwatch.ElapsedMilliseconds}ms");
108+
return result;
109+
});
110+
```
111+
112+
### Caching
113+
```csharp
114+
.AddListResourcesFilter(next => async (context, cancellationToken) =>
115+
{
116+
var cacheKey = $"resources:{context.Params.Cursor}";
117+
if (cache.TryGetValue(cacheKey, out var cached))
118+
return cached;
119+
120+
var result = await next(context, cancellationToken);
121+
cache.Set(cacheKey, result, TimeSpan.FromMinutes(5));
122+
return result;
123+
});
124+
```
125+
126+
## Built-in Authorization Filters
127+
128+
When using the ASP.NET Core integration (`ModelContextProtocol.AspNetCore`), authorization filters are automatically configured to support `[Authorize]` and `[AllowAnonymous]` attributes on MCP server tools, prompts, and resources.
129+
130+
### Authorization Attributes Support
131+
132+
The MCP server automatically respects the following authorization attributes:
133+
134+
- **`[Authorize]`** - Requires authentication for access
135+
- **`[Authorize(Roles = "RoleName")]`** - Requires specific roles
136+
- **`[Authorize(Policy = "PolicyName")]`** - Requires specific authorization policies
137+
- **`[AllowAnonymous]`** - Explicitly allows anonymous access (overrides `[Authorize]`)
138+
139+
### Tool Authorization
140+
141+
Tools can be decorated with authorization attributes to control access:
142+
143+
```csharp
144+
[McpServerToolType]
145+
public class WeatherTools
146+
{
147+
[McpServerTool, Description("Gets public weather data")]
148+
public static string GetWeather(string location)
149+
{
150+
return $"Weather for {location}: Sunny, 25°C";
151+
}
152+
153+
[McpServerTool, Description("Gets detailed weather forecast")]
154+
[Authorize] // Requires authentication
155+
public static string GetDetailedForecast(string location)
156+
{
157+
return $"Detailed forecast for {location}: ...";
158+
}
159+
160+
[McpServerTool, Description("Manages weather alerts")]
161+
[Authorize(Roles = "Admin")] // Requires Admin role
162+
public static string ManageWeatherAlerts(string alertType)
163+
{
164+
return $"Managing alert: {alertType}";
165+
}
166+
}
167+
```
168+
169+
### Class-Level Authorization
170+
171+
You can apply authorization at the class level, which affects all tools in the class:
172+
173+
```csharp
174+
[McpServerToolType]
175+
[Authorize] // All tools require authentication
176+
public class AdminTools
177+
{
178+
[McpServerTool, Description("Admin-only tool")]
179+
public static string AdminOperation()
180+
{
181+
return "Admin operation completed";
182+
}
183+
184+
[McpServerTool, Description("Public tool accessible to anonymous users")]
185+
[AllowAnonymous] // Overrides class-level [Authorize]
186+
public static string PublicOperation()
187+
{
188+
return "Public operation completed";
189+
}
190+
}
191+
```
192+
193+
### How Authorization Filters Work
194+
195+
The authorization filters work differently for list operations versus individual operations:
196+
197+
#### List Operations (ListTools, ListPrompts, ListResources)
198+
For list operations, the filters automatically remove unauthorized items from the results. Users only see tools, prompts, or resources they have permission to access.
199+
200+
#### Individual Operations (CallTool, GetPrompt, ReadResource)
201+
For individual operations, the filters return authorization errors when access is denied:
202+
203+
- **Tools**: Returns a `CallToolResult` with `IsError = true` and an error message
204+
- **Prompts**: Throws an `McpException` with "Access forbidden" message
205+
- **Resources**: Throws an `McpException` with "Access forbidden" message
206+
207+
### Setup Requirements
208+
209+
To use authorization features, you must configure authentication and authorization in your ASP.NET Core application:
210+
211+
```csharp
212+
var builder = WebApplication.CreateBuilder(args);
213+
214+
// Add authentication
215+
builder.Services.AddAuthentication("Bearer")
216+
.AddJwtBearer("Bearer", options => { /* JWT configuration */ });
217+
218+
// Add authorization (required for [Authorize] attributes to work)
219+
builder.Services.AddAuthorization();
220+
221+
// Add MCP server
222+
builder.Services.AddMcpServer()
223+
.WithTools<WeatherTools>();
224+
225+
var app = builder.Build();
226+
227+
// Use authentication and authorization middleware
228+
app.UseAuthentication();
229+
app.UseAuthorization();
230+
231+
app.MapMcp();
232+
app.Run();
233+
```
234+
235+
### Custom Authorization Filters
236+
237+
You can also create custom authorization filters using the filter methods:
238+
239+
```csharp
240+
.AddCallToolFilter(next => async (context, cancellationToken) =>
241+
{
242+
// Custom authorization logic
243+
if (context.User?.Identity?.IsAuthenticated != true)
244+
{
245+
return new CallToolResult
246+
{
247+
Content = [new TextContent { Text = "Custom: Authentication required" }],
248+
IsError = true
249+
};
250+
}
251+
252+
return await next(context, cancellationToken);
253+
});
254+
```
255+
256+
### RequestContext
257+
258+
Within filters, you have access to:
259+
260+
- `context.User` - The current user's `ClaimsPrincipal`
261+
- `context.Services` - The request's service provider for resolving authorization services
262+
- `context.MatchedPrimitive` - The matched tool/prompt/resource with its metadata including authorization attributes via `context.MatchedPrimitive.Metadata`

docs/concepts/toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ items:
1313
items:
1414
- name: Logging
1515
uid: logging
16+
- name: Server Features
17+
items:
18+
- name: Filters
19+
uid: filters

samples/AspNetCoreMcpServer/Properties/launchSettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"applicationUrl": "http://localhost:3001",
88
"environmentVariables": {
99
"ASPNETCORE_ENVIRONMENT": "Development",
10-
"OTEL_SERVICE_NAME": "aspnetcore-mcp-server",
10+
"OTEL_SERVICE_NAME": "aspnetcore-mcp-server"
1111
}
1212
},
1313
"https": {
@@ -16,7 +16,7 @@
1616
"applicationUrl": "https://localhost:7133;http://localhost:3001",
1717
"environmentVariables": {
1818
"ASPNETCORE_ENVIRONMENT": "Development",
19-
"OTEL_SERVICE_NAME": "aspnetcore-mcp-server",
19+
"OTEL_SERVICE_NAME": "aspnetcore-mcp-server"
2020
}
2121
}
2222
}

0 commit comments

Comments
 (0)