diff --git a/mcp-bom/pom.xml b/mcp-bom/pom.xml
index 3c83afda3..02ff025d2 100644
--- a/mcp-bom/pom.xml
+++ b/mcp-bom/pom.xml
@@ -7,7 +7,7 @@
io.modelcontextprotocol.sdk
mcp-parent
- 0.15.0-SNAPSHOT
+ 0.16.0
mcp-bom
diff --git a/mcp-core/pom.xml b/mcp-core/pom.xml
index 5681385b6..dc244a603 100644
--- a/mcp-core/pom.xml
+++ b/mcp-core/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdk
mcp-parent
- 0.15.0-SNAPSHOT
+ 0.16.0
mcp-core
jar
@@ -68,7 +68,7 @@
io.modelcontextprotocol.sdk
mcp-json
- 0.15.0-SNAPSHOT
+ 0.16.0
@@ -101,7 +101,7 @@
io.modelcontextprotocol.sdk
mcp-json-jackson2
- 0.15.0-SNAPSHOT
+ 0.16.0
test
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java
index 047462ae4..87c84ba1b 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java
@@ -598,7 +598,6 @@ public AsyncSpecification resources(McpServerFeatures.AsyncResourceSpecificat
* null.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if resourceTemplates is null.
- * @see #resourceTemplates(ResourceTemplate...)
*/
public AsyncSpecification resourceTemplates(
List resourceTemplates) {
@@ -1195,7 +1194,6 @@ public SyncSpecification resources(McpServerFeatures.SyncResourceSpecificatio
* null.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if resourceTemplates is null.
- * @see #resourceTemplates(ResourceTemplate...)
*/
public SyncSpecification resourceTemplates(
List resourceTemplates) {
@@ -1703,7 +1701,6 @@ public StatelessAsyncSpecification resources(
* templates.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if resourceTemplates is null.
- * @see #resourceTemplates(ResourceTemplate...)
*/
public StatelessAsyncSpecification resourceTemplates(
List resourceTemplates) {
@@ -2166,7 +2163,6 @@ public StatelessSyncSpecification resources(
* existing templates.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if resourceTemplates is null.
- * @see #resourceTemplates(ResourceTemplate...)
*/
public StatelessSyncSpecification resourceTemplates(
List resourceTemplatesSpec) {
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
index bc3f53467..0ba7ab3b8 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
@@ -166,9 +166,15 @@ private void handle(McpSchema.JSONRPCMessage message) {
else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
handleIncomingRequest(request).onErrorResume(error -> {
+
+ McpSchema.JSONRPCResponse.JSONRPCError jsonRpcError = (error instanceof McpError mcpError
+ && mcpError.getJsonRpcError() != null) ? mcpError.getJsonRpcError()
+ // TODO: add error message through the data field
+ : new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
+ error.getMessage(), McpError.aggregateExceptionMessages(error));
+
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
- new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
- error.getMessage(), null));
+ jsonRpcError);
return Mono.just(errorResponse);
}).flatMap(this.transport::sendMessage).onErrorComplete(t -> {
logger.warn("Issue sending response to the client, ", t);
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
index e43469903..342fc5347 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
@@ -167,9 +167,9 @@ public sealed interface Request extends Meta
permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, CompleteRequest,
GetPromptRequest, ReadResourceRequest, SubscribeRequest, UnsubscribeRequest, PaginatedRequest {
- default String progressToken() {
+ default Object progressToken() {
if (meta() != null && meta().containsKey("progressToken")) {
- return meta().get("progressToken").toString();
+ return meta().get("progressToken");
}
return null;
}
@@ -277,12 +277,12 @@ public record JSONRPCNotification( // @formatter:off
}
/**
- * A successful (non-error) response to a request.
+ * A response to a request (successful, or error).
*
* @param jsonrpc The JSON-RPC version (must be "2.0")
* @param id The request identifier that this response corresponds to
- * @param result The result of the successful request
- * @param error Error information if the request failed
+ * @param result The result of the successful request; null if error
+ * @param error Error information if the request failed; null if has result
*/
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -1502,7 +1502,7 @@ public Builder meta(Map meta) {
return this;
}
- public Builder progressToken(String progressToken) {
+ public Builder progressToken(Object progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
@@ -1912,7 +1912,7 @@ public Builder meta(Map meta) {
return this;
}
- public Builder progressToken(String progressToken) {
+ public Builder progressToken(Object progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
@@ -2080,7 +2080,7 @@ public Builder meta(Map meta) {
return this;
}
- public Builder progressToken(String progressToken) {
+ public Builder progressToken(Object progressToken) {
if (this.meta == null) {
this.meta = new HashMap<>();
}
@@ -2217,13 +2217,13 @@ public record PaginatedResult(@JsonProperty("nextCursor") String nextCursor) {
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true)
public record ProgressNotification( // @formatter:off
- @JsonProperty("progressToken") String progressToken,
+ @JsonProperty("progressToken") Object progressToken,
@JsonProperty("progress") Double progress,
@JsonProperty("total") Double total,
@JsonProperty("message") String message,
@JsonProperty("_meta") Map meta) implements Notification { // @formatter:on
- public ProgressNotification(String progressToken, double progress, Double total, String message) {
+ public ProgressNotification(Object progressToken, double progress, Double total, String message) {
this(progressToken, progress, total, message, null);
}
}
diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java
index 86912b4bf..3de06f503 100644
--- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java
+++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java
@@ -6,11 +6,10 @@
import java.time.Duration;
import java.util.Map;
+import java.util.function.Function;
import io.modelcontextprotocol.MockMcpClientTransport;
import io.modelcontextprotocol.json.TypeRef;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,7 +18,6 @@
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Test suite for {@link McpClientSession} that verifies its JSON-RPC message handling,
@@ -39,35 +37,6 @@ class McpClientSessionTests {
private static final String ECHO_METHOD = "echo";
- private McpClientSession session;
-
- private MockMcpClientTransport transport;
-
- @BeforeEach
- void setUp() {
- transport = new MockMcpClientTransport();
- session = new McpClientSession(TIMEOUT, transport, Map.of(),
- Map.of(TEST_NOTIFICATION, params -> Mono.fromRunnable(() -> logger.info("Status update: {}", params))));
- }
-
- @AfterEach
- void tearDown() {
- if (session != null) {
- session.close();
- }
- }
-
- @Test
- void testConstructorWithInvalidArguments() {
- assertThatThrownBy(() -> new McpClientSession(null, transport, Map.of(), Map.of()))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("The requestTimeout can not be null");
-
- assertThatThrownBy(() -> new McpClientSession(TIMEOUT, null, Map.of(), Map.of()))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("transport can not be null");
- }
-
TypeRef responseType = new TypeRef<>() {
};
@@ -76,6 +45,11 @@ void testSendRequest() {
String testParam = "test parameter";
String responseData = "test response";
+ var transport = new MockMcpClientTransport();
+ var session = new McpClientSession(TIMEOUT, transport, Map.of(),
+ Map.of(TEST_NOTIFICATION, params -> Mono.fromRunnable(() -> logger.info("Status update: {}", params))),
+ Function.identity());
+
// Create a Mono that will emit the response after the request is sent
Mono responseMono = session.sendRequest(TEST_METHOD, testParam, responseType);
// Verify response handling
@@ -92,10 +66,17 @@ void testSendRequest() {
assertThat(request.params()).isEqualTo(testParam);
assertThat(response).isEqualTo(responseData);
}).verifyComplete();
+
+ session.close();
}
@Test
void testSendRequestWithError() {
+ var transport = new MockMcpClientTransport();
+ var session = new McpClientSession(TIMEOUT, transport, Map.of(),
+ Map.of(TEST_NOTIFICATION, params -> Mono.fromRunnable(() -> logger.info("Status update: {}", params))),
+ Function.identity());
+
Mono responseMono = session.sendRequest(TEST_METHOD, "test", responseType);
// Verify error handling
@@ -107,20 +88,34 @@ void testSendRequestWithError() {
transport.simulateIncomingMessage(
new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null, error));
}).expectError(McpError.class).verify();
+
+ session.close();
}
@Test
void testRequestTimeout() {
+ var transport = new MockMcpClientTransport();
+ var session = new McpClientSession(TIMEOUT, transport, Map.of(),
+ Map.of(TEST_NOTIFICATION, params -> Mono.fromRunnable(() -> logger.info("Status update: {}", params))),
+ Function.identity());
+
Mono responseMono = session.sendRequest(TEST_METHOD, "test", responseType);
// Verify timeout
StepVerifier.create(responseMono)
.expectError(java.util.concurrent.TimeoutException.class)
.verify(TIMEOUT.plusSeconds(1));
+
+ session.close();
}
@Test
void testSendNotification() {
+ var transport = new MockMcpClientTransport();
+ var session = new McpClientSession(TIMEOUT, transport, Map.of(),
+ Map.of(TEST_NOTIFICATION, params -> Mono.fromRunnable(() -> logger.info("Status update: {}", params))),
+ Function.identity());
+
Map params = Map.of("key", "value");
Mono notificationMono = session.sendNotification(TEST_NOTIFICATION, params);
@@ -132,6 +127,8 @@ void testSendNotification() {
assertThat(notification.method()).isEqualTo(TEST_NOTIFICATION);
assertThat(notification.params()).isEqualTo(params);
}).verifyComplete();
+
+ session.close();
}
@Test
@@ -139,8 +136,8 @@ void testRequestHandling() {
String echoMessage = "Hello MCP!";
Map> requestHandlers = Map.of(ECHO_METHOD,
params -> Mono.just(params));
- transport = new MockMcpClientTransport();
- session = new McpClientSession(TIMEOUT, transport, requestHandlers, Map.of());
+ var transport = new MockMcpClientTransport();
+ var session = new McpClientSession(TIMEOUT, transport, requestHandlers, Map.of(), Function.identity());
// Simulate incoming request
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, ECHO_METHOD,
@@ -153,15 +150,18 @@ void testRequestHandling() {
McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage;
assertThat(response.result()).isEqualTo(echoMessage);
assertThat(response.error()).isNull();
+
+ session.close();
}
@Test
void testNotificationHandling() {
Sinks.One