Skip to content

Commit 4d64df7

Browse files
committed
Produce JSONRPCError for error response
1. Add `@JsonValue` to serialize JSONRPCError instead of McpError itself. 2. Improve deprecated constructor `McpError(Object error)` to create JSONRPCError instance for serializing. 3. Add missing McpError for bad request. 4. Use `McpError.builder()` instead of deprecated `new McpError(Object)` Signed-off-by: Yanming Zhou <[email protected]>
1 parent a0afdcd commit 4d64df7

File tree

10 files changed

+169
-57
lines changed

10 files changed

+169
-57
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
*
6363
* @author Christian Tzolov
6464
* @author Alexandros Pappas
65+
* @author Yanming Zhou
6566
* @see McpServerTransportProvider
6667
* @see HttpServlet
6768
*/
@@ -278,7 +279,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
278279
response.setContentType(APPLICATION_JSON);
279280
response.setCharacterEncoding(UTF_8);
280281
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
281-
String jsonError = jsonMapper.writeValueAsString(new McpError("Session ID missing in message endpoint"));
282+
String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
283+
.message("Session ID missing in message endpoint")
284+
.build());
282285
PrintWriter writer = response.getWriter();
283286
writer.write(jsonError);
284287
writer.flush();
@@ -291,7 +294,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
291294
response.setContentType(APPLICATION_JSON);
292295
response.setCharacterEncoding(UTF_8);
293296
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
294-
String jsonError = jsonMapper.writeValueAsString(new McpError("Session not found: " + sessionId));
297+
String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
298+
.message("Session not found: " + sessionId)
299+
.build());
295300
PrintWriter writer = response.getWriter();
296301
writer.write(jsonError);
297302
writer.flush();
@@ -318,7 +323,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
318323
catch (Exception e) {
319324
logger.error("Error processing message: {}", e.getMessage());
320325
try {
321-
McpError mcpError = new McpError(e.getMessage());
326+
McpError mcpError = McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
327+
.message(e.getMessage())
328+
.build();
322329
response.setContentType(APPLICATION_JSON);
323330
response.setCharacterEncoding(UTF_8);
324331
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*
3333
* @author Christian Tzolov
3434
* @author Dariusz Jędrzejczyk
35+
* @author Yanming Zhou
3536
*/
3637
@WebServlet(asyncSupported = true)
3738
public class HttpServletStatelessServerTransport extends HttpServlet implements McpStatelessServerTransport {
@@ -127,7 +128,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
127128
String accept = request.getHeader(ACCEPT);
128129
if (accept == null || !(accept.contains(APPLICATION_JSON) && accept.contains(TEXT_EVENT_STREAM))) {
129130
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
130-
new McpError("Both application/json and text/event-stream required in Accept header"));
131+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
132+
.message("Invalid Accept headers. Expected application/json and text/event-stream")
133+
.build());
131134
return;
132135
}
133136

@@ -160,7 +163,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
160163
catch (Exception e) {
161164
logger.error("Failed to handle request: {}", e.getMessage());
162165
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
163-
new McpError("Failed to handle request: " + e.getMessage()));
166+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
167+
.message("Failed to handle request: " + e.getMessage())
168+
.build());
164169
}
165170
}
166171
else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
@@ -173,22 +178,29 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
173178
catch (Exception e) {
174179
logger.error("Failed to handle notification: {}", e.getMessage());
175180
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
176-
new McpError("Failed to handle notification: " + e.getMessage()));
181+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
182+
.message("Failed to handle notification: " + e.getMessage())
183+
.build());
177184
}
178185
}
179186
else {
180187
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
181-
new McpError("The server accepts either requests or notifications"));
188+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
189+
.message("The server accepts either requests or notifications")
190+
.build());
182191
}
183192
}
184193
catch (IllegalArgumentException | IOException e) {
185194
logger.error("Failed to deserialize message: {}", e.getMessage());
186-
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError("Invalid message format"));
195+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
196+
McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build());
187197
}
188198
catch (Exception e) {
189199
logger.error("Unexpected error handling message: {}", e.getMessage());
190200
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
191-
new McpError("Unexpected error: " + e.getMessage()));
201+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
202+
.message("Unexpected error: " + e.getMessage())
203+
.build());
192204
}
193205
}
194206

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* @author Zachary German
5555
* @author Christian Tzolov
5656
* @author Dariusz Jędrzejczyk
57+
* @author Yanming Zhou
5758
* @see McpStreamableServerTransportProvider
5859
* @see HttpServlet
5960
*/
@@ -261,7 +262,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
261262

262263
if (!badRequestErrors.isEmpty()) {
263264
String combinedMessage = String.join("; ", badRequestErrors);
264-
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage));
265+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
266+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build());
265267
return;
266268
}
267269

@@ -400,7 +402,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
400402
&& jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) {
401403
if (!badRequestErrors.isEmpty()) {
402404
String combinedMessage = String.join("; ", badRequestErrors);
403-
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage));
405+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
406+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build());
404407
return;
405408
}
406409

@@ -430,7 +433,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
430433
catch (Exception e) {
431434
logger.error("Failed to initialize session: {}", e.getMessage());
432435
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
433-
new McpError("Failed to initialize session: " + e.getMessage()));
436+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
437+
.message("Failed to initialize session: " + e.getMessage())
438+
.build());
434439
return;
435440
}
436441
}
@@ -443,15 +448,18 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
443448

444449
if (!badRequestErrors.isEmpty()) {
445450
String combinedMessage = String.join("; ", badRequestErrors);
446-
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage));
451+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
452+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build());
447453
return;
448454
}
449455

450456
McpStreamableServerSession session = this.sessions.get(sessionId);
451457

