1+ using OpenTelemetry ;
2+ using OpenTelemetry . Metrics ;
3+ using OpenTelemetry . Trace ;
4+ using AspNetCoreMcpServerPerUserTools . Tools ;
5+ using ModelContextProtocol . Server ;
6+
7+ var builder = WebApplication . CreateBuilder ( args ) ;
8+
9+ // Register all MCP server tools - they will be filtered per user later
10+ builder . Services . AddMcpServer ( )
11+ . WithHttpTransport ( options =>
12+ {
13+ // Configure per-session options to filter tools based on user permissions
14+ options . ConfigureSessionOptions = async ( httpContext , mcpOptions , cancellationToken ) =>
15+ {
16+ // Determine user role from headers (in real apps, use proper authentication)
17+ var userRole = GetUserRole ( httpContext ) ;
18+ var userId = GetUserId ( 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 user role
28+ switch ( userRole )
29+ {
30+ case "admin" :
31+ // Admins get all tools
32+ AddToolsForType < PublicTool > ( toolCollection ) ;
33+ AddToolsForType < UserTool > ( toolCollection ) ;
34+ AddToolsForType < AdminTool > ( toolCollection ) ;
35+ break ;
36+
37+ case "user" :
38+ // Regular users get public and user tools
39+ AddToolsForType < PublicTool > ( toolCollection ) ;
40+ AddToolsForType < UserTool > ( toolCollection ) ;
41+ break ;
42+
43+ default :
44+ // Anonymous/public users get only public tools
45+ AddToolsForType < PublicTool > ( toolCollection ) ;
46+ break ;
47+ }
48+ }
49+
50+ // Optional: Log the session configuration for debugging
51+ var logger = httpContext . RequestServices . GetRequiredService < ILogger < Program > > ( ) ;
52+ logger . LogInformation ( "Configured MCP session for user {UserId} with role {UserRole}, {ToolCount} tools available" ,
53+ userId , userRole , toolCollection ? . Count ?? 0 ) ;
54+ } ;
55+ } )
56+ . WithTools < PublicTool > ( )
57+ . WithTools < UserTool > ( )
58+ . WithTools < AdminTool > ( ) ;
59+
60+ // Add OpenTelemetry for observability
61+ builder . Services . AddOpenTelemetry ( )
62+ . WithTracing ( b => b . AddSource ( "*" )
63+ . AddAspNetCoreInstrumentation ( )
64+ . AddHttpClientInstrumentation ( ) )
65+ . WithMetrics ( b => b . AddMeter ( "*" )
66+ . AddAspNetCoreInstrumentation ( )
67+ . AddHttpClientInstrumentation ( ) )
68+ . WithLogging ( )
69+ . UseOtlpExporter ( ) ;
70+
71+ var app = builder . Build ( ) ;
72+
73+ // Add middleware to log requests for demo purposes
74+ app . Use ( async ( context , next ) =>
75+ {
76+ var logger = context . RequestServices . GetRequiredService < ILogger < Program > > ( ) ;
77+ var userRole = GetUserRole ( context ) ;
78+ var userId = GetUserId ( context ) ;
79+
80+ logger . LogInformation ( "Request from User {UserId} with Role {UserRole}: {Method} {Path}" ,
81+ userId , userRole , context . Request . Method , context . Request . Path ) ;
82+
83+ await next ( ) ;
84+ } ) ;
85+
86+ app . MapMcp ( ) ;
87+
88+ // Add a simple endpoint to test authentication headers
89+ app . MapGet ( "/test-auth" , ( HttpContext context ) =>
90+ {
91+ var userRole = GetUserRole ( context ) ;
92+ var userId = GetUserId ( context ) ;
93+
94+ return Results . Text ( $ "UserId: { userId } \n Role: { userRole } \n Message: You are authenticated as { userId } with role { userRole } ") ;
95+ } ) ;
96+
97+ app . Run ( ) ;
98+
99+ // Helper methods for authentication - in production, use proper authentication/authorization
100+ static string GetUserRole ( HttpContext context )
101+ {
102+ // Check for X-User-Role header first
103+ if ( context . Request . Headers . TryGetValue ( "X-User-Role" , out var roleHeader ) )
104+ {
105+ var role = roleHeader . ToString ( ) . ToLowerInvariant ( ) ;
106+ if ( role is "admin" or "user" or "public" )
107+ {
108+ return role ;
109+ }
110+ }
111+
112+ // Check for Authorization header pattern (Bearer token simulation)
113+ if ( context . Request . Headers . TryGetValue ( "Authorization" , out var authHeader ) )
114+ {
115+ var auth = authHeader . ToString ( ) ;
116+ if ( auth . StartsWith ( "Bearer admin-" , StringComparison . OrdinalIgnoreCase ) )
117+ return "admin" ;
118+ if ( auth . StartsWith ( "Bearer user-" , StringComparison . OrdinalIgnoreCase ) )
119+ return "user" ;
120+ if ( auth . StartsWith ( "Bearer " , StringComparison . OrdinalIgnoreCase ) )
121+ return "public" ;
122+ }
123+
124+ // Default to public access
125+ return "public" ;
126+ }
127+
128+ static string GetUserId ( HttpContext context )
129+ {
130+ // Check for X-User-Id header first
131+ if ( context . Request . Headers . TryGetValue ( "X-User-Id" , out var userIdHeader ) )
132+ {
133+ return userIdHeader . ToString ( ) ;
134+ }
135+
136+ // Extract from Authorization header if present
137+ if ( context . Request . Headers . TryGetValue ( "Authorization" , out var authHeader ) )
138+ {
139+ var auth = authHeader . ToString ( ) ;
140+ if ( auth . StartsWith ( "Bearer " , StringComparison . OrdinalIgnoreCase ) )
141+ {
142+ var token = auth [ "Bearer " . Length ..] ;
143+ return token . Contains ( '-' ) ? token : $ "user-{ token } ";
144+ }
145+ }
146+
147+ // Generate anonymous ID
148+ return $ "anonymous-{ Guid . NewGuid ( ) : N} "[ ..16 ] ;
149+ }
150+
151+ static void AddToolsForType < [ System . Diagnostics . CodeAnalysis . DynamicallyAccessedMembers (
152+ System . Diagnostics . CodeAnalysis . DynamicallyAccessedMemberTypes . PublicMethods ) ] T > (
153+ McpServerPrimitiveCollection < McpServerTool > toolCollection )
154+ {
155+ var toolType = typeof ( T ) ;
156+ var methods = toolType . GetMethods ( System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . Static )
157+ . Where ( m => m . GetCustomAttributes ( typeof ( McpServerToolAttribute ) , false ) . Any ( ) ) ;
158+
159+ foreach ( var method in methods )
160+ {
161+ try
162+ {
163+ var tool = McpServerTool . Create ( method , target : null , new McpServerToolCreateOptions ( ) ) ;
164+ toolCollection . Add ( tool ) ;
165+ }
166+ catch ( Exception ex )
167+ {
168+ // Log error but continue with other tools
169+ Console . WriteLine ( $ "Failed to add tool { toolType . Name } .{ method . Name } : { ex . Message } ") ;
170+ }
171+ }
172+ }
0 commit comments