diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 4739e231a..475bccca9 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -62,6 +62,7 @@ * * @author Christian Tzolov * @author Alexandros Pappas + * @author Yanming Zhou * @see McpServerTransportProvider * @see HttpServlet */ @@ -278,7 +279,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - String jsonError = jsonMapper.writeValueAsString(new McpError("Session ID missing in message endpoint")); + String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID missing in message endpoint") + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -291,7 +294,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_NOT_FOUND); - String jsonError = jsonMapper.writeValueAsString(new McpError("Session not found: " + sessionId)); + String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -318,7 +323,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Error processing message: {}", e.getMessage()); try { - McpError mcpError = new McpError(e.getMessage()); + McpError mcpError = McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message(e.getMessage()) + .build(); response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 40767f416..91ef98835 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -32,6 +32,7 @@ * * @author Christian Tzolov * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ @WebServlet(asyncSupported = true) public class HttpServletStatelessServerTransport extends HttpServlet implements McpStatelessServerTransport { @@ -127,7 +128,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) String accept = request.getHeader(ACCEPT); if (accept == null || !(accept.contains(APPLICATION_JSON) && accept.contains(TEXT_EVENT_STREAM))) { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Both application/json and text/event-stream required in Accept header")); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected application/json and text/event-stream") + .build()); return; } @@ -160,7 +163,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Failed to handle request: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to handle request: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle request: " + e.getMessage()) + .build()); } } else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { @@ -173,22 +178,29 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { catch (Exception e) { logger.error("Failed to handle notification: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to handle notification: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle notification: " + e.getMessage()) + .build()); } } else { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("The server accepts either requests or notifications")); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("The server accepts either requests or notifications") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError("Invalid message format")); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build()); } catch (Exception e) { logger.error("Unexpected error handling message: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Unexpected error: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Unexpected error: " + e.getMessage()) + .build()); } } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 34671c105..9fba8cd04 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -54,6 +54,7 @@ * @author Zachary German * @author Christian Tzolov * @author Dariusz Jędrzejczyk + * @author Yanming Zhou * @see McpStreamableServerTransportProvider * @see HttpServlet */ @@ -261,7 +262,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build()); return; } @@ -400,7 +402,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) && jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) { if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build()); return; } @@ -430,7 +433,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Failed to initialize session: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to initialize session: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to initialize session: " + e.getMessage()) + .build()); return; } } @@ -443,7 +448,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message(combinedMessage).build()); return; } @@ -451,7 +457,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (session == null) { this.responseError(response, HttpServletResponse.SC_NOT_FOUND, - new McpError("Session not found: " + sessionId)); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); return; } @@ -492,20 +500,24 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { } } else { - this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Unknown message type")); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Unknown message type").build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Invalid message format: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR) + .message("Invalid message format: " + e.getMessage()) + .build()); } catch (Exception e) { logger.error("Error handling message: {}", e.getMessage()); try { this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Error processing message: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Error processing message: " + e.getMessage()) + .build()); } catch (IOException ex) { logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); @@ -545,7 +557,9 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response if (request.getHeader(HttpHeaders.MCP_SESSION_ID) == null) { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Session ID required in mcp-session-id header")); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID required in mcp-session-id header") + .build()); return; } @@ -566,7 +580,7 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response logger.error("Failed to delete session {}: {}", sessionId, e.getMessage()); try { this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError(e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } catch (IOException ex) { logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java index 4f717306a..a8379f8b3 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.spec; +import com.fasterxml.jackson.annotation.JsonValue; import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.JSONRPCError; import io.modelcontextprotocol.util.Assert; @@ -20,7 +21,8 @@ public class McpError extends RuntimeException { public static final Function RESOURCE_NOT_FOUND = resourceUri -> new McpError(new JSONRPCError( McpSchema.ErrorCodes.RESOURCE_NOT_FOUND, "Resource not found", Map.of("uri", resourceUri))); - private JSONRPCError jsonRpcError; + @JsonValue + private final JSONRPCError jsonRpcError; public McpError(JSONRPCError jsonRpcError) { super(jsonRpcError.message()); @@ -30,6 +32,7 @@ public McpError(JSONRPCError jsonRpcError) { @Deprecated public McpError(Object error) { super(error.toString()); + this.jsonRpcError = new JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR, error.toString(), null); } public JSONRPCError getJsonRpcError() { diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java index 95355c0f2..fb41ac940 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java @@ -71,6 +71,7 @@ * @author Christian Tzolov * @author Alexandros Pappas * @author Dariusz Jędrzejczyk + * @author Yanming Zhou * @see McpServerTransport * @see ServerSentEvent */ @@ -318,14 +319,19 @@ private Mono handleMessage(ServerRequest request) { } if (request.queryParam("sessionId").isEmpty()) { - return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing in message endpoint")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID missing in message endpoint") + .build()); } McpServerSession session = sessions.get(request.queryParam("sessionId").get()); if (session == null) { return ServerResponse.status(HttpStatus.NOT_FOUND) - .bodyValue(new McpError("Session not found: " + request.queryParam("sessionId").get())); + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + request.queryParam("sessionId").get()) + .build()); } McpTransportContext transportContext = this.contextExtractor.extract(request); @@ -339,12 +345,17 @@ private Mono handleMessage(ServerRequest request) { // - the error is signalled on the SSE connection // return ServerResponse.ok().build(); return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) - .bodyValue(new McpError(error.getMessage())); + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message(error.getMessage()) + .build()); }); } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR) + .message("Invalid message format") + .build()); } }).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)); } diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index 400be341e..5d6893c82 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -29,6 +29,7 @@ * Implementation of a WebFlux based {@link McpStatelessServerTransport}. * * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ public class WebFluxStatelessServerTransport implements McpStatelessServerTransport { @@ -101,7 +102,10 @@ private Mono handlePost(ServerRequest request) { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) && acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) { - return ServerResponse.badRequest().build(); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected application/json and text/event-stream") + .build()); } return request.bodyToMono(String.class).flatMap(body -> { @@ -127,12 +131,17 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { } else { return ServerResponse.badRequest() - .bodyValue(new McpError("The server accepts either requests or notifications")); + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("The server accepts either requests or notifications") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR) + .message("Invalid message format") + .build()); } }).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)); } diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java index 144a3ce02..294a90da6 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java @@ -42,6 +42,7 @@ * Implementation of a WebFlux based {@link McpStreamableServerTransportProvider}. * * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ public class WebFluxStreamableServerTransportProvider implements McpStreamableServerTransportProvider { @@ -171,12 +172,17 @@ private Mono handleGet(ServerRequest request) { return Mono.defer(() -> { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM)) { - return ServerResponse.badRequest().build(); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected text/event-stream") + .build()); } if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { - return ServerResponse.badRequest().build(); // TODO: say we need a session - // id + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Missing header " + HttpHeaders.MCP_SESSION_ID) + .build()); } String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); @@ -226,7 +232,10 @@ private Mono handlePost(ServerRequest request) { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) && acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) { - return ServerResponse.badRequest().build(); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected application/json and text/event-stream") + .build()); } return request.bodyToMono(String.class).flatMap(body -> { @@ -259,7 +268,10 @@ private Mono handlePost(ServerRequest request) { } if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { - return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID missing") + .build()); } String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); @@ -267,7 +279,9 @@ private Mono handlePost(ServerRequest request) { if (session == null) { return ServerResponse.status(HttpStatus.NOT_FOUND) - .bodyValue(new McpError("Session not found: " + sessionId)); + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); } if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) { @@ -293,12 +307,18 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { ServerSentEvent.class); } else { - return ServerResponse.badRequest().bodyValue(new McpError("Unknown message type")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Unknown message type") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR) + .message("Invalid message format") + .build()); } }) .switchIfEmpty(ServerResponse.badRequest().build()) @@ -314,8 +334,10 @@ private Mono handleDelete(ServerRequest request) { return Mono.defer(() -> { if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { - return ServerResponse.badRequest().build(); // TODO: say we need a session - // id + return ServerResponse.badRequest() + .bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Missing header " + HttpHeaders.MCP_SESSION_ID) + .build()); } if (this.disallowDelete) { diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java index 0b71ddc1f..8e4035ae4 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java @@ -70,6 +70,7 @@ * * @author Christian Tzolov * @author Alexandros Pappas + * @author Yanming Zhou * @see McpServerTransportProvider * @see RouterFunction */ @@ -309,14 +310,20 @@ private ServerResponse handleMessage(ServerRequest request) { } if (request.param("sessionId").isEmpty()) { - return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint")); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID missing in message endpoint") + .build()); } String sessionId = request.param("sessionId").get(); McpServerSession session = sessions.get(sessionId); if (session == null) { - return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId)); + return ServerResponse.status(HttpStatus.NOT_FOUND) + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); } try { @@ -335,11 +342,13 @@ private ServerResponse handleMessage(ServerRequest request) { } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().body(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build()); } catch (Exception e) { logger.error("Error handling message: {}", e.getMessage()); - return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage())); + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } } diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index 4223084ff..a30c5a921 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -33,6 +33,7 @@ * {@link io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport} * * @author Christian Tzolov + * @author Yanming Zhou */ public class WebMvcStatelessServerTransport implements McpStatelessServerTransport { @@ -105,7 +106,10 @@ private ServerResponse handlePost(ServerRequest request) { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) && acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) { - return ServerResponse.badRequest().build(); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected application/json and text/event-stream") + .build()); } try { @@ -123,7 +127,9 @@ private ServerResponse handlePost(ServerRequest request) { catch (Exception e) { logger.error("Failed to handle request: {}", e.getMessage()); return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new McpError("Failed to handle request: " + e.getMessage())); + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle request: " + e.getMessage()) + .build()); } } else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { @@ -136,22 +142,29 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { catch (Exception e) { logger.error("Failed to handle notification: {}", e.getMessage()); return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new McpError("Failed to handle notification: " + e.getMessage())); + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle notification: " + e.getMessage()) + .build()); } } else { return ServerResponse.badRequest() - .body(new McpError("The server accepts either requests or notifications")); + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("The server accepts either requests or notifications") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().body(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build()); } catch (Exception e) { logger.error("Unexpected error handling message: {}", e.getMessage()); return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new McpError("Unexpected error: " + e.getMessage())); + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Unexpected error: " + e.getMessage()) + .build()); } } diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java index d85046a67..d3c6bf508 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java @@ -49,6 +49,7 @@ * * @author Christian Tzolov * @author Dariusz Jędrzejczyk + * @author Yanming Zhou * @see McpStreamableServerTransportProvider * @see RouterFunction */ @@ -235,7 +236,7 @@ private ServerResponse handleGet(ServerRequest request) { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM)) { - return ServerResponse.badRequest().body("Invalid Accept header. Expected TEXT_EVENT_STREAM"); + return ServerResponse.badRequest().body("Invalid Accept header. Expected text/event-stream"); } McpTransportContext transportContext = this.contextExtractor.extract(request); @@ -319,7 +320,9 @@ private ServerResponse handlePost(ServerRequest request) { if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM) || !acceptHeaders.contains(MediaType.APPLICATION_JSON)) { return ServerResponse.badRequest() - .body(new McpError("Invalid Accept headers. Expected TEXT_EVENT_STREAM and APPLICATION_JSON")); + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid Accept headers. Expected application/json and text/event-stream") + .build()); } McpTransportContext transportContext = this.contextExtractor.extract(request); @@ -349,13 +352,15 @@ private ServerResponse handlePost(ServerRequest request) { } catch (Exception e) { logger.error("Failed to initialize session: {}", e.getMessage()); - return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage())); + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } } // Handle other messages that require a session if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { - return ServerResponse.badRequest().body(new McpError("Session ID missing")); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Session ID missing").build()); } String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); @@ -363,7 +368,9 @@ private ServerResponse handlePost(ServerRequest request) { if (session == null) { return ServerResponse.status(HttpStatus.NOT_FOUND) - .body(new McpError("Session not found: " + sessionId)); + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); } if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) { @@ -404,16 +411,20 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { } else { return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new McpError("Unknown message type")); + .body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Unknown message type") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - return ServerResponse.badRequest().body(new McpError("Invalid message format")); + return ServerResponse.badRequest() + .body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build()); } catch (Exception e) { logger.error("Error handling message: {}", e.getMessage()); - return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage())); + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } } @@ -451,7 +462,8 @@ private ServerResponse handleDelete(ServerRequest request) { } catch (Exception e) { logger.error("Failed to delete session {}: {}", sessionId, e.getMessage()); - return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage())); + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } }