elicitResultRef = new AtomicReference<>();
+
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
@@ -414,13 +415,9 @@ void testCreateElicitationSuccess(String clientType) {
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
.build();
- StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
- assertThat(result).isNotNull();
- assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
- assertThat(result.content().get("message")).isEqualTo("Test message");
- }).verifyComplete();
-
- return Mono.just(callResponse);
+ return exchange.createElicitation(elicitationRequest)
+ .doOnNext(elicitResultRef::set)
+ .thenReturn(callResponse);
})
.build();
@@ -438,6 +435,11 @@ void testCreateElicitationSuccess(String clientType) {
assertThat(response).isNotNull();
assertThat(response).isEqualTo(callResponse);
+ assertWith(elicitResultRef.get(), result -> {
+ assertThat(result).isNotNull();
+ assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
+ assertThat(result.content().get("message")).isEqualTo("Test message");
+ });
}
finally {
mcpServer.closeGracefully().block();
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
index bee8f4f16..2ef45a1e0 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java
@@ -610,22 +610,17 @@ void testListAllResourceTemplatesReturnsImmutableList() {
});
}
- // @Test
+ @Test
void testResourceSubscription() {
withClient(createMcpTransport(), mcpAsyncClient -> {
- StepVerifier.create(mcpAsyncClient.listResources()).consumeNextWith(resources -> {
- if (!resources.resources().isEmpty()) {
- Resource firstResource = resources.resources().get(0);
-
- // Test subscribe
- StepVerifier.create(mcpAsyncClient.subscribeResource(new SubscribeRequest(firstResource.uri())))
- .verifyComplete();
-
- // Test unsubscribe
- StepVerifier.create(mcpAsyncClient.unsubscribeResource(new UnsubscribeRequest(firstResource.uri())))
- .verifyComplete();
+ StepVerifier.create(mcpAsyncClient.listResources().flatMap(resources -> {
+ if (resources.resources().isEmpty()) {
+ return Mono.empty();
}
- }).verifyComplete();
+ Resource firstResource = resources.resources().get(0);
+ return mcpAsyncClient.subscribeResource(new SubscribeRequest(firstResource.uri()))
+ .then(mcpAsyncClient.unsubscribeResource(new UnsubscribeRequest(firstResource.uri())));
+ })).verifyComplete();
});
}
diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java
index 5390cc4c2..6c2cc2bf4 100644
--- a/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java
+++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java
@@ -4,9 +4,11 @@
package io.modelcontextprotocol.server.transport;
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@@ -135,6 +137,42 @@ void shouldHandleIncomingMessages() throws Exception {
}).verifyComplete();
}
+ @Test
+ void shouldHandleUtf8MessagesWithNonUtf8DefaultCharset() throws Exception {
+ String utf8Content = "한글 漢字 café 🎉";
+ String jsonMessage = "{\"jsonrpc\":\"2.0\",\"method\":\"test\"," + "\"params\":{\"message\":\"" + utf8Content
+ + "\"},\"id\":1}\n";
+
+ // Start a subprocess with non-UTF-8 default charset
+ String javaHome = System.getProperty("java.home");
+ String classpath = System.getProperty("java.class.path");
+ ProcessBuilder pb = new ProcessBuilder(javaHome + "/bin/java", "-Dfile.encoding=ISO-8859-1", "-cp", classpath,
+ StdioUtf8TestServer.class.getName());
+ pb.redirectErrorStream(false);
+ Process process = pb.start();
+
+ try {
+ // Write UTF-8 encoded JSON-RPC message to the subprocess stdin
+ process.getOutputStream().write(jsonMessage.getBytes(StandardCharsets.UTF_8));
+ process.getOutputStream().flush();
+ process.getOutputStream().close();
+
+ // Read the echoed message from subprocess stdout
+ String result;
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ result = reader.readLine();
+ }
+
+ // Verify that multi-byte UTF-8 characters survived the round trip
+ assertThat(result).isEqualTo(utf8Content);
+ }
+ finally {
+ process.destroyForcibly();
+ process.waitFor(10, TimeUnit.SECONDS);
+ }
+ }
+
@Test
void shouldNotifyClients() {
// Set session factory
diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioUtf8TestServer.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioUtf8TestServer.java
new file mode 100644
index 000000000..3fc3a716d
--- /dev/null
+++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioUtf8TestServer.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ */
+
+package io.modelcontextprotocol.server.transport;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import io.modelcontextprotocol.json.McpJsonDefaults;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpServerSession;
+import reactor.core.publisher.Mono;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Minimal STDIO server process for testing UTF-8 encoding behavior.
+ *
+ *
+ * This class is spawned as a subprocess with {@code -Dfile.encoding=ISO-8859-1} to
+ * simulate a non-UTF-8 default charset environment. It uses
+ * {@link StdioServerTransportProvider} to read a JSON-RPC message from stdin and echoes
+ * the received {@code params.message} value back to stdout, allowing the parent test to
+ * verify that multi-byte UTF-8 characters are preserved regardless of the JVM default
+ * charset.
+ *
+ * @see StdioServerTransportProviderTests#shouldHandleUtf8MessagesWithNonUtf8DefaultCharset
+ */
+public class StdioUtf8TestServer {
+
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) throws Exception {
+ // Capture the original stdout for echoing the result later
+ PrintStream originalOut = System.out;
+
+ // Redirect System.out to stderr so that logger output does not
+ // interfere with the test result written to stdout
+ System.setOut(new PrintStream(System.err, true));
+
+ CountDownLatch messageLatch = new CountDownLatch(1);
+ StringBuilder receivedMessage = new StringBuilder();
+
+ StdioServerTransportProvider transportProvider = new StdioServerTransportProvider(McpJsonDefaults.getMapper(),
+ System.in, OutputStream.nullOutputStream());
+
+ McpServerSession.Factory sessionFactory = transport -> {
+ McpServerSession session = mock(McpServerSession.class);
+ when(session.handle(any())).thenAnswer(invocation -> {
+ McpSchema.JSONRPCMessage msg = invocation.getArgument(0);
+ if (msg instanceof McpSchema.JSONRPCRequest request) {
+ Map params = (Map) request.params();
+ receivedMessage.append(params.get("message"));
+ }
+ messageLatch.countDown();
+ return Mono.empty();
+ });
+ when(session.closeGracefully()).thenReturn(Mono.empty());
+ return session;
+ };
+
+ // Start processing stdin
+ transportProvider.setSessionFactory(sessionFactory);
+
+ // Wait for the message to be processed
+ if (messageLatch.await(10, TimeUnit.SECONDS)) {
+ // Write the received message to the original stdout in UTF-8
+ originalOut.write(receivedMessage.toString().getBytes(StandardCharsets.UTF_8));
+ originalOut.write('\n');
+ originalOut.flush();
+ }
+
+ transportProvider.closeGracefully().block(java.time.Duration.ofSeconds(5));
+ }
+
+}
diff --git a/mcp/pom.xml b/mcp/pom.xml
index 2db79f87b..9928f5d7b 100644
--- a/mcp/pom.xml
+++ b/mcp/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdk
mcp-parent
- 1.0.0-SNAPSHOT
+ 1.0.2-SNAPSHOT
mcp
jar
@@ -25,13 +25,13 @@
io.modelcontextprotocol.sdk
mcp-json-jackson3
- 1.0.0-SNAPSHOT
+ 1.0.2-SNAPSHOT
io.modelcontextprotocol.sdk
mcp-core
- 1.0.0-SNAPSHOT
+ 1.0.2-SNAPSHOT
diff --git a/mkdocs.yml b/mkdocs.yml
index 3e27c3fb5..5c71d8f54 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -48,9 +48,7 @@ nav:
- Contributing:
- Contributing Guide: contribute.md
- Documentation: development.md
- - API Reference: https://javadoc.io/doc/io.modelcontextprotocol.sdk/mcp-core/latest
- - News:
- - blog/index.md
+ - API Reference: https://www.javadocs.dev/io.modelcontextprotocol.sdk/mcp-core/1.0.0/index.html
markdown_extensions:
- admonition
@@ -94,4 +92,3 @@ extra:
plugins:
- search
- - blog
diff --git a/pom.xml b/pom.xml
index 13b456d8f..2bc24bb7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdk
mcp-parent
- 1.0.0-SNAPSHOT
+ 1.0.2-SNAPSHOT
pom
https://github.com/modelcontextprotocol/java-sdk