@@ -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
@@ -617,7 +621,7 @@ func (s *Server) capabilities() *ServerCapabilities {
617621 }
618622
619623 // Augment with resources capability if resources/templates exist or legacy HasResources is set.
620- if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 {
624+ if s .opts .HasResources || s .resources .len () > 0 || s .resourceTemplates .len () > 0 || s . opts . ListResourcesHandler != nil {
621625 if caps .Resources == nil {
622626 caps .Resources = & ResourceCapabilities {ListChanged : true }
623627 }
@@ -850,18 +854,165 @@ func (s *Server) callTool(ctx context.Context, req *CallToolRequest) (*CallToolR
850854 return res , err
851855}
852856
853- func (s * Server ) listResources (_ context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
854- s .mu .Lock ()
855- defer s .mu .Unlock ()
857+ func (s * Server ) listResources (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
856858 if req .Params == nil {
857859 req .Params = & ListResourcesParams {}
858860 }
859- return paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, func (res * ListResourcesResult , resources []* serverResource ) {
860- res .Resources = []* Resource {} // avoid JSON null
861- for _ , r := range resources {
862- res .Resources = append (res .Resources , r .resource )
861+ if s .opts .ListResourcesHandler == nil {
862+ s .mu .Lock ()
863+ defer s .mu .Unlock ()
864+ return paginateList (s .resources , s .opts .PageSize , req .Params , & ListResourcesResult {}, populateListResourcesResult )
865+ }
866+ return s .listResourcesWithHandler (ctx , req )
867+ }
868+
869+ func populateListResourcesResult (res * ListResourcesResult , resources []* serverResource ) {
870+ res .Resources = []* Resource {} // avoid JSON null
871+ for _ , r := range resources {
872+ res .Resources = append (res .Resources , r .resource )
873+ }
874+ }
875+
876+ const (
877+ listResourcesPhaseStatic = "static"
878+ listResourcesPhaseHandler = "handler"
879+ listResourcesCursorPrefix = "lr1:"
880+ )
881+
882+ type listResourcesCursor struct {
883+ Phase string
884+ StaticCursor string
885+ HandlerCursor string
886+ }
887+
888+ func encodeListResourcesCursor (c listResourcesCursor ) (string , error ) {
889+ var buf bytes.Buffer
890+ if err := gob .NewEncoder (& buf ).Encode (c ); err != nil {
891+ return "" , fmt .Errorf ("failed to encode list resources cursor: %w" , err )
892+ }
893+ return listResourcesCursorPrefix + base64 .URLEncoding .EncodeToString (buf .Bytes ()), nil
894+ }
895+
896+ func decodeListResourcesCursor (cursor string ) (* listResourcesCursor , error ) {
897+ if cursor == "" {
898+ return & listResourcesCursor {Phase : listResourcesPhaseStatic }, nil
899+ }
900+ if ! strings .HasPrefix (cursor , listResourcesCursorPrefix ) {
901+ return nil , fmt .Errorf ("not a list resources cursor" )
902+ }
903+ decoded , err := base64 .URLEncoding .DecodeString (cursor [len (listResourcesCursorPrefix ):])
904+ if err != nil {
905+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
906+ }
907+ var c listResourcesCursor
908+ if err := gob .NewDecoder (bytes .NewReader (decoded )).Decode (& c ); err != nil {
909+ return nil , fmt .Errorf ("failed to decode list resources cursor: %w" , err )
910+ }
911+ return & c , nil
912+ }
913+
914+ func normalizeListResourcesResult (res * ListResourcesResult ) * ListResourcesResult {
915+ if res == nil {
916+ return & ListResourcesResult {Resources : []* Resource {}}
917+ }
918+ if res .Resources == nil {
919+ res2 := * res
920+ res2 .Resources = []* Resource {}
921+ return & res2
922+ }
923+ return res
924+ }
925+
926+ func (s * Server ) listResourcesWithHandler (ctx context.Context , req * ListResourcesRequest ) (* ListResourcesResult , error ) {
927+ handler := s .opts .ListResourcesHandler
928+
929+ s .mu .Lock ()
930+ hasStatic := s .resources .len () > 0
931+ pageSize := s .opts .PageSize
932+ s .mu .Unlock ()
933+
934+ if ! hasStatic {
935+ res , err := handler (ctx , req )
936+ if err != nil {
937+ return nil , err
863938 }
864- })
939+ return normalizeListResourcesResult (res ), nil
940+ }
941+
942+ phase , err := decodeListResourcesCursor (req .Params .Cursor )
943+ if err != nil {
944+ return nil , jsonrpc2 .ErrInvalidParams
945+ }
946+
947+ if phase .Phase == listResourcesPhaseHandler {
948+ handlerReq := & ListResourcesRequest {
949+ Session : req .Session ,
950+ Params : & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .HandlerCursor },
951+ }
952+ res , err := handler (ctx , handlerReq )
953+ if err != nil {
954+ return nil , err
955+ }
956+ res = normalizeListResourcesResult (res )
957+ if res .NextCursor != "" {
958+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
959+ Phase : listResourcesPhaseHandler ,
960+ HandlerCursor : res .NextCursor ,
961+ })
962+ if err != nil {
963+ return nil , err
964+ }
965+ }
966+ return res , nil
967+ }
968+
969+ s .mu .Lock ()
970+ staticParams := & ListResourcesParams {Meta : req .Params .Meta , Cursor : phase .StaticCursor }
971+ res , err := paginateList (s .resources , pageSize , staticParams , & ListResourcesResult {}, populateListResourcesResult )
972+ s .mu .Unlock ()
973+ if err != nil {
974+ return nil , err
975+ }
976+
977+ if res .NextCursor != "" {
978+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
979+ Phase : listResourcesPhaseStatic ,
980+ StaticCursor : res .NextCursor ,
981+ })
982+ if err != nil {
983+ return nil , err
984+ }
985+ return res , nil
986+ }
987+
988+ if len (res .Resources ) > 0 {
989+ res .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {Phase : listResourcesPhaseHandler })
990+ if err != nil {
991+ return nil , err
992+ }
993+ return res , nil
994+ }
995+
996+ handlerReq := & ListResourcesRequest {
997+ Session : req .Session ,
998+ Params : & ListResourcesParams {Meta : req .Params .Meta },
999+ }
1000+ hRes , err := handler (ctx , handlerReq )
1001+ if err != nil {
1002+ return nil , err
1003+ }
1004+ hRes = normalizeListResourcesResult (hRes )
1005+
1006+ if hRes .NextCursor != "" {
1007+ hRes .NextCursor , err = encodeListResourcesCursor (listResourcesCursor {
1008+ Phase : listResourcesPhaseHandler ,
1009+ HandlerCursor : hRes .NextCursor ,
1010+ })
1011+ if err != nil {
1012+ return nil , err
1013+ }
1014+ }
1015+ return hRes , nil
8651016}
8661017
8671018func (s * Server ) listResourceTemplates (_ context.Context , req * ListResourceTemplatesRequest ) (* ListResourceTemplatesResult , error ) {
0 commit comments