Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
improve the json rpc error content
Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
  • Loading branch information
tzolov committed Sep 29, 2025
commit af2f6e5e51f2a52df55a1a7ffdfbd28cce9f8f83
32 changes: 29 additions & 3 deletions mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ public JSONRPCError getJsonRpcError() {

@Override
public String toString() {
var message = super.toString();
var builder = new StringBuilder(super.toString());
if (jsonRpcError != null) {
return message + jsonRpcError.toString();
builder.append("\n");
builder.append(jsonRpcError.toString());
}
return message;
return builder.toString();
}

public static Builder builder(int errorCode) {
Expand Down Expand Up @@ -87,4 +88,29 @@ public static Throwable findRootCause(Throwable throwable) {
return rootCause;
}

public static String aggregateExceptionMessages(Throwable throwable) {
Assert.notNull(throwable, "throwable must not be null");

StringBuilder messages = new StringBuilder();
Throwable current = throwable;

while (current != null) {
if (messages.length() > 0) {
messages.append("\n Caused by: ");
}

messages.append(current.getClass().getSimpleName());
if (current.getMessage() != null) {
messages.append(": ").append(current.getMessage());
}

if (current.getCause() == current) {
break;
}
current = current.getCause();
}

return messages.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ else if (message instanceof McpSchema.JSONRPCRequest request) {
McpSchema.JSONRPCResponse.JSONRPCError jsonRpcError = (error instanceof McpError mcpError
&& mcpError.getJsonRpcError() != null) ? mcpError.getJsonRpcError()
: new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
McpError.findRootCause(error).getMessage(), null);
error.getMessage(), McpError.aggregateExceptionMessages(error));
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
jsonRpcError);
// TODO: Should the error go to SSE or back as POST return?
Expand Down Expand Up @@ -290,7 +290,7 @@ private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCR
&& mcpError.getJsonRpcError() != null) ? mcpError.getJsonRpcError()
// TODO: add error message through the data field
: new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
McpError.findRootCause(error).getMessage(), null);
error.getMessage(), McpError.aggregateExceptionMessages(error));
return Mono.just(
new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null, jsonRpcError));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public Mono<Void> responseStream(McpSchema.JSONRPCRequest jsonrpcRequest, McpStr
McpSchema.JSONRPCResponse.JSONRPCError jsonRpcError = (e instanceof McpError mcpError
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Propagate existing JSONRPCError instead of wrapp

&& mcpError.getJsonRpcError() != null) ? mcpError.getJsonRpcError()
: new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
McpError.findRootCause(e).getMessage(), null);
e.getMessage(), McpError.aggregateExceptionMessages(e));

var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(),
null, jsonRpcError);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ void testCreateMessageSuccess(String clientType) {
CreateMessageResult.StopReason.STOP_SEQUENCE);
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();

Expand Down Expand Up @@ -224,8 +225,9 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr

// Server

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();

Expand Down Expand Up @@ -300,8 +302,9 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt
CreateMessageResult.StopReason.STOP_SEQUENCE);
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
Expand Down Expand Up @@ -393,8 +396,9 @@ void testCreateElicitationSuccess(String clientType) {
Map.of("message", request.message()));
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
Expand Down Expand Up @@ -448,8 +452,9 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<ElicitResult> resultRef = new AtomicReference<>();

Expand Down Expand Up @@ -520,7 +525,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
};

CallToolResult callResponse = new CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
CallToolResult callResponse = CallToolResult.builder().addContent(new TextContent("CALL RESPONSE")).build();

AtomicReference<ElicitResult> resultRef = new AtomicReference<>();

Expand Down Expand Up @@ -761,7 +766,9 @@ void testToolCallSuccess(String clientType) {
var clientBuilder = clientBuilders.get(clientType);

var responseBodyIsNullOrBlank = new AtomicBoolean(false);
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
var callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue"))
.build();
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand Down Expand Up @@ -832,8 +839,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) {
assertThat(initResult).isNotNull();

// We expect the tool call to fail immediately with the exception raised by
// the offending tool
// instead of getting back a timeout.
// the offending tool instead of getting back a timeout.
assertThatExceptionOfType(McpError.class)
.isThrownBy(() -> mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())))
.withMessageContaining("Timeout on blocking read");
Expand All @@ -853,8 +859,9 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
var transportContextIsEmpty = new AtomicBoolean(false);
var responseBodyIsNullOrBlank = new AtomicBoolean(false);

var expectedCallResponse = new McpSchema.CallToolResult(
List.of(new McpSchema.TextContent("CALL RESPONSE; ctx=value")), null);
var expectedCallResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value"))
.build();
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand All @@ -872,8 +879,9 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
e.printStackTrace();
}

return new McpSchema.CallToolResult(
List.of(new McpSchema.TextContent("CALL RESPONSE; ctx=" + ctxValue)), null);
return McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=" + ctxValue))
.build();
})
.build();

Expand Down Expand Up @@ -906,7 +914,10 @@ void testToolListChangeHandlingSuccess(String clientType) {

var clientBuilder = clientBuilders.get(clientType);

var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
var callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ void testCreateMessageSuccess(String clientType) {
CreateMessageResult.StopReason.STOP_SEQUENCE);
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();

Expand Down Expand Up @@ -228,8 +229,9 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr

// Server

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();

Expand Down Expand Up @@ -304,8 +306,9 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt
CreateMessageResult.StopReason.STOP_SEQUENCE);
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
Expand Down Expand Up @@ -397,8 +400,9 @@ void testCreateElicitationSuccess(String clientType) {
Map.of("message", request.message()));
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
Expand Down Expand Up @@ -452,8 +456,9 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
};

CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
null);
CallToolResult callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

AtomicReference<ElicitResult> resultRef = new AtomicReference<>();

Expand Down Expand Up @@ -524,7 +529,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) {
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
};

CallToolResult callResponse = new CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
CallToolResult callResponse = CallToolResult.builder().addContent(new TextContent("CALL RESPONSE")).build();

AtomicReference<ElicitResult> resultRef = new AtomicReference<>();

Expand Down Expand Up @@ -765,7 +770,9 @@ void testToolCallSuccess(String clientType) {
var clientBuilder = clientBuilders.get(clientType);

var responseBodyIsNullOrBlank = new AtomicBoolean(false);
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
var callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue"))
.build();
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand Down Expand Up @@ -836,8 +843,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) {
assertThat(initResult).isNotNull();

// We expect the tool call to fail immediately with the exception raised by
// the offending tool
// instead of getting back a timeout.
// the offending tool instead of getting back a timeout.
assertThatExceptionOfType(McpError.class)
.isThrownBy(() -> mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())))
.withMessageContaining("Timeout on blocking read");
Expand All @@ -857,8 +863,9 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
var transportContextIsEmpty = new AtomicBoolean(false);
var responseBodyIsNullOrBlank = new AtomicBoolean(false);

var expectedCallResponse = new McpSchema.CallToolResult(
List.of(new McpSchema.TextContent("CALL RESPONSE; ctx=value")), null);
var expectedCallResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value"))
.build();
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand All @@ -876,8 +883,9 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
e.printStackTrace();
}

return new McpSchema.CallToolResult(
List.of(new McpSchema.TextContent("CALL RESPONSE; ctx=" + ctxValue)), null);
return McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=" + ctxValue))
.build();
})
.build();

Expand Down Expand Up @@ -910,7 +918,10 @@ void testToolListChangeHandlingSuccess(String clientType) {

var clientBuilder = clientBuilders.get(clientType);

var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
var callResponse = McpSchema.CallToolResult.builder()
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
.build();

McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
.callHandler((exchange, request) -> {
Expand Down