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 ead7380f0..1a2b7fba6 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 @@ -72,6 +72,7 @@ * @author Christian Tzolov * @author Alexandros Pappas * @author Dariusz Jędrzejczyk + * @author Yanming Zhou * @see McpServerTransport * @see ServerSentEvent */ @@ -391,14 +392,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, new DefaultMcpTransportContext()); @@ -412,12 +418,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 23fff25b3..3545acd8e 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 @@ -30,6 +30,7 @@ * Implementation of a WebFlux based {@link McpStatelessServerTransport}. * * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ public class WebFluxStatelessServerTransport implements McpStatelessServerTransport { @@ -102,7 +103,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 -> { @@ -121,12 +125,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 963a50249..d6099f268 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 @@ -43,6 +43,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 -> { @@ -258,7 +267,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); @@ -266,7 +278,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) { @@ -292,12 +306,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()) @@ -313,8 +333,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 6e92cf10c..8c4e5bb51 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 @@ -71,6 +71,7 @@ * * @author Christian Tzolov * @author Alexandros Pappas + * @author Yanming Zhou * @see McpServerTransportProvider * @see RouterFunction */ @@ -386,14 +387,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 { @@ -413,11 +420,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 fef1920fc..0d265fa4e 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 @@ -34,6 +34,7 @@ * {@link io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport} * * @author Christian Tzolov + * @author Yanming Zhou */ public class WebMvcStatelessServerTransport implements McpStatelessServerTransport { @@ -106,7 +107,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 { @@ -124,7 +128,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) { @@ -137,22 +143,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 fa51a0130..b0ce9d31c 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 @@ -50,6 +50,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, new DefaultMcpTransportContext()); @@ -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, new DefaultMcpTransportContext()); @@ -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()); } } diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 582120e3f..af0fa0c50 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -63,6 +63,7 @@ * * @author Christian Tzolov * @author Alexandros Pappas + * @author Yanming Zhou * @see McpServerTransportProvider * @see HttpServlet */ @@ -343,7 +344,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - String jsonError = objectMapper.writeValueAsString(new McpError("Session ID missing in message endpoint")); + String jsonError = objectMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session ID missing in message endpoint") + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -356,7 +359,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_NOT_FOUND); - String jsonError = objectMapper.writeValueAsString(new McpError("Session not found: " + sessionId)); + String jsonError = objectMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Session not found: " + sessionId) + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -384,7 +389,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/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 25b003564..fe146ed65 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -33,6 +33,7 @@ * * @author Christian Tzolov * @author Dariusz Jędrzejczyk + * @author Yanming Zhou */ @WebServlet(asyncSupported = true) public class HttpServletStatelessServerTransport extends HttpServlet implements McpStatelessServerTransport { @@ -128,7 +129,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; } @@ -161,7 +164,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) { @@ -174,22 +179,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/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 8b95ec607..0e1f35f0c 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -55,6 +55,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/src/main/java/io/modelcontextprotocol/spec/McpError.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpError.java index 6172d8637..cf1af291d 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpError.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpError.java @@ -4,12 +4,14 @@ package io.modelcontextprotocol.spec; +import com.fasterxml.jackson.annotation.JsonValue; import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.JSONRPCError; import io.modelcontextprotocol.util.Assert; public class McpError extends RuntimeException { - private JSONRPCError jsonRpcError; + @JsonValue + private final JSONRPCError jsonRpcError; public McpError(JSONRPCError jsonRpcError) { super(jsonRpcError.message()); @@ -19,6 +21,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() {