44
55package io .modelcontextprotocol .server ;
66
7- import static io .modelcontextprotocol .util .ToolsUtils .EMPTY_JSON_SCHEMA ;
8-
97import java .time .Duration ;
108import java .util .List ;
119import java .util .Map ;
12- import java .util .concurrent .ConcurrentHashMap ;
1310import java .util .concurrent .atomic .AtomicReference ;
1411import java .util .function .BiFunction ;
1512
1916import io .modelcontextprotocol .server .transport .HttpServletStatelessServerTransport ;
2017import io .modelcontextprotocol .server .transport .TomcatTestUtil ;
2118import io .modelcontextprotocol .spec .HttpHeaders ;
19+ import io .modelcontextprotocol .spec .McpError ;
2220import io .modelcontextprotocol .spec .McpSchema ;
2321import io .modelcontextprotocol .spec .McpSchema .CallToolResult ;
2422import io .modelcontextprotocol .spec .McpSchema .CompleteRequest ;
4341import org .junit .jupiter .api .BeforeEach ;
4442import org .junit .jupiter .api .Test ;
4543import org .junit .jupiter .api .Timeout ;
46- import org .junit .jupiter .params .ParameterizedTest ;
47- import org .junit .jupiter .params .provider .ValueSource ;
4844
4945import org .springframework .mock .web .MockHttpServletRequest ;
5046import org .springframework .mock .web .MockHttpServletResponse ;
5349import static io .modelcontextprotocol .server .transport .HttpServletStatelessServerTransport .APPLICATION_JSON ;
5450import static io .modelcontextprotocol .server .transport .HttpServletStatelessServerTransport .TEXT_EVENT_STREAM ;
5551import static io .modelcontextprotocol .util .McpJsonMapperUtils .JSON_MAPPER ;
52+ import static io .modelcontextprotocol .util .ToolsUtils .EMPTY_JSON_SCHEMA ;
5653import static net .javacrumbs .jsonunit .assertj .JsonAssertions .assertThatJson ;
5754import static net .javacrumbs .jsonunit .assertj .JsonAssertions .json ;
5855import static org .assertj .core .api .Assertions .assertThat ;
56+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
57+ import static org .assertj .core .api .InstanceOfAssertFactories .type ;
5958import static org .awaitility .Awaitility .await ;
6059
6160@ Timeout (15 )
@@ -67,7 +66,12 @@ class HttpServletStatelessIntegrationTests {
6766
6867 private HttpServletStatelessServerTransport mcpStatelessServerTransport ;
6968
70- ConcurrentHashMap <String , McpClient .SyncSpec > clientBuilders = new ConcurrentHashMap <>();
69+ private final McpClient .SyncSpec clientBuilder = McpClient
70+ .sync (HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT )
71+ .endpoint (CUSTOM_MESSAGE_ENDPOINT )
72+ .build ())
73+ .initializationTimeout (Duration .ofHours (10 ))
74+ .requestTimeout (Duration .ofHours (10 ));
7175
7276 private Tomcat tomcat ;
7377
@@ -85,12 +89,6 @@ public void before() {
8589 catch (Exception e ) {
8690 throw new RuntimeException ("Failed to start Tomcat" , e );
8791 }
88-
89- clientBuilders
90- .put ("httpclient" ,
91- McpClient .sync (HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT )
92- .endpoint (CUSTOM_MESSAGE_ENDPOINT )
93- .build ()).initializationTimeout (Duration .ofHours (10 )).requestTimeout (Duration .ofHours (10 )));
9492 }
9593
9694 @ AfterEach
@@ -112,12 +110,8 @@ public void after() {
112110 // ---------------------------------------
113111 // Tools Tests
114112 // ---------------------------------------
115- @ ParameterizedTest (name = "{0} : {displayName} " )
116- @ ValueSource (strings = { "httpclient" })
117- void testToolCallSuccess (String clientType ) {
118-
119- var clientBuilder = clientBuilders .get (clientType );
120-
113+ @ Test
114+ void testToolCallSuccess () {
121115 var callResponse = CallToolResult .builder ()
122116 .content (List .of (McpSchema .TextContent .builder ("CALL RESPONSE" ).build ()))
123117 .isError (false )
@@ -158,12 +152,8 @@ void testToolCallSuccess(String clientType) {
158152 }
159153 }
160154
161- @ ParameterizedTest (name = "{0} : {displayName} " )
162- @ ValueSource (strings = { "httpclient" })
163- void testInitialize (String clientType ) {
164-
165- var clientBuilder = clientBuilders .get (clientType );
166-
155+ @ Test
156+ void testInitialize () {
167157 var mcpServer = McpServer .sync (mcpStatelessServerTransport ).build ();
168158
169159 try (var mcpClient = clientBuilder .build ()) {
@@ -178,11 +168,8 @@ void testInitialize(String clientType) {
178168 // ---------------------------------------
179169 // Completion Tests
180170 // ---------------------------------------
181- @ ParameterizedTest (name = "{0} : Completion call" )
182- @ ValueSource (strings = { "httpclient" })
183- void testCompletionShouldReturnExpectedSuggestions (String clientType ) {
184- var clientBuilder = clientBuilders .get (clientType );
185-
171+ @ Test
172+ void testCompletionShouldReturnExpectedSuggestions () {
186173 var expectedValues = List .of ("python" , "pytorch" , "pyside" );
187174 var completionResponse = new CompleteResult (new CompleteResult .CompleteCompletion (expectedValues , 10 , // total
188175 true // hasMore
@@ -233,11 +220,8 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
233220 }
234221 }
235222
236- @ ParameterizedTest (name = "{0} : Completion call without matching handler" )
237- @ ValueSource (strings = { "httpclient" })
238- void testCompletionWithoutMatchingHandlerReturnsEmptyResult (String clientType ) {
239- var clientBuilder = clientBuilders .get (clientType );
240-
223+ @ Test
224+ void testCompletionWithoutMatchingHandlerReturnsEmptyResult () {
241225 BiFunction <McpTransportContext , CompleteRequest , CompleteResult > completionHandler = (transportContext ,
242226 request ) -> new CompleteResult (new CompleteResult .CompleteCompletion (List .of ("java" ), 1 , false ));
243227
@@ -286,11 +270,8 @@ void testCompletionWithoutMatchingHandlerReturnsEmptyResult(String clientType) {
286270 }
287271 }
288272
289- @ ParameterizedTest (name = "{0} : Resource template completion call without matching handler" )
290- @ ValueSource (strings = { "httpclient" })
291- void testResourceTemplateCompletionWithoutMatchingHandlerReturnsEmptyResult (String clientType ) {
292- var clientBuilder = clientBuilders .get (clientType );
293-
273+ @ Test
274+ void testResourceTemplateCompletionWithoutMatchingHandlerReturnsEmptyResult () {
294275 BiFunction <McpTransportContext , CompleteRequest , CompleteResult > completionHandler = (transportContext ,
295276 request ) -> new CompleteResult (new CompleteResult .CompleteCompletion (List .of ("java" ), 1 , false ));
296277
@@ -337,14 +318,62 @@ void testResourceTemplateCompletionWithoutMatchingHandlerReturnsEmptyResult(Stri
337318 }
338319 }
339320
321+ @ Test
322+ void testCompletionForNonExistentPromptReturnsInvalidParams () {
323+ var mcpServer = McpServer .sync (mcpStatelessServerTransport )
324+ .capabilities (ServerCapabilities .builder ().completions ().build ())
325+ .build ();
326+
327+ try (var mcpClient = clientBuilder .build ()) {
328+ InitializeResult initResult = mcpClient .initialize ();
329+ assertThat (initResult ).isNotNull ();
330+
331+ CompleteRequest request = CompleteRequest
332+ .builder (new PromptReference ("nonexistent-prompt" ), new CompleteRequest .CompleteArgument ("arg" , "val" ))
333+ .build ();
334+
335+ assertThatThrownBy (() -> mcpClient .completeCompletion (request )).isInstanceOf (McpError .class )
336+ .asInstanceOf (type (McpError .class ))
337+ .extracting (McpError ::getJsonRpcError )
338+ .extracting (McpSchema .JSONRPCResponse .JSONRPCError ::code )
339+ .isEqualTo (ErrorCodes .INVALID_PARAMS );
340+ }
341+ finally {
342+ mcpServer .close ();
343+ }
344+ }
345+
346+ @ Test
347+ void testCompletionForNonExistentResourceReturnsResourceNotFound () {
348+ var mcpServer = McpServer .sync (mcpStatelessServerTransport )
349+ .capabilities (ServerCapabilities .builder ().completions ().build ())
350+ .build ();
351+
352+ try (var mcpClient = clientBuilder .build ()) {
353+ InitializeResult initResult = mcpClient .initialize ();
354+ assertThat (initResult ).isNotNull ();
355+
356+ CompleteRequest request = CompleteRequest
357+ .builder (new ResourceReference ("test://nonexistent/{param}" ),
358+ new CompleteRequest .CompleteArgument ("param" , "val" ))
359+ .build ();
360+
361+ assertThatThrownBy (() -> mcpClient .completeCompletion (request )).isInstanceOf (McpError .class )
362+ .asInstanceOf (type (McpError .class ))
363+ .extracting (McpError ::getJsonRpcError )
364+ .extracting (McpSchema .JSONRPCResponse .JSONRPCError ::code )
365+ .isEqualTo (McpSchema .ErrorCodes .RESOURCE_NOT_FOUND );
366+ }
367+ finally {
368+ mcpServer .close ();
369+ }
370+ }
371+
340372 // ---------------------------------------
341373 // Tool Structured Output Schema Tests
342374 // ---------------------------------------
343- @ ParameterizedTest (name = "{0} : {displayName} " )
344- @ ValueSource (strings = { "httpclient" })
345- void testStructuredOutputValidationSuccess (String clientType ) {
346- var clientBuilder = clientBuilders .get (clientType );
347-
375+ @ Test
376+ void testStructuredOutputValidationSuccess () {
348377 // Create a tool with output schema
349378 Map <String , Object > outputSchema = Map .of (
350379 "type" , "object" , "properties" , Map .of ("result" , Map .of ("type" , "number" ), "operation" ,
@@ -409,11 +438,8 @@ void testStructuredOutputValidationSuccess(String clientType) {
409438 }
410439 }
411440
412- @ ParameterizedTest (name = "{0} : {displayName} " )
413- @ ValueSource (strings = { "httpclient" })
414- void testStructuredOutputOfObjectArrayValidationSuccess (String clientType ) {
415- var clientBuilder = clientBuilders .get (clientType );
416-
441+ @ Test
442+ void testStructuredOutputOfObjectArrayValidationSuccess () {
417443 // Create a tool with output schema that returns an array of objects
418444 Map <String , Object > outputSchema = Map
419445 .of ( // @formatter:off
@@ -470,11 +496,8 @@ void testStructuredOutputOfObjectArrayValidationSuccess(String clientType) {
470496 }
471497 }
472498
473- @ ParameterizedTest (name = "{0} : {displayName} " )
474- @ ValueSource (strings = { "httpclient" })
475- void testStructuredOutputWithInHandlerError (String clientType ) {
476- var clientBuilder = clientBuilders .get (clientType );
477-
499+ @ Test
500+ void testStructuredOutputWithInHandlerError () {
478501 // Create a tool with output schema
479502 Map <String , Object > outputSchema = Map .of (
480503 "type" , "object" , "properties" , Map .of ("result" , Map .of ("type" , "number" ), "operation" ,
@@ -528,11 +551,8 @@ void testStructuredOutputWithInHandlerError(String clientType) {
528551 }
529552 }
530553
531- @ ParameterizedTest (name = "{0} : {displayName} " )
532- @ ValueSource (strings = { "httpclient" })
533- void testStructuredOutputValidationFailure (String clientType ) {
534- var clientBuilder = clientBuilders .get (clientType );
535-
554+ @ Test
555+ void testStructuredOutputValidationFailure () {
536556 // Create a tool with output schema
537557 Map <String , Object > outputSchema = Map .of ("type" , "object" , "properties" ,
538558 Map .of ("result" , Map .of ("type" , "number" ), "operation" , Map .of ("type" , "string" )), "required" ,
@@ -580,11 +600,8 @@ void testStructuredOutputValidationFailure(String clientType) {
580600 }
581601 }
582602
583- @ ParameterizedTest (name = "{0} : {displayName} " )
584- @ ValueSource (strings = { "httpclient" })
585- void testStructuredOutputMissingStructuredContent (String clientType ) {
586- var clientBuilder = clientBuilders .get (clientType );
587-
603+ @ Test
604+ void testStructuredOutputMissingStructuredContent () {
588605 // Create a tool with output schema
589606 Map <String , Object > outputSchema = Map .of ("type" , "object" , "properties" ,
590607 Map .of ("result" , Map .of ("type" , "number" )), "required" , List .of ("result" ));
@@ -629,11 +646,8 @@ void testStructuredOutputMissingStructuredContent(String clientType) {
629646 }
630647 }
631648
632- @ ParameterizedTest (name = "{0} : {displayName} " )
633- @ ValueSource (strings = { "httpclient" })
634- void testStructuredOutputRuntimeToolAddition (String clientType ) {
635- var clientBuilder = clientBuilders .get (clientType );
636-
649+ @ Test
650+ void testStructuredOutputRuntimeToolAddition () {
637651 // Start server without tools
638652 var mcpServer = McpServer .sync (mcpStatelessServerTransport )
639653 .serverInfo ("test-server" , "1.0.0" )
0 commit comments