55package io .modelcontextprotocol .client ;
66
77import java .time .Duration ;
8+ import java .util .List ;
89import java .util .Map ;
910import java .util .concurrent .atomic .AtomicBoolean ;
1011import java .util .concurrent .atomic .AtomicReference ;
1112import java .util .function .Consumer ;
1213import java .util .function .Function ;
1314
1415import io .modelcontextprotocol .spec .McpClientTransport ;
15- import io .modelcontextprotocol .spec .McpError ;
1616import io .modelcontextprotocol .spec .McpSchema ;
1717import io .modelcontextprotocol .spec .McpSchema .CallToolRequest ;
1818import io .modelcontextprotocol .spec .McpSchema .CallToolResult ;
@@ -112,33 +112,18 @@ void tearDown() {
112112
113113 static final Object DUMMY_RETURN_VALUE = new Object ();
114114
115- <T > void verifyNotificationTimesOut (Consumer <McpSyncClient > operation , String action ) {
116- verifyCallTimesOut (client -> {
115+ <T > void verifyNotificationSucceedsWithImplicitInitialization (Consumer <McpSyncClient > operation , String action ) {
116+ verifyCallSucceedsWithImplicitInitialization (client -> {
117117 operation .accept (client );
118118 return DUMMY_RETURN_VALUE ;
119119 }, action );
120120 }
121121
122- <T > void verifyCallTimesOut (Function <McpSyncClient , T > blockingOperation , String action ) {
122+ <T > void verifyCallSucceedsWithImplicitInitialization (Function <McpSyncClient , T > blockingOperation , String action ) {
123123 withClient (createMcpTransport (), mcpSyncClient -> {
124- // This scheduler is not replaced by virtual time scheduler
125- Scheduler customScheduler = Schedulers .newBoundedElastic (1 , 1 , "actualBoundedElastic" );
126-
127- StepVerifier .withVirtualTime (() -> Mono .fromSupplier (() -> blockingOperation .apply (mcpSyncClient ))
128- // Offload the blocking call to the real scheduler
129- .subscribeOn (customScheduler ))
130- .expectSubscription ()
131- // This works without actually waiting but executes all the
132- // tasks pending execution on the VirtualTimeScheduler.
133- // It is possible to execute the blocking code from the operation
134- // because it is blocked on a dedicated Scheduler and the main
135- // flow is not blocked and uses the VirtualTimeScheduler.
136- .thenAwait (getInitializationTimeout ())
137- .consumeErrorWith (e -> assertThat (e ).isInstanceOf (McpError .class )
138- .hasMessage ("Client must be initialized before " + action ))
139- .verify ();
140-
141- customScheduler .dispose ();
124+ StepVerifier .create (Mono .fromSupplier (() -> blockingOperation .apply (mcpSyncClient )))
125+ .expectNextCount (1 )
126+ .verifyComplete ();
142127 });
143128 }
144129
@@ -154,7 +139,7 @@ void testConstructorWithInvalidArguments() {
154139
155140 @ Test
156141 void testListToolsWithoutInitialization () {
157- verifyCallTimesOut (client -> client .listTools (null ), "listing tools" );
142+ verifyCallSucceedsWithImplicitInitialization (client -> client .listTools (null ), "listing tools" );
158143 }
159144
160145 @ Test
@@ -175,8 +160,8 @@ void testListTools() {
175160
176161 @ Test
177162 void testCallToolsWithoutInitialization () {
178- verifyCallTimesOut ( client -> client . callTool ( new CallToolRequest ( "add" , Map . of ( "a" , 3 , "b" , 4 ))),
179- "calling tools" );
163+ verifyCallSucceedsWithImplicitInitialization (
164+ client -> client . callTool ( new CallToolRequest ( "add" , Map . of ( "a" , 3 , "b" , 4 ))), "calling tools" );
180165 }
181166
182167 @ Test
@@ -200,7 +185,7 @@ void testCallTools() {
200185
201186 @ Test
202187 void testPingWithoutInitialization () {
203- verifyCallTimesOut (client -> client .ping (), "pinging the server" );
188+ verifyCallSucceedsWithImplicitInitialization (client -> client .ping (), "pinging the server" );
204189 }
205190
206191 @ Test
@@ -214,7 +199,7 @@ void testPing() {
214199 @ Test
215200 void testCallToolWithoutInitialization () {
216201 CallToolRequest callToolRequest = new CallToolRequest ("echo" , Map .of ("message" , TEST_MESSAGE ));
217- verifyCallTimesOut (client -> client .callTool (callToolRequest ), "calling tools" );
202+ verifyCallSucceedsWithImplicitInitialization (client -> client .callTool (callToolRequest ), "calling tools" );
218203 }
219204
220205 @ Test
@@ -243,7 +228,7 @@ void testCallToolWithInvalidTool() {
243228
244229 @ Test
245230 void testRootsListChangedWithoutInitialization () {
246- verifyNotificationTimesOut (client -> client .rootsListChangedNotification (),
231+ verifyNotificationSucceedsWithImplicitInitialization (client -> client .rootsListChangedNotification (),
247232 "sending roots list changed notification" );
248233 }
249234
@@ -257,7 +242,7 @@ void testRootsListChanged() {
257242
258243 @ Test
259244 void testListResourcesWithoutInitialization () {
260- verifyCallTimesOut (client -> client .listResources (null ), "listing resources" );
245+ verifyCallSucceedsWithImplicitInitialization (client -> client .listResources (null ), "listing resources" );
261246 }
262247
263248 @ Test
@@ -333,8 +318,14 @@ void testRemoveNonExistentRoot() {
333318
334319 @ Test
335320 void testReadResourceWithoutInitialization () {
336- Resource resource = new Resource ("test://uri" , "Test Resource" , null , null , null );
337- verifyCallTimesOut (client -> client .readResource (resource ), "reading resources" );
321+ AtomicReference <List <Resource >> resources = new AtomicReference <>();
322+ withClient (createMcpTransport (), mcpSyncClient -> {
323+ mcpSyncClient .initialize ();
324+ resources .set (mcpSyncClient .listResources ().resources ());
325+ });
326+
327+ verifyCallSucceedsWithImplicitInitialization (client -> client .readResource (resources .get ().get (0 )),
328+ "reading resources" );
338329 }
339330
340331 @ Test
@@ -355,7 +346,8 @@ void testReadResource() {
355346
356347 @ Test
357348 void testListResourceTemplatesWithoutInitialization () {
358- verifyCallTimesOut (client -> client .listResourceTemplates (null ), "listing resource templates" );
349+ verifyCallSucceedsWithImplicitInitialization (client -> client .listResourceTemplates (null ),
350+ "listing resource templates" );
359351 }
360352
361353 @ Test
@@ -413,8 +405,8 @@ void testNotificationHandlers() {
413405
414406 @ Test
415407 void testLoggingLevelsWithoutInitialization () {
416- verifyNotificationTimesOut ( client -> client . setLoggingLevel ( McpSchema . LoggingLevel . DEBUG ),
417- "setting logging level" );
408+ verifyNotificationSucceedsWithImplicitInitialization (
409+ client -> client . setLoggingLevel ( McpSchema . LoggingLevel . DEBUG ), "setting logging level" );
418410 }
419411
420412 @ Test
0 commit comments