@@ -20,6 +20,7 @@ import (
2020 "path/filepath"
2121 "reflect"
2222 "slices"
23+ "strings"
2324 "sync"
2425 "sync/atomic"
2526 "time"
@@ -90,6 +91,9 @@ type ServerOptions struct {
9091 SubscribeHandler func (context.Context , * SubscribeRequest ) error
9192 // Function called when a client session unsubscribes from a resource.
9293 UnsubscribeHandler func (context.Context , * UnsubscribeRequest ) error
94+ // ListResourcesHandler, if non-nil, serves resources/list dynamically.
95+ // See [ListResourcesHandler] for semantics.
96+ ListResourcesHandler ListResourcesHandler
9397
9498 // Capabilities optionally configures the server's default capabilities,
9599 // before any capabilities are inferred from other configuration or server
@@ -621,7 +625,7 @@ func (s *Server) capabilities() *ServerCapabilities {
621625 }
622626
623627 // Augment with resources capability if resources/templates exist or legacy HasResources is set.
624- if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
628+ if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 || s . opts . ListResourcesHandler != nil {
625629 if caps .Resources == nil {
626630 caps .Resources = & ResourceCapabilities {ListChanged : true }
627631 }
@@ -854,18 +858,165 @@ func (s *Server) callTool(ctx context.Context, req *CallToolRequest) (*CallToolR
854858 return res , err
855859}
856860
857- func (s * Server ) listResources (_ context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
858- s .mu .Lock ()
859- defer s .mu .Unlock ()
861+ func (s * Server ) listResources (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
860862 if req .Params == nil {
861863 req .Params = & ListResourcesParams {}
862864 }
863- return paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, func (res * ListResourcesResult , resources []* serverResource ) {
864- res .Resources = []* Resource {} // avoid JSON null
865- for _ , r := range resources {
866- res .Resources = append (res .Resources , r .resource )
865+ if s .opts .ListResourcesHandler == nil {
866+ s .mu .Lock ()
867+ defer s .mu .Unlock ()
868+ return paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, populateListResourcesResult )
869+ }
870+ return s .listResourcesWithHandler (ctx , req )
871+ }
872+
873+ func populateListResourcesResult (res * ListResourcesResult , resources []* serverResource ) {
874+ res .Resources = []* Resource {} // avoid JSON null
875+ for _ , r := range resources {
876+ res .Resources = append (res .Resources , r .resource )
877+ }
878+ }
879+
880+ const (
881+ listResourcesPhaseStatic = "static"
882+ listResourcesPhaseHandler = "handler"
883+ listResourcesCursorPrefix = "lr1:"
884+ )
885+
886+ type listResourcesCursor struct {
887+ Phase string
888+ StaticCursor string
889+ HandlerCursor string
890+ }
891+
892+ func encodeListResourcesCursor (c listResourcesCursor ) (string , error ) {
893+ var buf bytes.Buffer
894+ if err := gob .NewEncoder (& buf ).Encode (c ); err != nil {
895+ return "" , fmt .Errorf ("failed to encode list resources cursor: %w" , err )
896+ }
897+ return listResourcesCursorPrefix + base64 .URLEncoding .EncodeToString (buf .Bytes ()), nil
898+ }
899+
900+ func decodeListResourcesCursor (cursor string ) (* listResourcesCursor , error ) {
901+ if cursor == "" {
902+ return & listResourcesCursor {Phase : listResourcesPhaseStatic }, nil
903+ }
904+ if ! strings .HasPrefix (cursor , listResourcesCursorPrefix ) {
905+ return nil , fmt .Errorf ("not a list resources cursor" )
906+ }
907+ decoded , err := base64 .URLEncoding .DecodeString (cursor [len (listResourcesCursorPrefix ):])
908+ if err != nil {
909+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
910+ }
911+ var c listResourcesCursor
912+ if err := gob .NewDecoder (bytes .NewReader (decoded )).Decode (& c ); err != nil {
913+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
914+ }
915+ return & c , nil
916+ }
917+
918+ func normalizeListResourcesResult (res * ListResourcesResult ) * ListResourcesResult {
919+ if res == nil {
920+ return & ListResourcesResult {Resources : []* Resource {}}
921+ }
922+ if res .Resources == nil {
923+ res2 := * res
924+ res2 .Resources = []* Resource {}
925+ return & res2
926+ }
927+ return res
928+ }
929+
930+ func (s * Server ) listResourcesWithHandler (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
931+ handler := s .opts .ListResourcesHandler
932+
933+ s .mu .Lock ()
934+ hasStatic := s .resources .len () > 0
935+ pageSize := s .opts .PageSize
936+ s .mu .Unlock ()
937+
938+ if ! hasStatic {
939+ res , err := handler (ctx , req )
940+ if err != nil {
941+ return nil , err
867942 }
868- })
943+ return normalizeListResourcesResult (res ), nil
944+ }
945+
946+ phase , err := decodeListResourcesCursor (req .Params .Cursor )
947+ if err != nil {
948+ return nil , jsonrpc2 .ErrInvalidParams
949+ }
950+
951+ if phase .Phase == listResourcesPhaseHandler {
952+ handlerReq := & ListResourcesRequest {
953+ Session : req .Session ,
954+ Params : & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .HandlerCursor },
955+ }
956+ res , err := handler (ctx , handlerReq )
957+ if err != nil {
958+ return nil , err
959+ }
960+ res = normalizeListResourcesResult (res )
961+ if res .NextCursor != "" {
962+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
963+ Phase : listResourcesPhaseHandler ,
964+ HandlerCursor : res .NextCursor ,
965+ })
966+ if err != nil {
967+ return nil , err
968+ }
969+ }
970+ return res , nil
971+ }
972+
973+ s .mu .Lock ()
974+ staticParams := & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .StaticCursor }
975+ res , err := paginateList (s .resources , pageSize , staticParams , & ListResourcesResult {}, populateListResourcesResult )
976+ s .mu .Unlock ()
977+ if err != nil {
978+ return nil , err
979+ }
980+
981+ if res .NextCursor != "" {
982+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
983+ Phase : listResourcesPhaseStatic ,
984+ StaticCursor : res .NextCursor ,
985+ })
986+ if err != nil {
987+ return nil , err
988+ }
989+ return res , nil
990+ }
991+
992+ if len (res .Resources ) > 0 {
993+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {Phase : listResourcesPhaseHandler })
994+ if err != nil {
995+ return nil , err
996+ }
997+ return res , nil
998+ }
999+
1000+ handlerReq := & ListResourcesRequest {
1001+ Session : req .Session ,
1002+ Params : & ListResourcesParams {Meta : req .Params .Meta },
1003+ }
1004+ hRes , err := handler (ctx , handlerReq )
1005+ if err != nil {
1006+ return nil , err
1007+ }
1008+ hRes = normalizeListResourcesResult (hRes )
1009+
1010+ if hRes .NextCursor != "" {
1011+ hRes .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1012+ Phase : listResourcesPhaseHandler ,
1013+ HandlerCursor : hRes .NextCursor ,
1014+ })
1015+ if err != nil {
1016+ return nil , err
1017+ }
1018+ }
1019+ return hRes , nil
8691020}
8701021
8711022func (s * Server ) listResourceTemplates (_ context.Context , req * ListResourceTemplatesRequest ) (* ListResourceTemplatesResult , error ) {
0 commit comments