diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java index 42b91d14e..2cd2ad387 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransportTests.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -161,8 +162,8 @@ void testBuilderPattern() { @Test void testMessageProcessing() { // Create a test message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Simulate receiving the message transport.simulateMessageEvent(""" @@ -192,8 +193,8 @@ void testResponseMessageProcessing() { """); // Create and send a request message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message handling StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -216,8 +217,8 @@ void testErrorMessageProcessing() { """); // Create and send a request message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message handling StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -246,8 +247,8 @@ void testGracefulShutdown() { StepVerifier.create(transport.closeGracefully()).verifyComplete(); // Create a test message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message is not processed after shutdown StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -292,10 +293,10 @@ void testMultipleMessageProcessing() { """); // Create and send corresponding messages - JSONRPCRequest message1 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method1", "id1", + JSONRPCRequest message1 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method1", MessageId.of("id1"), Map.of("key", "value1")); - JSONRPCRequest message2 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method2", "id2", + JSONRPCRequest message2 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method2", MessageId.of("id2"), Map.of("key", "value2")); // Verify both messages are processed diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java index cc7d2abf8..e41c8838f 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.spec; import com.fasterxml.jackson.core.type.TypeReference; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import io.modelcontextprotocol.util.Assert; import org.reactivestreams.Publisher; import org.slf4j.Logger; @@ -47,7 +48,7 @@ public class McpClientSession implements McpSession { private final McpClientTransport transport; /** Map of pending responses keyed by request ID */ - private final ConcurrentHashMap> pendingResponses = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> pendingResponses = new ConcurrentHashMap<>(); /** Map of request handlers keyed by method name */ private final ConcurrentHashMap> requestHandlers = new ConcurrentHashMap<>(); @@ -231,10 +232,10 @@ private Mono handleIncomingNotification(McpSchema.JSONRPCNotification noti /** * Generates a unique request ID in a non-blocking way. Combines a session-specific * prefix with an atomic counter to ensure uniqueness. - * @return A unique request ID string + * @return A unique request ID */ - private String generateRequestId() { - return this.sessionPrefix + "-" + this.requestCounter.getAndIncrement(); + private MessageId generateRequestId() { + return MessageId.of(this.sessionPrefix + "-" + this.requestCounter.getAndIncrement()); } /** @@ -247,7 +248,7 @@ private String generateRequestId() { */ @Override public Mono sendRequest(String method, Object requestParams, TypeReference typeRef) { - String requestId = this.generateRequestId(); + MessageId requestId = this.generateRequestId(); return Mono.deferContextual(ctx -> Mono.create(pendingResponseSink -> { logger.debug("Sending message for method {}", method); diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 4a570aea0..b1af95d69 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +22,18 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.modelcontextprotocol.util.Assert; @@ -35,6 +46,7 @@ * @author Christian Tzolov * @author Luca Chang * @author Surbhi Bansal + * @author Zachary German */ public final class McpSchema { @@ -144,6 +156,107 @@ public static final class ErrorCodes { } + /** + * MCP JSON-RPC Message ID wrapper: MUST be non-null String or Int. + *

+ * Note: This does not follow the JSON-RPC 'id' specification, which is + * nullable and could be a floating-point. + *

+ */ + @JsonSerialize(using = MessageId.Serializer.class) + @JsonDeserialize(using = MessageId.Deserializer.class) + public static final class MessageId { + + private final Object value; + + public MessageId(String value) { + this.value = Objects.requireNonNull(value, "'id' must not be null"); + } + + public MessageId(Integer value) { + this.value = Objects.requireNonNull(value, "'id' must not be null"); + } + + public static MessageId of(Object raw) { + if (raw instanceof String s) + return new MessageId(s); + if (raw instanceof Integer i) + return new MessageId(i); + throw new IllegalArgumentException("MCP 'id' must be String or Integer"); + } + + public boolean isString() { + return value instanceof String; + } + + public boolean isInteger() { + return value instanceof Integer; + } + + public String asString() { + return (String) value; + } + + public Integer asInteger() { + return (Integer) value; + } + + public Object raw() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + MessageId messageId = (MessageId) o; + return value.equals(messageId.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public MessageId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonToken t = p.getCurrentToken(); + if (t == JsonToken.VALUE_STRING) { + return new MessageId(p.getText()); + } + else if (t == JsonToken.VALUE_NUMBER_INT) { + return new MessageId(p.getIntValue()); + } + throw JsonMappingException.from(p, "MCP 'id' must be a non-null String or Integer"); + } + + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(MessageId id, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (id.isString()) { + gen.writeString(id.asString()); + } + else { + gen.writeNumber(id.asInteger()); + } + } + + } + + } + public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, CompleteRequest, GetPromptRequest, PaginatedRequest, ReadResourceRequest { @@ -216,7 +329,7 @@ public sealed interface JSONRPCMessage permits JSONRPCRequest, JSONRPCNotificati public record JSONRPCRequest( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, @JsonProperty("method") String method, - @JsonProperty("id") Object id, + @JsonProperty("id") MessageId id, @JsonProperty("params") Object params) implements JSONRPCMessage { // @formatter:on } @@ -251,7 +364,7 @@ public record JSONRPCNotification( // @formatter:off // @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) public record JSONRPCResponse( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, - @JsonProperty("id") Object id, + @JsonProperty("id") MessageId id, @JsonProperty("result") Object result, @JsonProperty("error") JSONRPCError error) implements JSONRPCMessage { // @formatter:on diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java index 86906d859..43bbd93db 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import io.modelcontextprotocol.server.McpAsyncServerExchange; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; @@ -23,7 +24,7 @@ public class McpServerSession implements McpSession { private static final Logger logger = LoggerFactory.getLogger(McpServerSession.class); - private final ConcurrentHashMap> pendingResponses = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> pendingResponses = new ConcurrentHashMap<>(); private final String id; @@ -104,13 +105,13 @@ public void init(McpSchema.ClientCapabilities clientCapabilities, McpSchema.Impl this.clientInfo.lazySet(clientInfo); } - private String generateRequestId() { - return this.id + "-" + this.requestCounter.getAndIncrement(); + private MessageId generateRequestId() { + return MessageId.of(this.id + "-" + this.requestCounter.getAndIncrement()); } @Override public Mono sendRequest(String method, Object requestParams, TypeReference typeRef) { - String requestId = this.generateRequestId(); + MessageId requestId = this.generateRequestId(); return Mono.create(sink -> { this.pendingResponses.put(requestId, sink); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java index e773c8381..6f67faa6b 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java @@ -17,6 +17,7 @@ import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.InitializeResult; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import io.modelcontextprotocol.spec.McpSchema.PaginatedRequest; import io.modelcontextprotocol.spec.McpSchema.Root; import org.junit.jupiter.api.Test; @@ -172,7 +173,7 @@ void testRootsListRequestHandling() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_ROOTS_LIST, "test-id", null); + McpSchema.METHOD_ROOTS_LIST, MessageId.of("test-id"), null); transport.simulateIncomingMessage(request); // Verify response @@ -180,7 +181,7 @@ void testRootsListRequestHandling() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.result()) .isEqualTo(new McpSchema.ListRootsResult(List.of(new Root("file:///test/path", "test-root")))); assertThat(response.error()).isNull(); @@ -309,7 +310,7 @@ void testSamplingCreateMessageRequestHandling() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, "test-id", messageRequest); + McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, MessageId.of("test-id"), messageRequest); transport.simulateIncomingMessage(request); // Verify response @@ -317,7 +318,7 @@ void testSamplingCreateMessageRequestHandling() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.error()).isNull(); McpSchema.CreateMessageResult result = transport.unmarshalFrom(response.result(), @@ -350,7 +351,7 @@ void testSamplingCreateMessageRequestHandlingWithoutCapability() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, "test-id", messageRequest); + McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, MessageId.of("test-id"), messageRequest); transport.simulateIncomingMessage(request); // Verify error response @@ -358,7 +359,7 @@ void testSamplingCreateMessageRequestHandlingWithoutCapability() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.result()).isNull(); assertThat(response.error()).isNotNull(); assertThat(response.error().message()).contains("Method not found: sampling/createMessage"); @@ -414,7 +415,7 @@ void testElicitationCreateRequestHandling() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_ELICITATION_CREATE, "test-id", elicitRequest); + McpSchema.METHOD_ELICITATION_CREATE, MessageId.of("test-id"), elicitRequest); transport.simulateIncomingMessage(request); // Verify response @@ -422,7 +423,7 @@ void testElicitationCreateRequestHandling() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.error()).isNull(); McpSchema.ElicitResult result = transport.unmarshalFrom(response.result(), new TypeReference<>() { @@ -459,7 +460,7 @@ void testElicitationFailRequestHandling(McpSchema.ElicitResult.Action action) { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_ELICITATION_CREATE, "test-id", elicitRequest); + McpSchema.METHOD_ELICITATION_CREATE, MessageId.of("test-id"), elicitRequest); transport.simulateIncomingMessage(request); // Verify response @@ -467,7 +468,7 @@ void testElicitationFailRequestHandling(McpSchema.ElicitResult.Action action) { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.error()).isNull(); McpSchema.ElicitResult result = transport.unmarshalFrom(response.result(), new TypeReference<>() { @@ -498,7 +499,7 @@ void testElicitationCreateRequestHandlingWithoutCapability() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_ELICITATION_CREATE, "test-id", elicitRequest); + McpSchema.METHOD_ELICITATION_CREATE, MessageId.of("test-id"), elicitRequest); transport.simulateIncomingMessage(request); // Verify error response @@ -506,7 +507,7 @@ void testElicitationCreateRequestHandlingWithoutCapability() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("test-id"); + assertThat(response.id()).isEqualTo(MessageId.of("test-id")); assertThat(response.result()).isNull(); assertThat(response.error()).isNotNull(); assertThat(response.error().message()).contains("Method not found: elicitation/create"); @@ -535,7 +536,7 @@ void testPingMessageRequestHandling() { // Simulate incoming ping request from server McpSchema.JSONRPCRequest pingRequest = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, - McpSchema.METHOD_PING, "ping-id", null); + McpSchema.METHOD_PING, MessageId.of("ping-id"), null); transport.simulateIncomingMessage(pingRequest); // Verify response @@ -543,7 +544,7 @@ void testPingMessageRequestHandling() { assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; - assertThat(response.id()).isEqualTo("ping-id"); + assertThat(response.id()).isEqualTo(MessageId.of("ping-id")); assertThat(response.error()).isNull(); assertThat(response.result()).isInstanceOf(Map.class); assertThat(((Map) response.result())).isEmpty(); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index 31430543a..bda36ee4d 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -108,7 +109,7 @@ void cleanup() { @Test void testErrorOnBogusMessage() { // bogus message - JSONRPCRequest bogusMessage = new JSONRPCRequest(null, null, "test-id", Map.of("key", "value")); + JSONRPCRequest bogusMessage = new JSONRPCRequest(null, null, MessageId.of("test-id"), Map.of("key", "value")); StepVerifier.create(transport.sendMessage(bogusMessage)) .verifyErrorMessage( @@ -118,8 +119,8 @@ void testErrorOnBogusMessage() { @Test void testMessageProcessing() { // Create a test message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Simulate receiving the message transport.simulateMessageEvent(""" @@ -149,8 +150,8 @@ void testResponseMessageProcessing() { """); // Create and send a request message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message handling StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -173,8 +174,8 @@ void testErrorMessageProcessing() { """); // Create and send a request message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message handling StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -203,8 +204,8 @@ void testGracefulShutdown() { StepVerifier.create(transport.closeGracefully()).verifyComplete(); // Create a test message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); + JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", + MessageId.of("test-id"), Map.of("key", "value")); // Verify message is not processed after shutdown StepVerifier.create(transport.sendMessage(testMessage)).verifyComplete(); @@ -248,10 +249,10 @@ void testMultipleMessageProcessing() { """); // Create and send corresponding messages - JSONRPCRequest message1 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method1", "id1", + JSONRPCRequest message1 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method1", MessageId.of("id1"), Map.of("key", "value1")); - JSONRPCRequest message2 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method2", "id2", + JSONRPCRequest message2 = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method2", MessageId.of("id2"), Map.of("key", "value2")); // Verify both messages are processed diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java index f643f1ba3..b7b78a9ea 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java @@ -10,6 +10,7 @@ import io.modelcontextprotocol.MockMcpServerTransport; import io.modelcontextprotocol.MockMcpServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -23,7 +24,7 @@ class McpServerProtocolVersionTests { private static final McpSchema.Implementation CLIENT_INFO = new McpSchema.Implementation("test-client", "1.0.0"); - private McpSchema.JSONRPCRequest jsonRpcInitializeRequest(String requestId, String protocolVersion) { + private McpSchema.JSONRPCRequest jsonRpcInitializeRequest(MessageId requestId, String protocolVersion) { return new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, requestId, new McpSchema.InitializeRequest(protocolVersion, null, CLIENT_INFO)); } @@ -34,7 +35,7 @@ void shouldUseLatestVersionByDefault() { var transportProvider = new MockMcpServerTransportProvider(serverTransport); McpAsyncServer server = McpServer.async(transportProvider).serverInfo(SERVER_INFO).build(); - String requestId = UUID.randomUUID().toString(); + MessageId requestId = MessageId.of(UUID.randomUUID().toString()); transportProvider .simulateIncomingMessage(jsonRpcInitializeRequest(requestId, McpSchema.LATEST_PROTOCOL_VERSION)); @@ -60,7 +61,7 @@ void shouldNegotiateSpecificVersion() { server.setProtocolVersions(List.of(oldVersion, McpSchema.LATEST_PROTOCOL_VERSION)); - String requestId = UUID.randomUUID().toString(); + MessageId requestId = MessageId.of(UUID.randomUUID().toString()); transportProvider.simulateIncomingMessage(jsonRpcInitializeRequest(requestId, oldVersion)); @@ -83,7 +84,7 @@ void shouldSuggestLatestVersionForUnsupportedVersion() { McpAsyncServer server = McpServer.async(transportProvider).serverInfo(SERVER_INFO).build(); - String requestId = UUID.randomUUID().toString(); + MessageId requestId = MessageId.of(UUID.randomUUID().toString()); transportProvider.simulateIncomingMessage(jsonRpcInitializeRequest(requestId, unsupportedVersion)); @@ -111,7 +112,8 @@ void shouldUseHighestVersionWhenMultipleSupported() { server.setProtocolVersions(List.of(oldVersion, middleVersion, latestVersion)); - String requestId = UUID.randomUUID().toString(); + MessageId requestId = MessageId.of(UUID.randomUUID().toString()); + transportProvider.simulateIncomingMessage(jsonRpcInitializeRequest(requestId, latestVersion)); McpSchema.JSONRPCMessage response = serverTransport.getLastSentMessage(); diff --git a/mcp/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java b/mcp/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java index 85dcd26c2..5e3330d80 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpClientSessionTests.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import io.modelcontextprotocol.MockMcpClientTransport; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -144,7 +145,7 @@ void testRequestHandling() { // Simulate incoming request McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, ECHO_METHOD, - "test-id", echoMessage); + MessageId.of("test-id"), echoMessage); transport.simulateIncomingMessage(request); // Verify response @@ -179,7 +180,7 @@ void testNotificationHandling() { void testUnknownMethodHandling() { // Simulate incoming request for unknown method McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "unknown.method", - "test-id", null); + MessageId.of("test-id"), null); transport.simulateIncomingMessage(request); // Verify error response diff --git a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index df7ab514f..61dc08597 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; +import io.modelcontextprotocol.spec.McpSchema.MessageId; import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; import net.javacrumbs.jsonunit.core.Option; @@ -240,8 +241,8 @@ void testJSONRPCRequest() throws Exception { Map params = new HashMap<>(); params.put("key", "value"); - McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method_name", 1, - params); + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "method_name", + MessageId.of(1), params); String value = mapper.writeValueAsString(request); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -272,7 +273,8 @@ void testJSONRPCResponse() throws Exception { Map result = new HashMap<>(); result.put("result_key", "result_value"); - McpSchema.JSONRPCResponse response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, 1, result, null); + McpSchema.JSONRPCResponse response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, MessageId.of(1), + result, null); String value = mapper.writeValueAsString(response); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -287,7 +289,8 @@ void testJSONRPCResponseWithError() throws Exception { McpSchema.JSONRPCResponse.JSONRPCError error = new McpSchema.JSONRPCResponse.JSONRPCError( McpSchema.ErrorCodes.INVALID_REQUEST, "Invalid request", null); - McpSchema.JSONRPCResponse response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, 1, null, error); + McpSchema.JSONRPCResponse response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, MessageId.of(1), + null, error); String value = mapper.writeValueAsString(response); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)