From bb8b36e3c559b4c05d94856ac860f65bf94c5a6c Mon Sep 17 00:00:00 2001 From: vinsguru Date: Thu, 25 Dec 2025 18:34:58 +0530 Subject: [PATCH] Fix McpError handling when prompt methods are invoked via reflection Unwrap InvocationTargetException to correctly detect and propagate McpError thrown by prompt implementations. This preserves JSON-RPC error code, message, and data instead of overriding them with a generic error. --- .../prompt/SyncMcpPromptMethodCallback.java | 6 +++- .../SyncMcpPromptMethodCallbackTests.java | 29 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallback.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallback.java index 8d7a087..9b4ed43 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallback.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallback.java @@ -4,6 +4,7 @@ package org.springaicommunity.mcp.method.prompt; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.function.BiFunction; @@ -107,7 +108,10 @@ public GetPromptResult apply(McpSyncServerExchange exchange, GetPromptRequest re return promptResult; } catch (Exception e) { - if (e instanceof McpError mcpError && mcpError.getJsonRpcError() != null) { + + Throwable cause = e instanceof InvocationTargetException ite ? ite.getTargetException() : e; + + if (cause instanceof McpError mcpError && mcpError.getJsonRpcError() != null) { throw mcpError; } diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallbackTests.java index 359b553..1e3d681 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallbackTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/prompt/SyncMcpPromptMethodCallbackTests.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.function.BiFunction; +import io.modelcontextprotocol.spec.McpSchema; import org.junit.jupiter.api.Test; import org.springaicommunity.mcp.annotation.McpArg; import org.springaicommunity.mcp.annotation.McpMeta; @@ -44,7 +45,10 @@ private static class TestPromptProvider { @McpPrompt(name = "failing-prompt", description = "A prompt that throws an exception") public GetPromptResult getFailingPrompt(GetPromptRequest request) { - throw new RuntimeException("Test exception"); + if (request.arguments().get("type").toString().equals("runTimeException")) { + throw new RuntimeException("Test exception"); + } + throw McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message("Internal error").build(); } @McpPrompt(name = "greeting", description = "A simple greeting prompt") @@ -215,14 +219,27 @@ public void testMethodInvocationError() throws Exception { .build(); McpSyncServerExchange exchange = mock(McpSyncServerExchange.class); - Map args = new HashMap<>(); - args.put("name", "John"); - GetPromptRequest request = new GetPromptRequest("failing-prompt", args); + GetPromptRequest runTimeExceptionRequest = new GetPromptRequest("failing-prompt", + Map.of("type", "runTimeException")); // The new error handling should throw McpError instead of // McpPromptMethodException - assertThatThrownBy(() -> callback.apply(exchange, request)).isInstanceOf(McpError.class) - .hasMessageContaining("Error invoking prompt method"); + assertThatThrownBy(() -> callback.apply(exchange, runTimeExceptionRequest)).isInstanceOf(McpError.class) + .satisfies(t -> { + McpError e = (McpError) t; + assertThat(e.getJsonRpcError().code()).isEqualTo(McpSchema.ErrorCodes.INVALID_PARAMS); + assertThat(e.getJsonRpcError().message()).contains("Error invoking prompt method"); + }); + + // McpError should be preserved + GetPromptRequest mcpErrorRequest = new GetPromptRequest("failing-prompt", Map.of("type", "mcpError")); + + assertThatThrownBy(() -> callback.apply(exchange, mcpErrorRequest)).isInstanceOf(McpError.class) + .satisfies(t -> { + McpError e = (McpError) t; + assertThat(e.getJsonRpcError().code()).isEqualTo(McpSchema.ErrorCodes.INTERNAL_ERROR); + assertThat(e.getJsonRpcError().message()).isEqualTo("Internal error"); + }); } @Test