452458
if (session == null) {
453459
this.responseError(response, HttpServletResponse.SC_NOT_FOUND,
454-
new McpError("Session not found: " + sessionId));
460+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
461+
.message("Session not found: " + sessionId)
462+
.build());
455463
return;
456464
}
457465

@@ -492,20 +500,24 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
492500
}
493501
}
494502
else {
495-
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
496-
new McpError("Unknown message type"));
503+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
504+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Unknown message type").build());
497505
}
498506
}
499507
catch (IllegalArgumentException | IOException e) {
500508
logger.error("Failed to deserialize message: {}", e.getMessage());
501509
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
502-
new McpError("Invalid message format: " + e.getMessage()));
510+
McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
511+
.message("Invalid message format: " + e.getMessage())
512+
.build());
503513
}
504514
catch (Exception e) {
505515
logger.error("Error handling message: {}", e.getMessage());
506516
try {
507517
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
508-
new McpError("Error processing message: " + e.getMessage()));
518+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
519+
.message("Error processing message: " + e.getMessage())
520+
.build());
509521
}
510522
catch (IOException ex) {
511523
logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage());
@@ -545,7 +557,9 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response
545557

546558
if (request.getHeader(HttpHeaders.MCP_SESSION_ID) == null) {
547559
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
548-
new McpError("Session ID required in mcp-session-id header"));
560+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
561+
.message("Session ID required in mcp-session-id header")
562+
.build());
549563
return;
550564
}
551565

@@ -566,7 +580,7 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response
566580
logger.error("Failed to delete session {}: {}", sessionId, e.getMessage());
567581
try {
568582
this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
569-
new McpError(e.getMessage()));
583+
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
570584
}
571585
catch (IOException ex) {
572586
logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage());

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
package io.modelcontextprotocol.spec;
66

7+
import com.fasterxml.jackson.annotation.JsonValue;
78
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.JSONRPCError;
89
import io.modelcontextprotocol.util.Assert;
910

1011
public class McpError extends RuntimeException {
1112

12-
private JSONRPCError jsonRpcError;
13+
@JsonValue
14+
private final JSONRPCError jsonRpcError;
1315

1416
public McpError(JSONRPCError jsonRpcError) {
1517
super(jsonRpcError.message());
@@ -19,6 +21,7 @@ public McpError(JSONRPCError jsonRpcError) {
1921
@Deprecated
2022
public McpError(Object error) {
2123
super(error.toString());
24+
this.jsonRpcError = new JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR, error.toString(), null);
2225
}
2326

2427
public JSONRPCError getJsonRpcError() {

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
* @author Christian Tzolov
7272
* @author Alexandros Pappas
7373
* @author Dariusz Jędrzejczyk
74+
* @author Yanming Zhou
7475
* @see McpServerTransport
7576
* @see ServerSentEvent
7677
*/
@@ -318,14 +319,19 @@ private Mono<ServerResponse> handleMessage(ServerRequest request) {
318319
}
319320

320321
if (request.queryParam("sessionId").isEmpty()) {
321-
return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing in message endpoint"));
322+
return ServerResponse.badRequest()
323+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
324+
.message("Session ID missing in message endpoint")
325+
.build());
322326
}
323327

324328
McpServerSession session = sessions.get(request.queryParam("sessionId").get());
325329

326330
if (session == null) {
327331
return ServerResponse.status(HttpStatus.NOT_FOUND)
328-
.bodyValue(new McpError("Session not found: " + request.queryParam("sessionId").get()));
332+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
333+
.message("Session not found: " + request.queryParam("sessionId").get())
334+
.build());
329335
}
330336

331337
McpTransportContext transportContext = this.contextExtractor.extract(request);
@@ -339,12 +345,17 @@ private Mono<ServerResponse> handleMessage(ServerRequest request) {
339345
// - the error is signalled on the SSE connection
340346
// return ServerResponse.ok().build();
341347
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
342-
.bodyValue(new McpError(error.getMessage()));
348+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
349+
.message(error.getMessage())
350+
.build());
343351
});
344352
}
345353
catch (IllegalArgumentException | IOException e) {
346354
logger.error("Failed to deserialize message: {}", e.getMessage());
347-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
355+
return ServerResponse.badRequest()
356+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
357+
.message("Invalid message format")
358+
.build());
348359
}
349360
}).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext));
350361
}

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* Implementation of a WebFlux based {@link McpStatelessServerTransport}.
3030
*
3131
* @author Dariusz Jędrzejczyk
32+
* @author Yanming Zhou
3233
*/
3334
public class WebFluxStatelessServerTransport implements McpStatelessServerTransport {
3435

@@ -101,7 +102,10 @@ private Mono<ServerResponse> handlePost(ServerRequest request) {
101102
List<MediaType> acceptHeaders = request.headers().asHttpHeaders().getAccept();
102103
if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON)
103104
&& acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) {
104-
return ServerResponse.badRequest().build();
105+
return ServerResponse.badRequest()
106+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
107+
.message("Invalid Accept headers. Expected application/json and text/event-stream")
108+
.build());
105109
}
106110

107111
return request.bodyToMono(String.class).<ServerResponse>flatMap(body -> {
@@ -127,12 +131,17 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
127131
}
128132
else {
129133
return ServerResponse.badRequest()
130-
.bodyValue(new McpError("The server accepts either requests or notifications"));
134+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
135+
.message("The server accepts either requests or notifications")
136+
.build());
131137
}
132138
}
133139
catch (IllegalArgumentException | IOException e) {
134140
logger.error("Failed to deserialize message: {}", e.getMessage());
135-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
141+
return ServerResponse.badRequest()
142+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
143+
.message("Invalid message format")
144+
.build());
136145
}
137146
}).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext));
138147
}

0 commit comments

Comments
 (0)