Skip to content

Commit 4d5eaad

Browse files
authored
Merge branch 'modelcontextprotocol:main' into main
2 parents afc037b + c711f83 commit 4d5eaad

File tree

36 files changed

+5145
-1291
lines changed

36 files changed

+5145
-1291
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This SDK enables Java applications to interact with AI models and tools through
1010
For comprehensive guides and SDK API documentation
1111

1212
- [Features](https://modelcontextprotocol.io/sdk/java/mcp-overview#features) - Overview the features provided by the Java MCP SDK
13-
- [Acrchitecture](https://modelcontextprotocol.io/sdk/java/mcp-overview#architecture) - Java MCP SDK architecture overview.
13+
- [Architecture](https://modelcontextprotocol.io/sdk/java/mcp-overview#architecture) - Java MCP SDK architecture overview.
1414
- [Java Dependencies / BOM](https://modelcontextprotocol.io/sdk/java/mcp-overview#dependencies) - Java dependencies and BOM.
1515
- [Java MCP Client](https://modelcontextprotocol.io/sdk/java/mcp-client) - Learn how to use the MCP client to interact with MCP servers.
1616
- [Java MCP Server](https://modelcontextprotocol.io/sdk/java/mcp-server) - Learn how to implement and configure a MCP servers.

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,10 @@ private DefaultMcpTransportSession createTransportSession() {
129129
Function<String, Publisher<Void>> onClose = sessionId -> sessionId == null ? Mono.empty()
130130
: webClient.delete().uri(this.endpoint).headers(httpHeaders -> {
131131
httpHeaders.add("mcp-session-id", sessionId);
132-
})
133-
.retrieve()
134-
.toBodilessEntity()
135-
.doOnError(e -> logger.warn("Got error when closing transport", e))
136-
.then();
132+
}).retrieve().toBodilessEntity().onErrorComplete(e -> {
133+
logger.warn("Got error when closing transport", e);
134+
return true;
135+
}).then();
137136
return new DefaultMcpTransportSession(onClose);
138137
}
139138

@@ -305,12 +304,12 @@ else if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
305304
}
306305
})
307306
.flatMap(jsonRpcMessage -> this.handler.get().apply(Mono.just(jsonRpcMessage)))
308-
.onErrorResume(t -> {
307+
.onErrorComplete(t -> {
309308
// handle the error first
310309
this.handleException(t);
311310
// inform the caller of sendMessage
312311
sink.error(t);
313-
return Flux.empty();
312+
return true;
314313
})
315314
.doFinally(s -> {
316315
Disposable ref = disposableRef.getAndSet(null);

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -432,12 +432,6 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
432432
Function<ElicitRequest, ElicitResult> elicitationHandler = request -> {
433433
assertThat(request.message()).isNotEmpty();
434434
assertThat(request.requestedSchema()).isNotNull();
435-
try {
436-
TimeUnit.SECONDS.sleep(2);
437-
}
438-
catch (InterruptedException e) {
439-
throw new RuntimeException(e);
440-
}
441435
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
442436
};
443437

@@ -491,14 +485,18 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
491485
@ValueSource(strings = { "httpclient", "webflux" })
492486
void testCreateElicitationWithRequestTimeoutFail(String clientType) {
493487

488+
var latch = new CountDownLatch(1);
494489
// Client
495490
var clientBuilder = clientBuilders.get(clientType);
496491

497492
Function<ElicitRequest, ElicitResult> elicitationHandler = request -> {
498493
assertThat(request.message()).isNotEmpty();
499494
assertThat(request.requestedSchema()).isNotNull();
495+
500496
try {
501-
TimeUnit.SECONDS.sleep(2);
497+
if (!latch.await(2, TimeUnit.SECONDS)) {
498+
throw new RuntimeException("Timeout waiting for elicitation processing");
499+
}
502500
}
503501
catch (InterruptedException e) {
504502
throw new RuntimeException(e);
@@ -536,7 +534,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) {
536534

537535
var mcpServer = McpServer.async(mcpServerTransportProvider)
538536
.serverInfo("test-server", "1.0.0")
539-
.requestTimeout(Duration.ofSeconds(1))
537+
.requestTimeout(Duration.ofSeconds(1)) // 1 second.
540538
.tools(tool)
541539
.build();
542540

@@ -1025,4 +1023,61 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
10251023
mcpServer.close();
10261024
}
10271025

1028-
}
1026+
// ---------------------------------------
1027+
// Ping Tests
1028+
// ---------------------------------------
1029+
@ParameterizedTest(name = "{0} : {displayName} ")
1030+
@ValueSource(strings = { "httpclient", "webflux" })
1031+
void testPingSuccess(String clientType) {
1032+
var clientBuilder = clientBuilders.get(clientType);
1033+
1034+
// Create server with a tool that uses ping functionality
1035+
AtomicReference<String> executionOrder = new AtomicReference<>("");
1036+
1037+
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
1038+
new McpSchema.Tool("ping-async-test", "Test ping async behavior", emptyJsonSchema),
1039+
(exchange, request) -> {
1040+
1041+
executionOrder.set(executionOrder.get() + "1");
1042+
1043+
// Test async ping behavior
1044+
return exchange.ping().doOnNext(result -> {
1045+
1046+
assertThat(result).isNotNull();
1047+
// Ping should return an empty object or map
1048+
assertThat(result).isInstanceOf(Map.class);
1049+
1050+
executionOrder.set(executionOrder.get() + "2");
1051+
assertThat(result).isNotNull();
1052+
}).then(Mono.fromCallable(() -> {
1053+
executionOrder.set(executionOrder.get() + "3");
1054+
return new CallToolResult("Async ping test completed", false);
1055+
}));
1056+
});
1057+
1058+
var mcpServer = McpServer.async(mcpServerTransportProvider)
1059+
.serverInfo("test-server", "1.0.0")
1060+
.capabilities(ServerCapabilities.builder().tools(true).build())
1061+
.tools(tool)
1062+
.build();
1063+
1064+
try (var mcpClient = clientBuilder.build()) {
1065+
1066+
// Initialize client
1067+
InitializeResult initResult = mcpClient.initialize();
1068+
assertThat(initResult).isNotNull();
1069+
1070+
// Call the tool that tests ping async behavior
1071+
CallToolResult result = mcpClient.callTool(new McpSchema.CallToolRequest("ping-async-test", Map.of()));
1072+
assertThat(result).isNotNull();
1073+
assertThat(result.content().get(0)).isInstanceOf(McpSchema.TextContent.class);
1074+
assertThat(((McpSchema.TextContent) result.content().get(0)).text()).isEqualTo("Async ping test completed");
1075+
1076+
// Verify execution order
1077+
assertThat(executionOrder.get()).isEqualTo("123");
1078+
}
1079+
1080+
mcpServer.closeGracefully().block();
1081+
}
1082+
1083+
}

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebClientStreamableHttpAsyncClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.modelcontextprotocol.client;
22

3-
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
4-
import io.modelcontextprotocol.spec.McpClientTransport;
53
import org.junit.jupiter.api.Timeout;
4+
import org.springframework.web.reactive.function.client.WebClient;
65
import org.testcontainers.containers.GenericContainer;
76
import org.testcontainers.containers.wait.strategy.Wait;
87

9-
import org.springframework.web.reactive.function.client.WebClient;
8+
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
9+
import io.modelcontextprotocol.spec.McpClientTransport;
1010

1111
@Timeout(15)
1212
public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncClientTests {

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebClientStreamableHttpSyncClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.modelcontextprotocol.client;
22

3-
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
4-
import io.modelcontextprotocol.spec.McpClientTransport;
53
import org.junit.jupiter.api.Timeout;
4+
import org.springframework.web.reactive.function.client.WebClient;
65
import org.testcontainers.containers.GenericContainer;
76
import org.testcontainers.containers.wait.strategy.Wait;
87

9-
import org.springframework.web.reactive.function.client.WebClient;
8+
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
9+
import io.modelcontextprotocol.spec.McpClientTransport;
1010

1111
@Timeout(15)
1212
public class WebClientStreamableHttpSyncClientTests extends AbstractMcpSyncClientTests {

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,4 +862,58 @@ void testInitialize() {
862862
mcpServer.close();
863863
}
864864

865+
// ---------------------------------------
866+
// Ping Tests
867+
// ---------------------------------------
868+
@Test
869+
void testPingSuccess() {
870+
// Create server with a tool that uses ping functionality
871+
AtomicReference<String> executionOrder = new AtomicReference<>("");
872+
873+
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
874+
new McpSchema.Tool("ping-async-test", "Test ping async behavior", emptyJsonSchema),
875+
(exchange, request) -> {
876+
877+
executionOrder.set(executionOrder.get() + "1");
878+
879+
// Test async ping behavior
880+
return exchange.ping().doOnNext(result -> {
881+
882+
assertThat(result).isNotNull();
883+
// Ping should return an empty object or map
884+
assertThat(result).isInstanceOf(Map.class);
885+
886+
executionOrder.set(executionOrder.get() + "2");
887+
assertThat(result).isNotNull();
888+
}).then(Mono.fromCallable(() -> {
889+
executionOrder.set(executionOrder.get() + "3");
890+
return new CallToolResult("Async ping test completed", false);
891+
}));
892+
});
893+
894+
var mcpServer = McpServer.async(mcpServerTransportProvider)
895+
.serverInfo("test-server", "1.0.0")
896+
.capabilities(ServerCapabilities.builder().tools(true).build())
897+
.tools(tool)
898+
.build();
899+
900+
try (var mcpClient = clientBuilder.build()) {
901+
902+
// Initialize client
903+
InitializeResult initResult = mcpClient.initialize();
904+
assertThat(initResult).isNotNull();
905+
906+
// Call the tool that tests ping async behavior
907+
CallToolResult result = mcpClient.callTool(new McpSchema.CallToolRequest("ping-async-test", Map.of()));
908+
assertThat(result).isNotNull();
909+
assertThat(result.content().get(0)).isInstanceOf(McpSchema.TextContent.class);
910+
assertThat(((McpSchema.TextContent) result.content().get(0)).text()).isEqualTo("Async ping test completed");
911+
912+
// Verify execution order
913+
assertThat(executionOrder.get()).isEqualTo("123");
914+
}
915+
916+
mcpServer.close();
917+
}
918+
865919
}

mcp-spring/mcp-spring-webmvc/src/test/resources/logback.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@
99
</appender>
1010

1111
<!-- Main MCP package -->
12-
<logger name="io.modelcontextprotocol" level="DEBUG"/>
12+
<logger name="io.modelcontextprotocol" level="INFO"/>
1313

1414
<!-- Client packages -->
15-
<logger name="io.modelcontextprotocol.client" level="DEBUG"/>
15+
<logger name="io.modelcontextprotocol.client" level="INFO"/>
1616

1717
<!-- Server transport package -->
18-
<logger name="io.modelcontextprotocol.server.transport" level="DEBUG"/>
18+
<logger name="io.modelcontextprotocol.server.transport" level="INFO"/>
1919

2020
<!-- Spec package -->
21-
<logger name="io.modelcontextprotocol.spec" level="DEBUG"/>
21+
<logger name="io.modelcontextprotocol.spec" level="INFO"/>
2222

2323
<!-- Root logger -->
2424
<root level="INFO">

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientResiliencyTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public abstract class AbstractMcpAsyncClientResiliencyTests {
7979
host = "http://" + ipAddressViaToxiproxy + ":" + portViaToxiproxy;
8080
}
8181

82-
private static void disconnect() {
82+
static void disconnect() {
8383
long start = System.nanoTime();
8484
try {
8585
// disconnect
@@ -96,7 +96,7 @@ private static void disconnect() {
9696
}
9797
}
9898

99-
private static void reconnect() {
99+
static void reconnect() {
100100
long start = System.nanoTime();
101101
try {
102102
proxy.toxics().get("RESET_UPSTREAM").remove();
@@ -110,7 +110,7 @@ private static void reconnect() {
110110
}
111111
}
112112

113-
private static void restartMcpServer() {
113+
static void restartMcpServer() {
114114
container.stop();
115115
container.start();
116116
}

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,20 @@ void testListAllTools() {
182182
});
183183
}
184184

185+
@Test
186+
void testListAllToolsReturnsImmutableList() {
187+
withClient(createMcpTransport(), mcpAsyncClient -> {
188+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools()))
189+
.consumeNextWith(result -> {
190+
assertThat(result.tools()).isNotNull();
191+
// Verify that the returned list is immutable
192+
assertThatThrownBy(() -> result.tools().add(new Tool("test", "test", "{\"type\":\"object\"}")))
193+
.isInstanceOf(UnsupportedOperationException.class);
194+
})
195+
.verifyComplete();
196+
});
197+
}
198+
185199
@Test
186200
void testPingWithoutInitialization() {
187201
verifyCallSucceedsWithImplicitInitialization(client -> client.ping(), "pinging the server");
@@ -333,6 +347,21 @@ void testListAllResources() {
333347
});
334348
}
335349

350+
@Test
351+
void testListAllResourcesReturnsImmutableList() {
352+
withClient(createMcpTransport(), mcpAsyncClient -> {
353+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources()))
354+
.consumeNextWith(result -> {
355+
assertThat(result.resources()).isNotNull();
356+
// Verify that the returned list is immutable
357+
assertThatThrownBy(
358+
() -> result.resources().add(Resource.builder().uri("test://uri").name("test").build()))
359+
.isInstanceOf(UnsupportedOperationException.class);
360+
})
361+
.verifyComplete();
362+
});
363+
}
364+
336365
@Test
337366
void testMcpAsyncClientState() {
338367
withClient(createMcpTransport(), mcpAsyncClient -> {
@@ -384,6 +413,20 @@ void testListAllPrompts() {
384413
});
385414
}
386415

416+
@Test
417+
void testListAllPromptsReturnsImmutableList() {
418+
withClient(createMcpTransport(), mcpAsyncClient -> {
419+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts()))
420+
.consumeNextWith(result -> {
421+
assertThat(result.prompts()).isNotNull();
422+
// Verify that the returned list is immutable
423+
assertThatThrownBy(() -> result.prompts().add(new Prompt("test", "test", null)))
424+
.isInstanceOf(UnsupportedOperationException.class);
425+
})
426+
.verifyComplete();
427+
});
428+
}
429+
387430
@Test
388431
void testGetPromptWithoutInitialization() {
389432
GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of());
@@ -553,6 +596,21 @@ void testListAllResourceTemplates() {
553596
});
554597
}
555598

599+
@Test
600+
void testListAllResourceTemplatesReturnsImmutableList() {
601+
withClient(createMcpTransport(), mcpAsyncClient -> {
602+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates()))
603+
.consumeNextWith(result -> {
604+
assertThat(result.resourceTemplates()).isNotNull();
605+
// Verify that the returned list is immutable
606+
assertThatThrownBy(() -> result.resourceTemplates()
607+
.add(new McpSchema.ResourceTemplate("test://template", "test", null, null, null)))
608+
.isInstanceOf(UnsupportedOperationException.class);
609+
})
610+
.verifyComplete();
611+
});
612+
}
613+
556614
// @Test
557615
void testResourceSubscription() {
558616
withClient(createMcpTransport(), mcpAsyncClient -> {

0 commit comments

Comments
 (0)