@@ -20,6 +20,7 @@ import (
2020 "path/filepath"
2121 "reflect"
2222 "slices"
23+ "strings"
2324 "sync"
2425 "sync/atomic"
2526 "time"
@@ -105,6 +106,9 @@ type ServerOptions struct {
105106 SubscribeHandler func (context.Context , * SubscribeRequest ) error
106107 // Function called when a client session unsubscribes from a resource.
107108 UnsubscribeHandler func (context.Context , * UnsubscribeRequest ) error
109+ // ListResourcesHandler, if non-nil, serves resources/list dynamically.
110+ // See [ListResourcesHandler] for semantics.
111+ ListResourcesHandler ListResourcesHandler
108112
109113 // Capabilities optionally configures the server's default capabilities,
110114 // before any capabilities are inferred from other configuration or server
@@ -643,7 +647,7 @@ func (s *Server) capabilities() *ServerCapabilities {
643647 }
644648
645649 // Augment with resources capability if resources/templates exist or legacy HasResources is set.
646- if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
650+ if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 || s . opts . ListResourcesHandler != nil {
647651 if caps .Resources == nil {
648652 caps .Resources = & ResourceCapabilities {ListChanged : true }
649653 }
@@ -974,25 +978,177 @@ func (s *Server) callTool(ctx context.Context, req *CallToolRequest) (*CallToolR
974978 return res , err
975979}
976980
977- func (s * Server ) listResources (_ context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
978- s .mu .Lock ()
979- defer s .mu .Unlock ()
981+ func (s * Server ) listResources (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
980982 if req .Params == nil {
981983 req .Params = & ListResourcesParams {}
982984 }
983- res , err := paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, func (res * ListResourcesResult , resources []* serverResource ) {
984- res .Resources = []* Resource {} // avoid JSON null
985- for _ , r := range resources {
986- res .Resources = append (res .Resources , r .resource )
987- }
988- })
985+ var (
986+ res * ListResourcesResult
987+ err error
988+ )
989+ if s .opts .ListResourcesHandler == nil {
990+ s .mu .Lock ()
991+ res , err = paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, populateListResourcesResult )
992+ s .mu .Unlock ()
993+ } else {
994+ res , err = s .listResourcesWithHandler (ctx , req )
995+ }
989996 if err != nil {
990997 return nil , err
991998 }
992999 res .setDefaultCacheableValues ()
9931000 return res , nil
9941001}
9951002
1003+ func populateListResourcesResult (res * ListResourcesResult , resources []* serverResource ) {
1004+ res .Resources = []* Resource {} // avoid JSON null
1005+ for _ , r := range resources {
1006+ res .Resources = append (res .Resources , r .resource )
1007+ }
1008+ }
1009+
1010+ const (
1011+ listResourcesPhaseStatic = "static"
1012+ listResourcesPhaseHandler = "handler"
1013+ listResourcesCursorPrefix = "lr1:"
1014+ )
1015+
1016+ type listResourcesCursor struct {
1017+ Phase string
1018+ StaticCursor string
1019+ HandlerCursor string
1020+ }
1021+
1022+ func encodeListResourcesCursor (c listResourcesCursor ) (string , error ) {
1023+ var buf bytes.Buffer
1024+ if err := gob .NewEncoder (& buf ).Encode (c ); err != nil {
1025+ return "" , fmt .Errorf ("failed to encode list resources cursor: %w" , err )
1026+ }
1027+ return listResourcesCursorPrefix + base64 .URLEncoding .EncodeToString (buf .Bytes ()), nil
1028+ }
1029+
1030+ func decodeListResourcesCursor (cursor string ) (* listResourcesCursor , error ) {
1031+ if cursor == "" {
1032+ return & listResourcesCursor {Phase : listResourcesPhaseStatic }, nil
1033+ }
1034+ if ! strings .HasPrefix (cursor , listResourcesCursorPrefix ) {
1035+ return nil , fmt .Errorf ("not a list resources cursor" )
1036+ }
1037+ decoded , err := base64 .URLEncoding .DecodeString (cursor [len (listResourcesCursorPrefix ):])
1038+ if err != nil {
1039+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
1040+ }
1041+ var c listResourcesCursor
1042+ if err := gob .NewDecoder (bytes .NewReader (decoded )).Decode (& c ); err != nil {
1043+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
1044+ }
1045+ return & c , nil
1046+ }
1047+
1048+ func normalizeListResourcesResult (res * ListResourcesResult ) * ListResourcesResult {
1049+ if res == nil {
1050+ return & ListResourcesResult {Resources : []* Resource {}}
1051+ }
1052+ if res .Resources == nil {
1053+ res2 := * res
1054+ res2 .Resources = []* Resource {}
1055+ return & res2
1056+ }
1057+ return res
1058+ }
1059+
1060+ func (s * Server ) listResourcesWithHandler (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
1061+ handler := s .opts .ListResourcesHandler
1062+
1063+ s .mu .Lock ()
1064+ hasStatic := s .resources .len () > 0
1065+ pageSize := s .opts .PageSize
1066+ s .mu .Unlock ()
1067+
1068+ if ! hasStatic {
1069+ res , err := handler (ctx , req )
1070+ if err != nil {
1071+ return nil , err
1072+ }
1073+ return normalizeListResourcesResult (res ), nil
1074+ }
1075+
1076+ phase , err := decodeListResourcesCursor (req .Params .Cursor )
1077+ if err != nil {
1078+ return nil , jsonrpc2 .ErrInvalidParams
1079+ }
1080+
1081+ if phase .Phase == listResourcesPhaseHandler {
1082+ handlerReq := & ListResourcesRequest {
1083+ Session : req .Session ,
1084+ Params : & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .HandlerCursor },
1085+ }
1086+ res , err := handler (ctx , handlerReq )
1087+ if err != nil {
1088+ return nil , err
1089+ }
1090+ res = normalizeListResourcesResult (res )
1091+ if res .NextCursor != "" {
1092+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1093+ Phase : listResourcesPhaseHandler ,
1094+ HandlerCursor : res .NextCursor ,
1095+ })
1096+ if err != nil {
1097+ return nil , err
1098+ }
1099+ }
1100+ return res , nil
1101+ }
1102+
1103+ s .mu .Lock ()
1104+ staticParams := & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .StaticCursor }
1105+ res , err := paginateList (s .resources , pageSize , staticParams , & ListResourcesResult {}, populateListResourcesResult )
1106+ s .mu .Unlock ()
1107+ if err != nil {
1108+ return nil , err
1109+ }
1110+
1111+ if res .NextCursor != "" {
1112+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1113+ Phase : listResourcesPhaseStatic ,
1114+ StaticCursor : res .NextCursor ,
1115+ })
1116+ if err != nil {
1117+ return nil , err
1118+ }
1119+ return res , nil
1120+ }
1121+
1122+ if len (res .Resources ) > 0 {
1123+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {Phase : listResourcesPhaseHandler })
1124+ if err != nil {
1125+ return nil , err
1126+ }
1127+ return res , nil
1128+ }
1129+
1130+ handlerReq := & ListResourcesRequest {
1131+ Session : req .Session ,
1132+ Params : & ListResourcesParams {Meta : req .Params .Meta },
1133+ }
1134+ hRes , err := handler (ctx , handlerReq )
1135+ if err != nil {
1136+ return nil , err
1137+ }
1138+ hRes = normalizeListResourcesResult (hRes )
1139+
1140+ if hRes .NextCursor != "" {
1141+ hRes .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1142+ Phase : listResourcesPhaseHandler ,
1143+ HandlerCursor : hRes .NextCursor ,
1144+ })
1145+ if err != nil {
1146+ return nil , err
1147+ }
1148+ }
1149+ return hRes , nil
1150+ }
1151+
9961152func (s * Server ) listResourceTemplates (_ context.Context , req * ListResourceTemplatesRequest ) (* ListResourceTemplatesResult , error ) {
9971153 s .mu .Lock ()
9981154 defer s .mu .Unlock ()
0 commit comments