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