1717import io .modelcontextprotocol .server .transport .HttpServletStreamableServerTransportProvider ;
1818import io .modelcontextprotocol .server .transport .McpTestRequestRecordingServletFilter ;
1919import io .modelcontextprotocol .server .transport .TomcatTestUtil ;
20+ import io .modelcontextprotocol .spec .McpError ;
2021import io .modelcontextprotocol .spec .McpSchema ;
2122import io .modelcontextprotocol .spec .ProtocolVersions ;
2223import org .apache .catalina .LifecycleException ;
2324import org .apache .catalina .LifecycleState ;
2425import org .apache .catalina .startup .Tomcat ;
25- import org .junit .jupiter .api .AfterEach ;
26+ import org .junit .jupiter .api .AfterAll ;
27+ import org .junit .jupiter .api .BeforeAll ;
28+ import org .junit .jupiter .api .BeforeEach ;
2629import org .junit .jupiter .api .Test ;
2730
2831import static org .assertj .core .api .Assertions .assertThat ;
32+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
33+ import static org .assertj .core .api .InstanceOfAssertFactories .type ;
2934
3035class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
3136
32- private Tomcat tomcat ;
37+ private static Tomcat tomcat ;
3338
3439 private static final int PORT = TomcatTestUtil .findAvailablePort ();
3540
36- private final McpTestRequestRecordingServletFilter requestRecordingFilter = new McpTestRequestRecordingServletFilter ();
41+ private static final McpTestRequestRecordingServletFilter requestRecordingFilter = new McpTestRequestRecordingServletFilter ();
3742
38- private final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider
43+ private static final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider
3944 .builder ()
4045 .contextExtractor (
4146 req -> McpTransportContext .create (Map .of ("protocol-version" , req .getHeader ("MCP-protocol-version" ))))
@@ -51,78 +56,13 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
5156 .isError (false )
5257 .build ();
5358
54- McpSyncServer mcpServer = McpServer .sync (transport )
59+ private final McpSyncServer mcpServer = McpServer .sync (transport )
5560 .capabilities (McpSchema .ServerCapabilities .builder ().tools (false ).build ())
5661 .tools (McpServerFeatures .SyncToolSpecification .builder ().tool (toolSpec ).callHandler (toolHandler ).build ())
5762 .build ();
5863
59- @ AfterEach
60- void tearDown () {
61- stopTomcat ();
62- }
63-
64- @ Test
65- void usesLatestVersion () {
66- startTomcat ();
67-
68- var client = McpClient .sync (HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT ).build ())
69- .build ();
70-
71- client .initialize ();
72- McpSchema .CallToolResult response = client
73- .callTool (McpSchema .CallToolRequest .builder ("test-tool" ).arguments (Map .of ()).build ());
74-
75- var calls = requestRecordingFilter .getCalls ();
76-
77- assertThat (calls ).filteredOn (c -> !c .body ().contains ("\" method\" :\" initialize\" " ))
78- // GET /mcp ; POST notification/initialized ; POST tools/call
79- .hasSize (3 )
80- .map (McpTestRequestRecordingServletFilter .Call ::headers )
81- .allSatisfy (headers -> assertThat (headers ).containsEntry ("mcp-protocol-version" ,
82- ProtocolVersions .MCP_2025_11_25 ));
83-
84- assertThat (response ).isNotNull ();
85- assertThat (response .content ()).hasSize (1 )
86- .first ()
87- .extracting (McpSchema .TextContent .class ::cast )
88- .extracting (McpSchema .TextContent ::text )
89- .isEqualTo (ProtocolVersions .MCP_2025_11_25 );
90- mcpServer .close ();
91- }
92-
93- @ Test
94- void usesServerSupportedVersion () {
95- startTomcat ();
96-
97- var transport = HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT )
98- .supportedProtocolVersions (List .of (ProtocolVersions .MCP_2025_11_25 , "2263-03-18" ))
99- .build ();
100- var client = McpClient .sync (transport ).build ();
101-
102- client .initialize ();
103- McpSchema .CallToolResult response = client
104- .callTool (McpSchema .CallToolRequest .builder ("test-tool" ).arguments (Map .of ()).build ());
105-
106- var calls = requestRecordingFilter .getCalls ();
107- // Initialize tells the server the Client's latest supported version
108- // FIXME: Set the correct protocol version on GET /mcp
109- assertThat (calls ).filteredOn (c -> c .method ().equals ("POST" ) && !c .body ().contains ("\" method\" :\" initialize\" " ))
110- // POST notification/initialized ; POST tools/call
111- .hasSize (2 )
112- .map (McpTestRequestRecordingServletFilter .Call ::headers )
113- .allSatisfy (headers -> assertThat (headers ).containsEntry ("mcp-protocol-version" ,
114- ProtocolVersions .MCP_2025_11_25 ));
115-
116- assertThat (response ).isNotNull ();
117- assertThat (response .content ()).hasSize (1 )
118- .first ()
119- .extracting (McpSchema .TextContent .class ::cast )
120- .extracting (McpSchema .TextContent ::text )
121- .isEqualTo (ProtocolVersions .MCP_2025_11_25 );
122- mcpServer .close ();
123- }
124-
125- private void startTomcat () {
64+ @ BeforeAll
65+ static void startTomcat () {
12666 tomcat = TomcatTestUtil .createTomcatServer ("" , PORT , transport , requestRecordingFilter );
12767 try {
12868 tomcat .start ();
@@ -133,7 +73,8 @@ private void startTomcat() {
13373 }
13474 }
13575
136- private void stopTomcat () {
76+ @ AfterAll
77+ static void stopTomcat () {
13778 if (tomcat != null ) {
13879 try {
13980 tomcat .stop ();
@@ -145,4 +86,92 @@ private void stopTomcat() {
14586 }
14687 }
14788
89+ @ BeforeEach
90+ void setUp () {
91+ requestRecordingFilter .clear ();
92+ }
93+
94+ @ Test
95+ void usesLatestVersion () {
96+ try (var client = McpClient .sync (HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT ).build ())
97+ .build ()) {
98+ client .initialize ();
99+ McpSchema .CallToolResult response = client
100+ .callTool (McpSchema .CallToolRequest .builder ("test-tool" ).arguments (Map .of ()).build ());
101+
102+ var calls = requestRecordingFilter .getCalls ();
103+
104+ assertThat (calls ).filteredOn (c -> !c .body ().contains ("\" method\" :\" initialize\" " ))
105+ // GET /mcp ; POST notification/initialized ; POST tools/call
106+ .hasSize (3 )
107+ .map (McpTestRequestRecordingServletFilter .Call ::headers )
108+ .allSatisfy (headers -> assertThat (headers ).containsEntry ("mcp-protocol-version" ,
109+ ProtocolVersions .MCP_2025_11_25 ));
110+
111+ assertThat (response ).isNotNull ();
112+ assertThat (response .content ()).hasSize (1 )
113+ .first ()
114+ .extracting (McpSchema .TextContent .class ::cast )
115+ .extracting (McpSchema .TextContent ::text )
116+ .isEqualTo (ProtocolVersions .MCP_2025_11_25 );
117+ }
118+
119+ }
120+
121+ @ Test
122+ void usesServerSupportedVersion () {
123+ var transport = HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT )
124+ .supportedProtocolVersions (List .of (ProtocolVersions .MCP_2025_11_25 , "2263-03-18" ))
125+ .build ();
126+ try (var client = McpClient .sync (transport ).build ()) {
127+ client .initialize ();
128+ McpSchema .CallToolResult response = client
129+ .callTool (McpSchema .CallToolRequest .builder ("test-tool" ).arguments (Map .of ()).build ());
130+
131+ var calls = requestRecordingFilter .getCalls ();
132+ // Initialize tells the server the Client's latest supported version
133+ // FIXME: Set the correct protocol version on GET /mcp
134+ assertThat (calls )
135+ .filteredOn (c -> c .method ().equals ("POST" ) && !c .body ().contains ("\" method\" :\" initialize\" " ))
136+ // POST notification/initialized ; POST tools/call
137+ .hasSize (2 )
138+ .map (McpTestRequestRecordingServletFilter .Call ::headers )
139+ .allSatisfy (headers -> assertThat (headers ).containsEntry ("mcp-protocol-version" ,
140+ ProtocolVersions .MCP_2025_11_25 ));
141+
142+ assertThat (response ).isNotNull ();
143+ assertThat (response .content ()).hasSize (1 )
144+ .first ()
145+ .extracting (McpSchema .TextContent .class ::cast )
146+ .extracting (McpSchema .TextContent ::text )
147+ .isEqualTo (ProtocolVersions .MCP_2025_11_25 );
148+ }
149+ }
150+
151+ @ Test
152+ void clientDoesNotSupportProtocolVersion20241105ByDefault () {
153+ var transport = HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT ).build ();
154+
155+ // Testing it on the wire would require building a custom transport
156+ // We trust the protocolVersions() accessor instead
157+ assertThat (transport .protocolVersions ()).containsExactly (ProtocolVersions .MCP_2025_03_26 ,
158+ ProtocolVersions .MCP_2025_06_18 , ProtocolVersions .MCP_2025_11_25 );
159+ }
160+
161+ @ Test
162+ void serverDoesNotSupportProtocolVersion20241105 () {
163+ var clientTransport = HttpClientStreamableHttpTransport .builder ("http://localhost:" + PORT )
164+ .supportedProtocolVersions (List .of (ProtocolVersions .MCP_2024_11_05 ))
165+ .build ();
166+ try (var client = McpClient .sync (clientTransport ).build ()) {
167+ assertThatThrownBy (client ::initialize ).rootCause ()
168+ .isInstanceOf (McpError .class )
169+ .asInstanceOf (type (McpError .class ))
170+ .extracting (Throwable ::getMessage )
171+ .isEqualTo ("Unsupported protocol version" );
172+ mcpServer .close ();
173+ }
174+
175+ }
176+
148177}
0 commit comments