33using EverythingServer . Resources ;
44using EverythingServer . Tools ;
55using Microsoft . Extensions . AI ;
6- using Microsoft . Extensions . DependencyInjection ;
7- using Microsoft . Extensions . Hosting ;
8- using Microsoft . Extensions . Logging ;
96using ModelContextProtocol ;
107using ModelContextProtocol . Protocol ;
118using ModelContextProtocol . Server ;
1411using OpenTelemetry . Metrics ;
1512using OpenTelemetry . Resources ;
1613using OpenTelemetry . Trace ;
14+ using System . Collections . Concurrent ;
1715
18- var builder = Host . CreateApplicationBuilder ( args ) ;
19- builder . Logging . AddConsole ( consoleLogOptions =>
20- {
21- // Configure all logs to go to stderr
22- consoleLogOptions . LogToStandardErrorThreshold = LogLevel . Trace ;
23- } ) ;
16+ var builder = WebApplication . CreateBuilder ( args ) ;
2417
25- HashSet < string > subscriptions = [ ] ;
26- var _minimumLoggingLevel = LoggingLevel . Debug ;
18+ // Dictionary of session IDs to a set of resource URIs they are subscribed to
19+ // The value is a ConcurrentDictionary used as a thread-safe HashSet
20+ // because .NET does not have a built-in concurrent HashSet
21+ ConcurrentDictionary < string , ConcurrentDictionary < string , byte > > subscriptions = new ( ) ;
2722
2823builder . Services
2924 . AddMcpServer ( )
30- . WithStdioServerTransport ( )
25+ . WithHttpTransport ( options =>
26+ {
27+ // Add a RunSessionHandler to remove all subscriptions for the session when it ends
28+ options . RunSessionHandler = async ( httpContext , mcpServer , token ) =>
29+ {
30+ if ( mcpServer . SessionId == null )
31+ {
32+ // There is no sessionId if the serverOptions.Stateless is true
33+ await mcpServer . RunAsync ( token ) ;
34+ return ;
35+ }
36+ try
37+ {
38+ subscriptions [ mcpServer . SessionId ] = new ConcurrentDictionary < string , byte > ( ) ;
39+ // Start an instance of SubscriptionMessageSender for this session
40+ using var subscriptionSender = new SubscriptionMessageSender ( mcpServer , subscriptions [ mcpServer . SessionId ] ) ;
41+ await subscriptionSender . StartAsync ( token ) ;
42+ // Start an instance of LoggingUpdateMessageSender for this session
43+ using var loggingSender = new LoggingUpdateMessageSender ( mcpServer ) ;
44+ await loggingSender . StartAsync ( token ) ;
45+ await mcpServer . RunAsync ( token ) ;
46+ }
47+ finally
48+ {
49+ // This code runs when the session ends
50+ subscriptions . TryRemove ( mcpServer . SessionId , out _ ) ;
51+ }
52+ } ;
53+ } )
3154 . WithTools < AddTool > ( )
3255 . WithTools < AnnotatedMessageTool > ( )
3356 . WithTools < EchoTool > ( )
4063 . WithResources < SimpleResourceType > ( )
4164 . WithSubscribeToResourcesHandler ( async ( ctx , ct ) =>
4265 {
43- var uri = ctx . Params ? . Uri ;
44-
45- if ( uri is not null )
66+ if ( ctx . Server . SessionId == null )
4667 {
47- subscriptions . Add ( uri ) ;
68+ throw new McpException ( "Cannot add subscription for server with null SessionId" ) ;
69+ }
70+ if ( ctx . Params ? . Uri is { } uri )
71+ {
72+ subscriptions [ ctx . Server . SessionId ] . TryAdd ( uri , 0 ) ;
4873
4974 await ctx . Server . SampleAsync ( [
5075 new ChatMessage ( ChatRole . System , "You are a helpful test server" ) ,
@@ -62,10 +87,13 @@ await ctx.Server.SampleAsync([
6287 } )
6388 . WithUnsubscribeFromResourcesHandler ( async ( ctx , ct ) =>
6489 {
65- var uri = ctx . Params ? . Uri ;
66- if ( uri is not null )
90+ if ( ctx . Server . SessionId == null )
91+ {
92+ throw new McpException ( "Cannot remove subscription for server with null SessionId" ) ;
93+ }
94+ if ( ctx . Params ? . Uri is { } uri )
6795 {
68- subscriptions . Remove ( uri ) ;
96+ subscriptions [ ctx . Server . SessionId ] . TryRemove ( uri , out _ ) ;
6997 }
7098 return new EmptyResult ( ) ;
7199 } )
@@ -126,13 +154,13 @@ await ctx.Server.SampleAsync([
126154 throw new McpProtocolException ( "Missing required argument 'level'" , McpErrorCode . InvalidParams ) ;
127155 }
128156
129- _minimumLoggingLevel = ctx . Params . Level ;
157+ // The SDK updates the LoggingLevel field of the IMcpServer
130158
131159 await ctx . Server . SendNotificationAsync ( "notifications/message" , new
132160 {
133161 Level = "debug" ,
134162 Logger = "test-server" ,
135- Data = $ "Logging level set to { _minimumLoggingLevel } ",
163+ Data = $ "Logging level set to { ctx . Params . Level } ",
136164 } , cancellationToken : ct ) ;
137165
138166 return new EmptyResult ( ) ;
@@ -145,10 +173,8 @@ await ctx.Server.SampleAsync([
145173 . WithLogging ( b => b . SetResourceBuilder ( resource ) )
146174 . UseOtlpExporter ( ) ;
147175
148- builder . Services . AddSingleton ( subscriptions ) ;
149- builder . Services . AddHostedService < SubscriptionMessageSender > ( ) ;
150- builder . Services . AddHostedService < LoggingUpdateMessageSender > ( ) ;
176+ var app = builder . Build ( ) ;
151177
152- builder . Services . AddSingleton < Func < LoggingLevel > > ( _ => ( ) => _minimumLoggingLevel ) ;
178+ app . MapMcp ( ) ;
153179
154- await builder . Build ( ) . RunAsync ( ) ;
180+ app . Run ( ) ;
0 commit comments