@@ -13,6 +13,7 @@ import (
1313 "fmt"
1414 "iter"
1515 "log"
16+ "maps"
1617 "net/url"
1718 "path/filepath"
1819 "slices"
@@ -43,6 +44,7 @@ type Server struct {
4344 sessions []* ServerSession
4445 sendingMethodHandler_ MethodHandler [* ServerSession ]
4546 receivingMethodHandler_ MethodHandler [* ServerSession ]
47+ resourceSubscriptions map [string ]map [* ServerSession ]bool // uri -> session -> bool
4648}
4749
4850// ServerOptions is used to configure behavior of the server.
@@ -64,6 +66,10 @@ type ServerOptions struct {
6466 // If the peer fails to respond to pings originating from the keepalive check,
6567 // the session is automatically closed.
6668 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
6773}
6874
6975// NewServer creates a new MCP server. The resulting server has no features:
@@ -89,7 +95,12 @@ func NewServer(impl *Implementation, opts *ServerOptions) *Server {
8995 if opts .PageSize == 0 {
9096 opts .PageSize = DefaultPageSize
9197 }
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+ }
93104 return & Server {
94105 impl : impl ,
95106 opts : * opts ,
@@ -99,6 +110,7 @@ func NewServer(impl *Implementation, opts *ServerOptions) *Server {
99110 resourceTemplates : newFeatureSet (func (t * serverResourceTemplate ) string { return t .resourceTemplate .URITemplate }),
100111 sendingMethodHandler_ : defaultSendingMethodHandler [* ServerSession ],
101112 receivingMethodHandler_ : defaultReceivingMethodHandler [* ServerSession ],
113+ resourceSubscriptions : make (map [string ]map [* ServerSession ]bool ),
102114 }
103115}
104116
@@ -225,6 +237,9 @@ func (s *Server) capabilities() *serverCapabilities {
225237 }
226238 if s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
227239 caps .Resources = & resourceCapabilities {ListChanged : true }
240+ if s .opts .SubscribeHandler != nil {
241+ caps .Resources .Subscribe = true
242+ }
228243 }
229244 return caps
230245}
@@ -428,6 +443,57 @@ func fileResourceHandler(dir string) ResourceHandler {
428443 }
429444}
430445
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+
431497// Run runs the server over the given transport, which must be persistent.
432498//
433499// Run blocks until the client terminates the connection or the provided
@@ -475,6 +541,10 @@ func (s *Server) disconnect(cc *ServerSession) {
475541 s .sessions = slices .DeleteFunc (s .sessions , func (cc2 * ServerSession ) bool {
476542 return cc2 == cc
477543 })
544+
545+ for _ , subscribedSessions := range s .resourceSubscriptions {
546+ delete (subscribedSessions , cc )
547+ }
478548}
479549
480550// Connect connects the MCP server over the given transport and starts handling
@@ -616,6 +686,8 @@ var serverMethodInfos = map[string]methodInfo{
616686 methodListResourceTemplates : newMethodInfo (serverMethod ((* Server ).listResourceTemplates )),
617687 methodReadResource : newMethodInfo (serverMethod ((* Server ).readResource )),
618688 methodSetLevel : newMethodInfo (sessionMethod ((* ServerSession ).setLevel )),
689+ methodSubscribe : newMethodInfo (serverMethod ((* Server ).subscribe )),
690+ methodUnsubscribe : newMethodInfo (serverMethod ((* Server ).unsubscribe )),
619691 notificationInitialized : newMethodInfo (serverMethod ((* Server ).callInitializedHandler )),
620692 notificationRootsListChanged : newMethodInfo (serverMethod ((* Server ).callRootsListChangedHandler )),
621693 notificationProgress : newMethodInfo (sessionMethod ((* ServerSession ).callProgressNotificationHandler )),
0 commit comments