@@ -20,6 +20,7 @@ import (
2020 "path/filepath"
2121 "reflect"
2222 "slices"
23+ "strings"
2324 "sync"
2425 "sync/atomic"
2526 "time"
@@ -104,6 +105,9 @@ type ServerOptions struct {
104105 SubscribeHandler func (context.Context , * SubscribeRequest ) error
105106 // Function called when a client session unsubscribes from a resource.
106107 UnsubscribeHandler func (context.Context , * UnsubscribeRequest ) error
108+ // ListResourcesHandler, if non-nil, serves resources/list dynamically.
109+ // See [ListResourcesHandler] for semantics.
110+ ListResourcesHandler ListResourcesHandler
107111
108112 // Capabilities optionally configures the server's default capabilities,
109113 // before any capabilities are inferred from other configuration or server
@@ -642,7 +646,7 @@ func (s *Server) capabilities() *ServerCapabilities {
642646 }
643647
644648 // Augment with resources capability if resources/templates exist or legacy HasResources is set.
645- if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
649+ if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 || s . opts . ListResourcesHandler != nil {
646650 if caps .Resources == nil {
647651 caps .Resources = & ResourceCapabilities {ListChanged : true }
648652 }
@@ -953,25 +957,177 @@ func (s *Server) callTool(ctx context.Context, req *CallToolRequest) (*CallToolR
953957 return res , err
954958}
955959
956- func (s * Server ) listResources (_ context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
957- s .mu .Lock ()
958- defer s .mu .Unlock ()
960+ func (s * Server ) listResources (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
959961 if req .Params == nil {
960962 req .Params = & ListResourcesParams {}
961963 }
962- res , err := paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, func (res * ListResourcesResult , resources []* serverResource ) {
963- res .Resources = []* Resource {} // avoid JSON null
964- for _ , r := range resources {
965- res .Resources = append (res .Resources , r .resource )
966- }
967- })
964+ var (
965+ res * ListResourcesResult
966+ err error
967+ )
968+ if s .opts .ListResourcesHandler == nil {
969+ s .mu .Lock ()
970+ res , err = paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, populateListResourcesResult )
971+ s .mu .Unlock ()
972+ } else {
973+ res , err = s .listResourcesWithHandler (ctx , req )
974+ }
968975 if err != nil {
969976 return nil , err
970977 }
971978 res .setDefaultCacheableValues ()
972979 return res , nil
973980}
974981
982+ func populateListResourcesResult (res * ListResourcesResult , resources []* serverResource ) {
983+ res .Resources = []* Resource {} // avoid JSON null
984+ for _ , r := range resources {
985+ res .Resources = append (res .Resources , r .resource )
986+ }
987+ }
988+
989+ const (
990+ listResourcesPhaseStatic = "static"
991+ listResourcesPhaseHandler = "handler"
992+ listResourcesCursorPrefix = "lr1:"
993+ )
994+
995+ type listResourcesCursor struct {
996+ Phase string
997+ StaticCursor string
998+ HandlerCursor string
999+ }
1000+
1001+ func encodeListResourcesCursor (c listResourcesCursor ) (string , error ) {
1002+ var buf bytes.Buffer
1003+ if err := gob .NewEncoder (& buf ).Encode (c ); err != nil {
1004+ return "" , fmt .Errorf ("failed to encode list resources cursor: %w" , err )
1005+ }
1006+ return listResourcesCursorPrefix + base64 .URLEncoding .EncodeToString (buf .Bytes ()), nil
1007+ }
1008+
1009+ func decodeListResourcesCursor (cursor string ) (* listResourcesCursor , error ) {
1010+ if cursor == "" {
1011+ return & listResourcesCursor {Phase : listResourcesPhaseStatic }, nil
1012+ }
1013+ if ! strings .HasPrefix (cursor , listResourcesCursorPrefix ) {
1014+ return nil , fmt .Errorf ("not a list resources cursor" )
1015+ }
1016+ decoded , err := base64 .URLEncoding .DecodeString (cursor [len (listResourcesCursorPrefix ):])
1017+ if err != nil {
1018+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
1019+ }
1020+ var c listResourcesCursor
1021+ if err := gob .NewDecoder (bytes .NewReader (decoded )).Decode (& c ); err != nil {
1022+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
1023+ }
1024+ return & c , nil
1025+ }
1026+
1027+ func normalizeListResourcesResult (res * ListResourcesResult ) * ListResourcesResult {
1028+ if res == nil {
1029+ return & ListResourcesResult {Resources : []* Resource {}}
1030+ }
1031+ if res .Resources == nil {
1032+ res2 := * res
1033+ res2 .Resources = []* Resource {}
1034+ return & res2
1035+ }
1036+ return res
1037+ }
1038+
1039+ func (s * Server ) listResourcesWithHandler (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
1040+ handler := s .opts .ListResourcesHandler
1041+
1042+ s .mu .Lock ()
1043+ hasStatic := s .resources .len () > 0
1044+ pageSize := s .opts .PageSize
1045+ s .mu .Unlock ()
1046+
1047+ if ! hasStatic {
1048+ res , err := handler (ctx , req )
1049+ if err != nil {
1050+ return nil , err
1051+ }
1052+ return normalizeListResourcesResult (res ), nil
1053+ }
1054+
1055+ phase , err := decodeListResourcesCursor (req .Params .Cursor )
1056+ if err != nil {
1057+ return nil , jsonrpc2 .ErrInvalidParams
1058+ }
1059+
1060+ if phase .Phase == listResourcesPhaseHandler {
1061+ handlerReq := & ListResourcesRequest {
1062+ Session : req .Session ,
1063+ Params : & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .HandlerCursor },
1064+ }
1065+ res , err := handler (ctx , handlerReq )
1066+ if err != nil {
1067+ return nil , err
1068+ }
1069+ res = normalizeListResourcesResult (res )
1070+ if res .NextCursor != "" {
1071+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1072+ Phase : listResourcesPhaseHandler ,
1073+ HandlerCursor : res .NextCursor ,
1074+ })
1075+ if err != nil {
1076+ return nil , err
1077+ }
1078+ }
1079+ return res , nil
1080+ }
1081+
1082+ s .mu .Lock ()
1083+ staticParams := & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .StaticCursor }
1084+ res , err := paginateList (s .resources , pageSize , staticParams , & ListResourcesResult {}, populateListResourcesResult )
1085+ s .mu .Unlock ()
1086+ if err != nil {
1087+ return nil , err
1088+ }
1089+
1090+ if res .NextCursor != "" {
1091+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1092+ Phase : listResourcesPhaseStatic ,
1093+ StaticCursor : res .NextCursor ,
1094+ })
1095+ if err != nil {
1096+ return nil , err
1097+ }
1098+ return res , nil
1099+ }
1100+
1101+ if len (res .Resources ) > 0 {
1102+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {Phase : listResourcesPhaseHandler })
1103+ if err != nil {
1104+ return nil , err
1105+ }
1106+ return res , nil
1107+ }
1108+
1109+ handlerReq := & ListResourcesRequest {
1110+ Session : req .Session ,
1111+ Params : & ListResourcesParams {Meta : req .Params .Meta },
1112+ }
1113+ hRes , err := handler (ctx , handlerReq )
1114+ if err != nil {
1115+ return nil , err
1116+ }
1117+ hRes = normalizeListResourcesResult (hRes )
1118+
1119+ if hRes .NextCursor != "" {
1120+ hRes .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1121+ Phase : listResourcesPhaseHandler ,
1122+ HandlerCursor : hRes .NextCursor ,
1123+ })
1124+ if err != nil {
1125+ return nil , err
1126+ }
1127+ }
1128+ return hRes , nil
1129+ }
1130+
9751131func (s * Server ) listResourceTemplates (_ context.Context , req * ListResourceTemplatesRequest ) (* ListResourceTemplatesResult , error ) {
9761132 s .mu .Lock ()
9771133 defer s .mu .Unlock ()
0 commit comments