Skip to content

Commit f92222d

Browse files
authored
Merge pull request #2 from PederHP/copilot/fix-98a8182b-5c5d-4882-a04a-1d92ba914d7b
Rename AspNetCoreMcpServerPerUserTools to AspNetCoreMcpPerSessionTools with route-based filtering
2 parents 774c752 + 8d79c6e commit f92222d

File tree

14 files changed

+535
-529
lines changed

14 files changed

+535
-529
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using OpenTelemetry;
2+
using OpenTelemetry.Metrics;
3+
using OpenTelemetry.Trace;
4+
using AspNetCoreMcpPerSessionTools.Tools;
5+
using ModelContextProtocol.Server;
6+
7+
var builder = WebApplication.CreateBuilder(args);
8+
9+
// Register all MCP server tools - they will be filtered per session based on route
10+
builder.Services.AddMcpServer()
11+
.WithHttpTransport(options =>
12+
{
13+
// Configure per-session options to filter tools based on route category
14+
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
15+
{
16+
// Determine tool category from route parameters
17+
var toolCategory = GetToolCategoryFromRoute(httpContext);
18+
var sessionInfo = GetSessionInfo(httpContext);
19+
20+
// Get the tool collection that we can modify per session
21+
var toolCollection = mcpOptions.Capabilities?.Tools?.ToolCollection;
22+
if (toolCollection != null)
23+
{
24+
// Clear all tools first
25+
toolCollection.Clear();
26+
27+
// Add tools based on the requested category
28+
switch (toolCategory?.ToLower())
29+
{
30+
case "clock":
31+
// Clock category gets time/date tools
32+
AddToolsForType<ClockTool>(toolCollection);
33+
break;
34+
35+
case "calculator":
36+
// Calculator category gets mathematical tools
37+
AddToolsForType<CalculatorTool>(toolCollection);
38+
break;
39+
40+
case "userinfo":
41+
// UserInfo category gets session and system information tools
42+
AddToolsForType<UserInfoTool>(toolCollection);
43+
break;
44+
45+
case "all":
46+
default:
47+
// Default or "all" category gets all tools
48+
AddToolsForType<ClockTool>(toolCollection);
49+
AddToolsForType<CalculatorTool>(toolCollection);
50+
AddToolsForType<UserInfoTool>(toolCollection);
51+
break;
52+
}
53+
}
54+
55+
// Optional: Log the session configuration for debugging
56+
var logger = httpContext.RequestServices.GetRequiredService<ILogger<Program>>();
57+
logger.LogInformation("Configured MCP session for category '{ToolCategory}' from {SessionInfo}, {ToolCount} tools available",
58+
toolCategory, sessionInfo, toolCollection?.Count ?? 0);
59+
};
60+
})
61+
.WithTools<ClockTool>()
62+
.WithTools<CalculatorTool>()
63+
.WithTools<UserInfoTool>();
64+
65+
// Add OpenTelemetry for observability
66+
builder.Services.AddOpenTelemetry()
67+
.WithTracing(b => b.AddSource("*")
68+
.AddAspNetCoreInstrumentation()
69+
.AddHttpClientInstrumentation())
70+
.WithMetrics(b => b.AddMeter("*")
71+
.AddAspNetCoreInstrumentation()
72+
.AddHttpClientInstrumentation())
73+
.WithLogging()
74+
.UseOtlpExporter();
75+
76+
var app = builder.Build();
77+
78+
// Add middleware to log requests for demo purposes
79+
app.Use(async (context, next) =>
80+
{
81+
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
82+
var toolCategory = GetToolCategoryFromRoute(context);
83+
var sessionInfo = GetSessionInfo(context);
84+
85+
logger.LogInformation("Request for category '{ToolCategory}' from {SessionInfo}: {Method} {Path}",
86+
toolCategory, sessionInfo, context.Request.Method, context.Request.Path);
87+
88+
await next();
89+
});
90+
91+
// Map MCP with route parameter for tool category filtering
92+
app.MapMcp("/{toolCategory?}");
93+
94+
// Add endpoints to test different tool categories
95+
app.MapGet("/", () => Results.Text(
96+
"MCP Per-Session Tools Demo\n" +
97+
"=========================\n" +
98+
"Available endpoints:\n" +
99+
"- /clock - MCP server with clock/time tools\n" +
100+
"- /calculator - MCP server with calculation tools\n" +
101+
"- /userinfo - MCP server with session/system info tools\n" +
102+
"- /all - MCP server with all tools (default)\n" +
103+
"\n" +
104+
"Test routes:\n" +
105+
"- /test-category/{category} - Test category detection\n"
106+
));
107+
108+
app.MapGet("/test-category/{toolCategory?}", (string? toolCategory, HttpContext context) =>
109+
{
110+
var detectedCategory = GetToolCategoryFromRoute(context);
111+
var sessionInfo = GetSessionInfo(context);
112+
113+
return Results.Text($"Tool Category: {detectedCategory ?? "all (default)"}\n" +
114+
$"Session Info: {sessionInfo}\n" +
115+
$"Route Parameter: {toolCategory ?? "none"}\n" +
116+
$"Message: MCP session would be configured for '{detectedCategory ?? "all"}' tools");
117+
});
118+
119+
app.Run();
120+
121+
// Helper methods for route-based tool category detection
122+
static string? GetToolCategoryFromRoute(HttpContext context)
123+
{
124+
// Try to get tool category from route values
125+
if (context.Request.RouteValues.TryGetValue("toolCategory", out var categoryObj) && categoryObj is string category)
126+
{
127+
return string.IsNullOrEmpty(category) ? "all" : category;
128+
}
129+
130+
// Fallback: try to extract from path
131+
var path = context.Request.Path.Value?.Trim('/');
132+
if (!string.IsNullOrEmpty(path))
133+
{
134+
var segments = path.Split('/');
135+
if (segments.Length > 0)
136+
{
137+
var firstSegment = segments[0].ToLower();
138+
if (firstSegment is "clock" or "calculator" or "userinfo" or "all")
139+
{
140+
return firstSegment;
141+
}
142+
}
143+
}
144+
145+
// Default to "all" if no category specified
146+
return "all";
147+
}
148+
149+
static string GetSessionInfo(HttpContext context)
150+
{
151+
var userAgent = context.Request.Headers.UserAgent.ToString();
152+
var clientInfo = !string.IsNullOrEmpty(userAgent) ? userAgent[..Math.Min(userAgent.Length, 20)] + "..." : "Unknown";
153+
var remoteIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
154+
155+
return $"{clientInfo} ({remoteIp})";
156+
}
157+
158+
static void AddToolsForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(
159+
System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)]T>(
160+
McpServerPrimitiveCollection<McpServerTool> toolCollection)
161+
{
162+
var toolType = typeof(T);
163+
var methods = toolType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)
164+
.Where(m => m.GetCustomAttributes(typeof(McpServerToolAttribute), false).Any());
165+
166+
foreach (var method in methods)
167+
{
168+
try
169+
{
170+
var tool = McpServerTool.Create(method, target: null, new McpServerToolCreateOptions());
171+
toolCollection.Add(tool);
172+
}
173+
catch (Exception ex)
174+
{
175+
// Log error but continue with other tools
176+
Console.WriteLine($"Failed to add tool {toolType.Name}.{method.Name}: {ex.Message}");
177+
}
178+
}
179+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# ASP.NET Core MCP Server with Per-Session Tool Filtering
2+
3+
This sample demonstrates how to create an MCP (Model Context Protocol) server that provides different sets of tools based on route-based session configuration. This showcases the technique of using `ConfigureSessionOptions` to dynamically modify the `ToolCollection` based on route parameters for each MCP session.
4+
5+
## Overview
6+
7+
The sample demonstrates route-based tool filtering using the MCP SDK's `ConfigureSessionOptions` callback. Instead of using authentication headers, this approach uses URL routes to determine which tools are available to each MCP session, making it easy to test different tool configurations.
8+
9+
## Features
10+
11+
- **Route-Based Tool Filtering**: Different routes expose different tool sets
12+
- **Three Tool Categories**:
13+
- **Clock**: Time and date related tools (`/clock`)
14+
- **Calculator**: Mathematical calculation tools (`/calculator`)
15+
- **UserInfo**: Session and system information tools (`/userinfo`)
16+
- **Dynamic Tool Loading**: Tools are filtered per session based on the route used to connect
17+
- **Easy Testing**: Simple URL-based testing without complex authentication setup
18+
- **Comprehensive Logging**: Logs session configuration and tool access for monitoring
19+
20+
## Tool Categories
21+
22+
### Clock Tools (`/clock`)
23+
- **GetTime**: Gets the current server time
24+
- **GetDate**: Gets the current date in various formats
25+
- **ConvertTimeZone**: Converts time between timezones (simulated)
26+
27+
### Calculator Tools (`/calculator`)
28+
- **Calculate**: Performs basic arithmetic operations (+, -, *, /)
29+
- **CalculatePercentage**: Calculates percentage of a number
30+
- **SquareRoot**: Calculates square root of a number
31+
32+
### UserInfo Tools (`/userinfo`)
33+
- **GetSessionInfo**: Gets information about the current MCP session
34+
- **GetSystemInfo**: Gets system information about the server
35+
- **EchoWithContext**: Echoes messages with session context
36+
- **GetConnectionInfo**: Gets basic connection information
37+
38+
## Route-Based Configuration
39+
40+
The server uses route parameters to determine which tools to make available:
41+
42+
- `GET /clock` - MCP server with only clock/time tools
43+
- `GET /calculator` - MCP server with only calculation tools
44+
- `GET /userinfo` - MCP server with only session/system info tools
45+
- `GET /all` or `GET /` - MCP server with all tools (default)
46+
47+
## Running the Sample
48+
49+
1. Navigate to the sample directory:
50+
```bash
51+
cd samples/AspNetCoreMcpPerSessionTools
52+
```
53+
54+
2. Run the server:
55+
```bash
56+
dotnet run
57+
```
58+
59+
3. The server will start on `https://localhost:5001` (or the port shown in the console)
60+
61+
## Testing Tool Categories
62+
63+
### Testing Clock Tools
64+
Connect your MCP client to: `https://localhost:5001/clock`
65+
- Available tools: GetTime, GetDate, ConvertTimeZone
66+
67+
### Testing Calculator Tools
68+
Connect your MCP client to: `https://localhost:5001/calculator`
69+
- Available tools: Calculate, CalculatePercentage, SquareRoot
70+
71+
### Testing UserInfo Tools
72+
Connect your MCP client to: `https://localhost:5001/userinfo`
73+
- Available tools: GetSessionInfo, GetSystemInfo, EchoWithContext, GetConnectionInfo
74+
75+
### Testing All Tools
76+
Connect your MCP client to: `https://localhost:5001/all` or `https://localhost:5001/`
77+
- Available tools: All tools from all categories
78+
79+
### Browser Testing
80+
You can also test the route detection in a browser:
81+
- `https://localhost:5001/` - Shows available endpoints
82+
- `https://localhost:5001/test-category/clock` - Tests clock category detection
83+
- `https://localhost:5001/test-category/calculator` - Tests calculator category detection
84+
- `https://localhost:5001/test-category/userinfo` - Tests userinfo category detection
85+
86+
## How It Works
87+
88+
### 1. Tool Registration
89+
All tools are registered during startup using the normal MCP tool registration:
90+
91+
```csharp
92+
builder.Services.AddMcpServer()
93+
.WithTools<ClockTool>()
94+
.WithTools<CalculatorTool>()
95+
.WithTools<UserInfoTool>();
96+
```
97+
98+
### 2. Route-Based Session Filtering
99+
The key technique is using `ConfigureSessionOptions` to modify the tool collection per session based on the route:
100+
101+
```csharp
102+
.WithHttpTransport(options =>
103+
{
104+
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
105+
{
106+
var toolCategory = GetToolCategoryFromRoute(httpContext);
107+
var toolCollection = mcpOptions.Capabilities?.Tools?.ToolCollection;
108+
109+
if (toolCollection != null)
110+
{
111+
// Clear all tools and add back only those for this category
112+
toolCollection.Clear();
113+
114+
switch (toolCategory?.ToLower())
115+
{
116+
case "clock":
117+
AddToolsForType<ClockTool>(toolCollection);
118+
break;
119+
case "calculator":
120+
AddToolsForType<CalculatorTool>(toolCollection);
121+
break;
122+
case "userinfo":
123+
AddToolsForType<UserInfoTool>(toolCollection);
124+
break;
125+
default:
126+
// All tools for default/all category
127+
AddToolsForType<ClockTool>(toolCollection);
128+
AddToolsForType<CalculatorTool>(toolCollection);
129+
AddToolsForType<UserInfoTool>(toolCollection);
130+
break;
131+
}
132+
}
133+
};
134+
})
135+
```
136+
137+
### 3. Route Parameter Detection
138+
The `GetToolCategoryFromRoute` method extracts the tool category from the URL route:
139+
140+
```csharp
141+
static string? GetToolCategoryFromRoute(HttpContext context)
142+
{
143+
if (context.Request.RouteValues.TryGetValue("toolCategory", out var categoryObj) && categoryObj is string category)
144+
{
145+
return string.IsNullOrEmpty(category) ? "all" : category;
146+
}
147+
return "all"; // Default
148+
}
149+
```
150+
151+
### 4. Dynamic Tool Loading
152+
The `AddToolsForType<T>` helper method uses reflection to discover and add all tools from a specific tool type to the session's tool collection.
153+
154+
## Key Benefits
155+
156+
- **Easy Testing**: No need to manage authentication tokens or headers
157+
- **Clear Separation**: Each tool category is isolated and can be tested independently
158+
- **Flexible Architecture**: Easy to add new tool categories or modify existing ones
159+
- **Production Ready**: The same technique can be extended for production scenarios with proper routing logic
160+
- **Observable**: Built-in logging shows exactly which tools are configured for each session
161+
162+
## Adapting for Production
163+
164+
For production use, you might want to:
165+
166+
1. **Add Authentication**: Combine route-based filtering with proper authentication
167+
2. **Database-Driven Categories**: Load tool categories and permissions from a database
168+
3. **User-Specific Routing**: Use user information to determine allowed categories
169+
4. **Advanced Routing**: Support nested categories or query parameters
170+
5. **Rate Limiting**: Add rate limiting per tool category
171+
6. **Caching**: Cache tool collections for better performance
172+
173+
## Related Issues
174+
175+
- [#714](https://github.com/modelcontextprotocol/csharp-sdk/issues/714) - Support varying tools/resources per user
176+
- [#237](https://github.com/modelcontextprotocol/csharp-sdk/issues/237) - Session-specific tool configuration
177+
- [#476](https://github.com/modelcontextprotocol/csharp-sdk/issues/476) - Dynamic tool management
178+
- [#612](https://github.com/modelcontextprotocol/csharp-sdk/issues/612) - Per-session resource filtering
179+
180+
## Learn More
181+
182+
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
183+
- [ASP.NET Core MCP Integration](../../src/ModelContextProtocol.AspNetCore/README.md)
184+
- [MCP C# SDK Documentation](https://modelcontextprotocol.github.io/csharp-sdk/)

0 commit comments

Comments
 (0)