1+ using ModelContextProtocol ;
2+ using ModelContextProtocol . Server ;
3+
4+ // This class manages subscriptions to resources by McpServer instances.
5+ // The subscription information must be accessed in a thread-safe manner since handlers
6+ // can run in parallel even in the context of a single session.
7+ static class SubscriptionManager
8+ {
9+ // Subscriptions tracks resource URIs to bags of McpServer instances (thread-safe via locking)
10+ private static Dictionary < string , List < IMcpServer > > subscriptions = new ( ) ;
11+
12+ // SessionSubscriptions is a secondary index to subscriptions to allow efficient removal of all
13+ // subscriptions for a given session when it ends. (thread-safe via locking)
14+ private static Dictionary < string /* sessionId */ , List < string > /* uris */ > sessionSubscriptions = new ( ) ;
15+
16+ private static readonly object _subscriptionsLock = new ( ) ;
17+
18+ public static void AddSubscription ( string uri , IMcpServer server )
19+ {
20+ if ( server . SessionId == null )
21+ {
22+ throw new McpException ( "Cannot add subscription for server with null SessionId" ) ;
23+ }
24+ lock ( _subscriptionsLock )
25+ {
26+ subscriptions [ uri ] ??= new List < IMcpServer > ( ) ;
27+ subscriptions [ uri ] . Add ( server ) ;
28+ sessionSubscriptions [ server . SessionId ] ??= new List < string > ( ) ;
29+ sessionSubscriptions [ server . SessionId ] . Add ( uri ) ;
30+ }
31+ }
32+
33+ public static void RemoveSubscription ( string uri , IMcpServer server )
34+ {
35+ if ( server . SessionId == null )
36+ {
37+ throw new McpException ( "Cannot remove subscription for server with null SessionId" ) ;
38+ }
39+ lock ( _subscriptionsLock )
40+ {
41+ if ( subscriptions . ContainsKey ( uri ) )
42+ {
43+ // Remove the server from the list of subscriptions for the URI
44+ subscriptions [ uri ] = subscriptions [ uri ] . Where ( s => s . SessionId != server . SessionId ) . ToList ( ) ;
45+ if ( subscriptions [ uri ] ? . Count == 0 )
46+ {
47+ subscriptions . Remove ( uri ) ;
48+ }
49+ }
50+ // Remove the URI from the list of subscriptions for the session
51+ sessionSubscriptions [ server . SessionId ] ? . Remove ( uri ) ;
52+ if ( sessionSubscriptions [ server . SessionId ] ? . Count == 0 )
53+ {
54+ sessionSubscriptions . Remove ( server . SessionId ) ;
55+ }
56+ }
57+ }
58+
59+ public static IDictionary < string , List < IMcpServer > > GetSubscriptions ( )
60+ {
61+ lock ( _subscriptionsLock )
62+ {
63+ // Return a copy of the subscriptions dictionary to avoid external modification
64+ return subscriptions . ToDictionary ( entry => entry . Key ,
65+ entry => entry . Value . ToList ( ) ) ;
66+ }
67+ }
68+
69+ public static void RemoveAllSubscriptions ( IMcpServer server )
70+ {
71+ if ( server . SessionId is { } sessionId )
72+ {
73+ lock ( _subscriptionsLock )
74+ {
75+ // Remove all subscriptions for the session
76+ if ( sessionSubscriptions . TryGetValue ( sessionId , out var uris ) )
77+ {
78+ foreach ( var uri in uris )
79+ {
80+ subscriptions [ uri ] = subscriptions [ uri ] . Where ( s => s . SessionId != sessionId ) . ToList ( ) ;
81+ if ( subscriptions [ uri ] ? . Count == 0 )
82+ {
83+ subscriptions . Remove ( uri ) ;
84+ }
85+ }
86+ sessionSubscriptions . Remove ( sessionId ) ;
87+ }
88+ }
89+ }
90+ }
91+ }
0 commit comments