@@ -460,3 +460,135 @@ func (s *MCPServer) DeleteSessionTools(sessionID string, names ...string) error
460460
461461 return nil
462462}
463+
464+ // AddSessionResource adds a resource for a specific session
465+ func (s * MCPServer ) AddSessionResource (sessionID string , resource mcp.Resource , handler ResourceHandlerFunc ) error {
466+ return s .AddSessionResources (sessionID , ServerResource {Resource : resource , Handler : handler })
467+ }
468+
469+ // AddSessionResources adds resources for a specific session
470+ func (s * MCPServer ) AddSessionResources (sessionID string , resources ... ServerResource ) error {
471+ sessionValue , ok := s .sessions .Load (sessionID )
472+ if ! ok {
473+ return ErrSessionNotFound
474+ }
475+
476+ session , ok := sessionValue .(SessionWithResources )
477+ if ! ok {
478+ return ErrSessionDoesNotSupportResources
479+ }
480+
481+ // For session resources, we want listChanged enabled by default
482+ s .implicitlyRegisterCapabilities (
483+ func () bool { return s .capabilities .resources != nil },
484+ func () { s .capabilities .resources = & resourceCapabilities {listChanged : true } },
485+ )
486+
487+ // Get existing resources (this should return a thread-safe copy)
488+ sessionResources := session .GetSessionResources ()
489+
490+ // Create a new map to avoid concurrent modification issues
491+ newSessionResources := make (map [string ]ServerResource , len (sessionResources )+ len (resources ))
492+
493+ // Copy existing resources
494+ for k , v := range sessionResources {
495+ newSessionResources [k ] = v
496+ }
497+
498+ // Add new resources
499+ for _ , resource := range resources {
500+ newSessionResources [resource .Resource .URI ] = resource
501+ }
502+
503+ // Set the resources (this should be thread-safe)
504+ session .SetSessionResources (newSessionResources )
505+
506+ // It only makes sense to send resource notifications to initialized sessions --
507+ // if we're not initialized yet the client can't possibly have sent their
508+ // initial resources/list message.
509+ //
510+ // For initialized sessions, honor resources.listChanged, which is specifically
511+ // about whether notifications will be sent or not.
512+ // see <https://modelcontextprotocol.io/specification/2025-03-26/server/resources#capabilities>
513+ if session .Initialized () && s .capabilities .resources != nil && s .capabilities .resources .listChanged {
514+ // Send notification only to this session
515+ if err := s .SendNotificationToSpecificClient (sessionID , "notifications/resources/list_changed" , nil ); err != nil {
516+ // Log the error but don't fail the operation
517+ // The resources were successfully added, but notification failed
518+ if s .hooks != nil && len (s .hooks .OnError ) > 0 {
519+ hooks := s .hooks
520+ go func (sID string , hooks * Hooks ) {
521+ ctx := context .Background ()
522+ hooks .onError (ctx , nil , "notification" , map [string ]any {
523+ "method" : "notifications/resources/list_changed" ,
524+ "sessionID" : sID ,
525+ }, fmt .Errorf ("failed to send notification after adding resources: %w" , err ))
526+ }(sessionID , hooks )
527+ }
528+ }
529+ }
530+
531+ return nil
532+ }
533+
534+ // DeleteSessionResources removes resources from a specific session
535+ func (s * MCPServer ) DeleteSessionResources (sessionID string , uris ... string ) error {
536+ sessionValue , ok := s .sessions .Load (sessionID )
537+ if ! ok {
538+ return ErrSessionNotFound
539+ }
540+
541+ session , ok := sessionValue .(SessionWithResources )
542+ if ! ok {
543+ return ErrSessionDoesNotSupportResources
544+ }
545+
546+ // Get existing resources (this should return a thread-safe copy)
547+ sessionResources := session .GetSessionResources ()
548+ if sessionResources == nil {
549+ return nil
550+ }
551+
552+ // Create a new map to avoid concurrent modification issues
553+ newSessionResources := make (map [string ]ServerResource , len (sessionResources ))
554+
555+ // Copy existing resources except those being deleted
556+ for k , v := range sessionResources {
557+ newSessionResources [k ] = v
558+ }
559+
560+ // Remove specified resources
561+ for _ , uri := range uris {
562+ delete (newSessionResources , uri )
563+ }
564+
565+ // Set the resources (this should be thread-safe)
566+ session .SetSessionResources (newSessionResources )
567+
568+ // It only makes sense to send resource notifications to initialized sessions --
569+ // if we're not initialized yet the client can't possibly have sent their
570+ // initial resources/list message.
571+ //
572+ // For initialized sessions, honor resources.listChanged, which is specifically
573+ // about whether notifications will be sent or not.
574+ // see <https://modelcontextprotocol.io/specification/2025-03-26/server/resources#capabilities>
575+ if session .Initialized () && s .capabilities .resources != nil && s .capabilities .resources .listChanged {
576+ // Send notification only to this session
577+ if err := s .SendNotificationToSpecificClient (sessionID , "notifications/resources/list_changed" , nil ); err != nil {
578+ // Log the error but don't fail the operation
579+ // The resources were successfully deleted, but notification failed
580+ if s .hooks != nil && len (s .hooks .OnError ) > 0 {
581+ hooks := s .hooks
582+ go func (sID string , hooks * Hooks ) {
583+ ctx := context .Background ()
584+ hooks .onError (ctx , nil , "notification" , map [string ]any {
585+ "method" : "notifications/resources/list_changed" ,
586+ "sessionID" : sID ,
587+ }, fmt .Errorf ("failed to send notification after deleting resources: %w" , err ))
588+ }(sessionID , hooks )
589+ }
590+ }
591+ }
592+
593+ return nil
594+ }
0 commit comments