@@ -185,6 +185,12 @@ public class McpAsyncClient {
185185
186186 private final boolean applyElicitationDefaults ;
187187
188+ /**
189+ * Client cache for caching list and resource results based on server ttlMs hints
190+ * (SEP-2549).
191+ */
192+ private final McpClientCache clientCache ;
193+
188194 /**
189195 * Create a new McpAsyncClient with the given transport and session request-response
190196 * timeout.
@@ -208,6 +214,7 @@ public class McpAsyncClient {
208214 this .roots = new ConcurrentHashMap <>(features .roots ());
209215 this .jsonSchemaValidator = jsonSchemaValidator ;
210216 this .toolsOutputSchemaCache = new ConcurrentHashMap <>();
217+ this .clientCache = new McpClientCache ();
211218 this .enableCallToolSchemaCaching = features .enableCallToolSchemaCaching ();
212219 this .applyElicitationDefaults = features .applyElicitationDefaults ();
213220
@@ -746,7 +753,7 @@ public Mono<McpSchema.ListToolsResult> listTools() {
746753 * @return A Mono that emits the list of tools result
747754 */
748755 public Mono <McpSchema .ListToolsResult > listTools (String cursor ) {
749- return this .initializer . withInitialization ( "listing tools" , init -> this . listToolsInternal ( init , cursor , null ) );
756+ return this .listTools ( cursor , null );
750757 }
751758
752759 /**
@@ -756,6 +763,10 @@ public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
756763 * @return A Mono that emits the list of tools result
757764 */
758765 public Mono <McpSchema .ListToolsResult > listTools (String cursor , Map <String , Object > meta ) {
766+ McpSchema .ListToolsResult cached = this .clientCache .get (new McpClientCache .ListToolsCacheKey (cursor , meta ));
767+ if (cached != null ) {
768+ return Mono .just (cached );
769+ }
759770 return this .initializer .withInitialization ("listing tools" , init -> this .listToolsInternal (init , cursor , meta ));
760771 }
761772
@@ -773,6 +784,9 @@ private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, S
773784 if (result .tools () != null ) {
774785 result .tools ().forEach (tool -> ToolNameValidator .validate (tool .name (), false ));
775786 }
787+ if (result .ttlMs () != null && result .ttlMs () > 0 ) {
788+ this .clientCache .put (new McpClientCache .ListToolsCacheKey (cursor , meta ), result , result .ttlMs ());
789+ }
776790 if (this .enableCallToolSchemaCaching && result .tools () != null ) {
777791 // Cache tools output schema
778792 result .tools ()
@@ -786,14 +800,18 @@ private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, S
786800 private NotificationHandler asyncToolsChangeNotificationHandler (
787801 List <Function <List <McpSchema .Tool >, Mono <Void >>> toolsChangeConsumers ) {
788802 // TODO: params are not used yet
789- return params -> this .listTools ()
790- .flatMap (listToolsResult -> Flux .fromIterable (toolsChangeConsumers )
791- .flatMap (consumer -> consumer .apply (listToolsResult .tools ()))
792- .onErrorResume (error -> {
793- logger .error ("Error handling tools list change notification" , error );
794- return Mono .empty ();
795- })
796- .then ());
803+ return params -> {
804+ this .clientCache .clearTools ();
805+ this .toolsOutputSchemaCache .clear ();
806+ return this .listTools ()
807+ .flatMap (listToolsResult -> Flux .fromIterable (toolsChangeConsumers )
808+ .flatMap (consumer -> consumer .apply (listToolsResult .tools ()))
809+ .onErrorResume (error -> {
810+ logger .error ("Error handling tools list change notification" , error );
811+ return Mono .empty ();
812+ })
813+ .then ());
814+ };
797815 }
798816
799817 // --------------------------
@@ -837,7 +855,7 @@ public Mono<McpSchema.ListResourcesResult> listResources() {
837855 * @see #readResource(McpSchema.Resource)
838856 */
839857 public Mono <McpSchema .ListResourcesResult > listResources (String cursor ) {
840- return this .listResourcesInternal (cursor , null );
858+ return this .listResources (cursor , null );
841859 }
842860
843861 /**
@@ -851,6 +869,11 @@ public Mono<McpSchema.ListResourcesResult> listResources(String cursor) {
851869 * @see #readResource(McpSchema.Resource)
852870 */
853871 public Mono <McpSchema .ListResourcesResult > listResources (String cursor , Map <String , Object > meta ) {
872+ McpSchema .ListResourcesResult cached = this .clientCache
873+ .get (new McpClientCache .ListResourcesCacheKey (cursor , meta ));
874+ if (cached != null ) {
875+ return Mono .just (cached );
876+ }
854877 return this .listResourcesInternal (cursor , meta );
855878 }
856879
@@ -861,7 +884,13 @@ private Mono<McpSchema.ListResourcesResult> listResourcesInternal(String cursor,
861884 }
862885 return init .mcpSession ()
863886 .sendRequest (McpSchema .METHOD_RESOURCES_LIST , new McpSchema .PaginatedRequest (cursor , meta ),
864- LIST_RESOURCES_RESULT_TYPE_REF );
887+ LIST_RESOURCES_RESULT_TYPE_REF )
888+ .doOnNext (result -> {
889+ if (result .ttlMs () != null && result .ttlMs () > 0 ) {
890+ this .clientCache .put (new McpClientCache .ListResourcesCacheKey (cursor , meta ), result ,
891+ result .ttlMs ());
892+ }
893+ });
865894 });
866895 }
867896
@@ -887,12 +916,23 @@ public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.Resource resour
887916 * @see McpSchema.ReadResourceResult
888917 */
889918 public Mono <McpSchema .ReadResourceResult > readResource (McpSchema .ReadResourceRequest readResourceRequest ) {
919+ McpSchema .ReadResourceResult cached = this .clientCache
920+ .get (new McpClientCache .ReadResourceCacheKey (readResourceRequest .uri ()));
921+ if (cached != null ) {
922+ return Mono .just (cached );
923+ }
890924 return this .initializer .withInitialization ("reading resources" , init -> {
891925 if (init .initializeResult ().capabilities ().resources () == null ) {
892926 return Mono .error (new IllegalStateException ("Server does not provide the resources capability" ));
893927 }
894928 return init .mcpSession ()
895- .sendRequest (McpSchema .METHOD_RESOURCES_READ , readResourceRequest , READ_RESOURCE_RESULT_TYPE_REF );
929+ .sendRequest (McpSchema .METHOD_RESOURCES_READ , readResourceRequest , READ_RESOURCE_RESULT_TYPE_REF )
930+ .doOnNext (result -> {
931+ if (result .ttlMs () != null && result .ttlMs () > 0 ) {
932+ this .clientCache .put (new McpClientCache .ReadResourceCacheKey (readResourceRequest .uri ()), result ,
933+ result .ttlMs ());
934+ }
935+ });
896936 });
897937 }
898938
@@ -922,7 +962,7 @@ public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates() {
922962 * @see McpSchema.ListResourceTemplatesResult
923963 */
924964 public Mono <McpSchema .ListResourceTemplatesResult > listResourceTemplates (String cursor ) {
925- return this .listResourceTemplatesInternal (cursor , null );
965+ return this .listResourceTemplates (cursor , null );
926966 }
927967
928968 /**
@@ -935,6 +975,11 @@ public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates(String
935975 * @see McpSchema.ListResourceTemplatesResult
936976 */
937977 public Mono <McpSchema .ListResourceTemplatesResult > listResourceTemplates (String cursor , Map <String , Object > meta ) {
978+ McpSchema .ListResourceTemplatesResult cached = this .clientCache
979+ .get (new McpClientCache .ListResourceTemplatesCacheKey (cursor , meta ));
980+ if (cached != null ) {
981+ return Mono .just (cached );
982+ }
938983 return this .listResourceTemplatesInternal (cursor , meta );
939984 }
940985
@@ -946,7 +991,13 @@ private Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplatesInterna
946991 }
947992 return init .mcpSession ()
948993 .sendRequest (McpSchema .METHOD_RESOURCES_TEMPLATES_LIST , new McpSchema .PaginatedRequest (cursor , meta ),
949- LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF );
994+ LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF )
995+ .doOnNext (result -> {
996+ if (result .ttlMs () != null && result .ttlMs () > 0 ) {
997+ this .clientCache .put (new McpClientCache .ListResourceTemplatesCacheKey (cursor , meta ), result ,
998+ result .ttlMs ());
999+ }
1000+ });
9501001 });
9511002 }
9521003
@@ -980,13 +1031,16 @@ public Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRe
9801031
9811032 private NotificationHandler asyncResourcesChangeNotificationHandler (
9821033 List <Function <List <McpSchema .Resource >, Mono <Void >>> resourcesChangeConsumers ) {
983- return params -> listResources ().flatMap (listResourcesResult -> Flux .fromIterable (resourcesChangeConsumers )
984- .flatMap (consumer -> consumer .apply (listResourcesResult .resources ()))
985- .onErrorResume (error -> {
986- logger .error ("Error handling resources list change notification" , error );
987- return Mono .empty ();
988- })
989- .then ());
1034+ return params -> {
1035+ this .clientCache .clearResources ();
1036+ return listResources ().flatMap (listResourcesResult -> Flux .fromIterable (resourcesChangeConsumers )
1037+ .flatMap (consumer -> consumer .apply (listResourcesResult .resources ()))
1038+ .onErrorResume (error -> {
1039+ logger .error ("Error handling resources list change notification" , error );
1040+ return Mono .empty ();
1041+ })
1042+ .then ());
1043+ };
9901044 }
9911045
9921046 private NotificationHandler asyncResourcesUpdatedNotificationHandler (
@@ -996,6 +1050,8 @@ private NotificationHandler asyncResourcesUpdatedNotificationHandler(
9961050 new TypeRef <>() {
9971051 });
9981052
1053+ this .clientCache .clearResource (resourcesUpdatedNotification .uri ());
1054+
9991055 return readResource (McpSchema .ReadResourceRequest .builder (resourcesUpdatedNotification .uri ()).build ())
10001056 .flatMap (readResourceResult -> Flux .fromIterable (resourcesUpdateConsumers )
10011057 .flatMap (consumer -> consumer .apply (readResourceResult .contents ()))
@@ -1040,7 +1096,7 @@ public Mono<ListPromptsResult> listPrompts() {
10401096 * @see #getPrompt(GetPromptRequest)
10411097 */
10421098 public Mono <ListPromptsResult > listPrompts (String cursor ) {
1043- return this .listPromptsInternal (cursor , null );
1099+ return this .listPrompts (cursor , null );
10441100 }
10451101
10461102 /**
@@ -1052,14 +1108,24 @@ public Mono<ListPromptsResult> listPrompts(String cursor) {
10521108 * @see #getPrompt(GetPromptRequest)
10531109 */
10541110 public Mono <ListPromptsResult > listPrompts (String cursor , Map <String , Object > meta ) {
1111+ McpSchema .ListPromptsResult cached = this .clientCache .get (new McpClientCache .ListPromptsCacheKey (cursor , meta ));
1112+ if (cached != null ) {
1113+ return Mono .just (cached );
1114+ }
10551115 return this .listPromptsInternal (cursor , meta );
10561116 }
10571117
10581118 private Mono <ListPromptsResult > listPromptsInternal (String cursor , Map <String , Object > meta ) {
10591119 return this .initializer .withInitialization ("listing prompts" ,
10601120 init -> init .mcpSession ()
10611121 .sendRequest (McpSchema .METHOD_PROMPT_LIST , new PaginatedRequest (cursor , meta ),
1062- LIST_PROMPTS_RESULT_TYPE_REF ));
1122+ LIST_PROMPTS_RESULT_TYPE_REF )
1123+ .doOnNext (result -> {
1124+ if (result .ttlMs () != null && result .ttlMs () > 0 ) {
1125+ this .clientCache .put (new McpClientCache .ListPromptsCacheKey (cursor , meta ), result ,
1126+ result .ttlMs ());
1127+ }
1128+ }));
10631129 }
10641130
10651131 /**
@@ -1078,13 +1144,16 @@ public Mono<GetPromptResult> getPrompt(GetPromptRequest getPromptRequest) {
10781144
10791145 private NotificationHandler asyncPromptsChangeNotificationHandler (
10801146 List <Function <List <McpSchema .Prompt >, Mono <Void >>> promptsChangeConsumers ) {
1081- return params -> listPrompts ().flatMap (listPromptsResult -> Flux .fromIterable (promptsChangeConsumers )
1082- .flatMap (consumer -> consumer .apply (listPromptsResult .prompts ()))
1083- .onErrorResume (error -> {
1084- logger .error ("Error handling prompts list change notification" , error );
1085- return Mono .empty ();
1086- })
1087- .then ());
1147+ return params -> {
1148+ this .clientCache .clearPrompts ();
1149+ return listPrompts ().flatMap (listPromptsResult -> Flux .fromIterable (promptsChangeConsumers )
1150+ .flatMap (consumer -> consumer .apply (listPromptsResult .prompts ()))
1151+ .onErrorResume (error -> {
1152+ logger .error ("Error handling prompts list change notification" , error );
1153+ return Mono .empty ();
1154+ })
1155+ .then ());
1156+ };
10881157 }
10891158
10901159 // --------------------------
@@ -1146,6 +1215,10 @@ void setProtocolVersions(List<String> protocolVersions) {
11461215 this .initializer .setProtocolVersions (protocolVersions );
11471216 }
11481217
1218+ McpClientCache getClientCache () {
1219+ return this .clientCache ;
1220+ }
1221+
11491222 // --------------------------
11501223 // Completions
11511224 // --------------------------
0 commit comments