From 11ae729d241ed654434503a1469af0d39a0b39d9 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Thu, 21 Aug 2025 17:10:33 +0800 Subject: [PATCH] Fix NPE caused by response without id Close GH-506 Signed-off-by: Yanming Zhou --- .../spec/McpClientSession.java | 16 ++++++---- .../modelcontextprotocol/spec/McpSchema.java | 4 ++- .../spec/McpServerSession.java | 15 ++++++---- .../spec/McpStreamableServerSession.java | 29 ++++++++++++------- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java index f7db3d7aa..5f60b74fb 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java @@ -35,6 +35,7 @@ * * @author Christian Tzolov * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ public class McpClientSession implements McpSession { @@ -146,13 +147,18 @@ private void dismissPendingResponses() { private void handle(McpSchema.JSONRPCMessage message) { if (message instanceof McpSchema.JSONRPCResponse response) { - logger.debug("Received Response: {}", response); - var sink = pendingResponses.remove(response.id()); - if (sink == null) { - logger.warn("Unexpected response for unknown id {}", response.id()); + logger.debug("Received response: {}", response); + if (response.id() != null) { + var sink = pendingResponses.remove(response.id()); + if (sink == null) { + logger.warn("Unexpected response for unknown id {}", response.id()); + } + else { + sink.success(response); + } } else { - sink.success(response); + logger.debug("Discarded response without id"); } } else if (message instanceof McpSchema.JSONRPCRequest request) { diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 8a109a8d1..fcc924328 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.util.Assert; +import reactor.util.annotation.Nullable; /** * Based on the JSON-RPC 2.0 @@ -36,6 +37,7 @@ * @author Luca Chang * @author Surbhi Bansal * @author Anurag Pant + * @author Yanming Zhou */ public final class McpSchema { @@ -281,7 +283,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") @Nullable Object 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 62985dc17..5abf431d1 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java @@ -202,13 +202,18 @@ public Mono handle(McpSchema.JSONRPCMessage message) { // TODO handle errors for communication to without initialization happening // first if (message instanceof McpSchema.JSONRPCResponse response) { - logger.debug("Received Response: {}", response); - var sink = pendingResponses.remove(response.id()); - if (sink == null) { - logger.warn("Unexpected response for unknown id {}", response.id()); + logger.debug("Received response: {}", response); + if (response.id() != null) { + var sink = pendingResponses.remove(response.id()); + if (sink == null) { + logger.warn("Unexpected response for unknown id {}", response.id()); + } + else { + sink.success(response); + } } else { - sink.success(response); + logger.debug("Discarded response without id"); } return Mono.empty(); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java index ef7967c1e..e22c5afc2 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java @@ -33,6 +33,7 @@ * capability without the insight into the transport-specific details of HTTP handling. * * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ public class McpStreamableServerSession implements McpLoggableSession { @@ -214,19 +215,25 @@ public Mono accept(McpSchema.JSONRPCNotification notification) { */ public Mono accept(McpSchema.JSONRPCResponse response) { return Mono.defer(() -> { - var stream = this.requestIdToStream.get(response.id()); - if (stream == null) { - return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO - // JSONize - } - // TODO: encapsulate this inside the stream itself - var sink = stream.pendingResponses.remove(response.id()); - if (sink == null) { - return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO - // JSONize + logger.debug("Received response: {}", response); + if (response.id() != null) { + var stream = this.requestIdToStream.get(response.id()); + if (stream == null) { + return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO + // JSONize + } + // TODO: encapsulate this inside the stream itself + var sink = stream.pendingResponses.remove(response.id()); + if (sink == null) { + return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO + // JSONize + } + else { + sink.success(response); + } } else { - sink.success(response); + logger.debug("Discarded response without id"); } return Mono.empty(); });