@@ -13,6 +13,7 @@ import (
13
13
"fmt"
14
14
"iter"
15
15
"log"
16
+ "maps"
16
17
"net/url"
17
18
"path/filepath"
18
19
"slices"
@@ -43,6 +44,7 @@ type Server struct {
43
44
sessions []* ServerSession
44
45
sendingMethodHandler_ MethodHandler [* ServerSession ]
45
46
receivingMethodHandler_ MethodHandler [* ServerSession ]
47
+ resourceSubscriptions map [string ]map [* ServerSession ]bool // uri -> session -> bool
46
48
}
47
49
48
50
// ServerOptions is used to configure behavior of the server.
@@ -64,6 +66,10 @@ type ServerOptions struct {
64
66
// If the peer fails to respond to pings originating from the keepalive check,
65
67
// the session is automatically closed.
66
68
KeepAlive time.Duration
69
+ // Function called when a client session subscribes to a resource.
70
+ SubscribeHandler func (context.Context , * SubscribeParams ) error
71
+ // Function called when a client session unsubscribes from a resource.
72
+ UnsubscribeHandler func (context.Context , * UnsubscribeParams ) error
67
73
}
68
74
69
75
// NewServer creates a new MCP server. The resulting server has no features:
@@ -89,7 +95,12 @@ func NewServer(impl *Implementation, opts *ServerOptions) *Server {
89
95
if opts .PageSize == 0 {
90
96
opts .PageSize = DefaultPageSize
91
97
}
92
-
98
+ if opts .SubscribeHandler != nil && opts .UnsubscribeHandler == nil {
99
+ panic ("SubscribeHandler requires UnsubscribeHandler" )
100
+ }
101
+ if opts .UnsubscribeHandler != nil && opts .SubscribeHandler == nil {
102
+ panic ("UnsubscribeHandler requires SubscribeHandler" )
103
+ }
93
104
return & Server {
94
105
impl : impl ,
95
106
opts : * opts ,
@@ -99,6 +110,7 @@ func NewServer(impl *Implementation, opts *ServerOptions) *Server {
99
110
resourceTemplates : newFeatureSet (func (t * serverResourceTemplate ) string { return t .resourceTemplate .URITemplate }),
100
111
sendingMethodHandler_ : defaultSendingMethodHandler [* ServerSession ],
101
112
receivingMethodHandler_ : defaultReceivingMethodHandler [* ServerSession ],
113
+ resourceSubscriptions : make (map [string ]map [* ServerSession ]bool ),
102
114
}
103
115
}
104
116
@@ -225,6 +237,9 @@ func (s *Server) capabilities() *serverCapabilities {
225
237
}
226
238
if s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
227
239
caps .Resources = & resourceCapabilities {ListChanged : true }
240
+ if s .opts .SubscribeHandler != nil {
241
+ caps .Resources .Subscribe = true
242
+ }
228
243
}
229
244
return caps
230
245
}
@@ -428,6 +443,57 @@ func fileResourceHandler(dir string) ResourceHandler {
428
443
}
429
444
}
430
445
446
+ // ResourceUpdated sends a notification to all clients that have subscribed to the
447
+ // resource specified in params. This method is the primary way for a
448
+ // server author to signal that a resource has changed.
449
+ func (s * Server ) ResourceUpdated (ctx context.Context , params * ResourceUpdatedNotificationParams ) error {
450
+ s .mu .Lock ()
451
+ subscribedSessions := s .resourceSubscriptions [params .URI ]
452
+ sessions := slices .Collect (maps .Keys (subscribedSessions ))
453
+ s .mu .Unlock ()
454
+ notifySessions (sessions , notificationResourceUpdated , params )
455
+ return nil
456
+ }
457
+
458
+ func (s * Server ) subscribe (ctx context.Context , ss * ServerSession , params * SubscribeParams ) (* emptyResult , error ) {
459
+ if s .opts .SubscribeHandler == nil {
460
+ return nil , fmt .Errorf ("%w: server does not support resource subscriptions" , jsonrpc2 .ErrMethodNotFound )
461
+ }
462
+ if err := s .opts .SubscribeHandler (ctx , params ); err != nil {
463
+ return nil , err
464
+ }
465
+
466
+ s .mu .Lock ()
467
+ defer s .mu .Unlock ()
468
+ if s .resourceSubscriptions [params .URI ] == nil {
469
+ s .resourceSubscriptions [params .URI ] = make (map [* ServerSession ]bool )
470
+ }
471
+ s.resourceSubscriptions [params.URI ][ss ] = true
472
+
473
+ return & emptyResult {}, nil
474
+ }
475
+
476
+ func (s * Server ) unsubscribe (ctx context.Context , ss * ServerSession , params * UnsubscribeParams ) (* emptyResult , error ) {
477
+ if s .opts .UnsubscribeHandler == nil {
478
+ return nil , jsonrpc2 .ErrMethodNotFound
479
+ }
480
+
481
+ if err := s .opts .UnsubscribeHandler (ctx , params ); err != nil {
482
+ return nil , err
483
+ }
484
+
485
+ s .mu .Lock ()
486
+ defer s .mu .Unlock ()
487
+ if subscribedSessions , ok := s .resourceSubscriptions [params .URI ]; ok {
488
+ delete (subscribedSessions , ss )
489
+ if len (subscribedSessions ) == 0 {
490
+ delete (s .resourceSubscriptions , params .URI )
491
+ }
492
+ }
493
+
494
+ return & emptyResult {}, nil
495
+ }
496
+
431
497
// Run runs the server over the given transport, which must be persistent.
432
498
//
433
499
// Run blocks until the client terminates the connection or the provided
@@ -475,6 +541,10 @@ func (s *Server) disconnect(cc *ServerSession) {
475
541
s .sessions = slices .DeleteFunc (s .sessions , func (cc2 * ServerSession ) bool {
476
542
return cc2 == cc
477
543
})
544
+
545
+ for _ , subscribedSessions := range s .resourceSubscriptions {
546
+ delete (subscribedSessions , cc )
547
+ }
478
548
}
479
549
480
550
// Connect connects the MCP server over the given transport and starts handling
@@ -616,6 +686,8 @@ var serverMethodInfos = map[string]methodInfo{
616
686
methodListResourceTemplates : newMethodInfo (serverMethod ((* Server ).listResourceTemplates )),
617
687
methodReadResource : newMethodInfo (serverMethod ((* Server ).readResource )),
618
688
methodSetLevel : newMethodInfo (sessionMethod ((* ServerSession ).setLevel )),
689
+ methodSubscribe : newMethodInfo (serverMethod ((* Server ).subscribe )),
690
+ methodUnsubscribe : newMethodInfo (serverMethod ((* Server ).unsubscribe )),
619
691
notificationInitialized : newMethodInfo (serverMethod ((* Server ).callInitializedHandler )),
620
692
notificationRootsListChanged : newMethodInfo (serverMethod ((* Server ).callRootsListChangedHandler )),
621
693
notificationProgress : newMethodInfo (sessionMethod ((* ServerSession ).callProgressNotificationHandler )),
0 commit comments