Skip to content

Commit 8d6c313

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 <zhouyanming@gmail.com>
1 parent b4fef52 commit 8d6c313

File tree

10 files changed

+169
-57
lines changed

10 files changed

+169
-57
lines changed

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
@@ -72,6 +72,7 @@
7272
* @author Christian Tzolov
7373
* @author Alexandros Pappas
7474
* @author Dariusz Jędrzejczyk
75+
* @author Yanming Zhou
7576
* @see McpServerTransport
7677
* @see ServerSentEvent
7778
*/
@@ -391,14 +392,19 @@ private Mono<ServerResponse> handleMessage(ServerRequest request) {
391392
}
392393

393394
if (request.queryParam("sessionId").isEmpty()) {
394-
return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing in message endpoint"));
395+
return ServerResponse.badRequest()
396+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
397+
.message("Session ID missing in message endpoint")
398+
.build());
395399
}
396400

397401
McpServerSession session = sessions.get(request.queryParam("sessionId").get());
398402

399403
if (session == null) {
400404
return ServerResponse.status(HttpStatus.NOT_FOUND)
401-
.bodyValue(new McpError("Session not found: " + request.queryParam("sessionId").get()));
405+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
406+
.message("Session not found: " + request.queryParam("sessionId").get())
407+
.build());
402408
}
403409

404410
McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext());
@@ -412,12 +418,17 @@ private Mono<ServerResponse> handleMessage(ServerRequest request) {
412418
// - the error is signalled on the SSE connection
413419
// return ServerResponse.ok().build();
414420
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
415-
.bodyValue(new McpError(error.getMessage()));
421+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
422+
.message(error.getMessage())
423+
.build());
416424
});
417425
}
418426
catch (IllegalArgumentException | IOException e) {
419427
logger.error("Failed to deserialize message: {}", e.getMessage());
420-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
428+
return ServerResponse.badRequest()
429+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
430+
.message("Invalid message format")
431+
.build());
421432
}
422433
}).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext));
423434
}

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
@@ -30,6 +30,7 @@
3030
* Implementation of a WebFlux based {@link McpStatelessServerTransport}.
3131
*
3232
* @author Dariusz Jędrzejczyk
33+
* @author Yanming Zhou
3334
*/
3435
public class WebFluxStatelessServerTransport implements McpStatelessServerTransport {
3536

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

108112
return request.bodyToMono(String.class).<ServerResponse>flatMap(body -> {
@@ -121,12 +125,17 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
121125
}
122126
else {
123127
return ServerResponse.badRequest()
124-
.bodyValue(new McpError("The server accepts either requests or notifications"));
128+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
129+
.message("The server accepts either requests or notifications")
130+
.build());
125131
}
126132
}
127133
catch (IllegalArgumentException | IOException e) {
128134
logger.error("Failed to deserialize message: {}", e.getMessage());
129-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
135+
return ServerResponse.badRequest()
136+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
137+
.message("Invalid message format")
138+
.build());
130139
}
131140
}).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext));
132141
}

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

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* Implementation of a WebFlux based {@link McpStreamableServerTransportProvider}.
4444
*
4545
* @author Dariusz Jędrzejczyk
46+
* @author Yanming Zhou
4647
*/
4748
public class WebFluxStreamableServerTransportProvider implements McpStreamableServerTransportProvider {
4849

@@ -171,12 +172,17 @@ private Mono<ServerResponse> handleGet(ServerRequest request) {
171172
return Mono.defer(() -> {
172173
List<MediaType> acceptHeaders = request.headers().asHttpHeaders().getAccept();
173174
if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM)) {
174-
return ServerResponse.badRequest().build();
175+
return ServerResponse.badRequest()
176+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
177+
.message("Invalid Accept headers. Expected text/event-stream")
178+
.build());
175179
}
176180

177181
if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) {
178-
return ServerResponse.badRequest().build(); // TODO: say we need a session
179-
// id
182+
return ServerResponse.badRequest()
183+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
184+
.message("Missing header " + HttpHeaders.MCP_SESSION_ID)
185+
.build());
180186
}
181187

182188
String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID);
@@ -226,7 +232,10 @@ private Mono<ServerResponse> handlePost(ServerRequest request) {
226232
List<MediaType> acceptHeaders = request.headers().asHttpHeaders().getAccept();
227233
if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON)
228234
&& acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) {
229-
return ServerResponse.badRequest().build();
235+
return ServerResponse.badRequest()
236+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
237+
.message("Invalid Accept headers. Expected application/json and text/event-stream")
238+
.build());
230239
}
231240

232241
return request.bodyToMono(String.class).<ServerResponse>flatMap(body -> {
@@ -258,15 +267,20 @@ private Mono<ServerResponse> handlePost(ServerRequest request) {
258267
}
259268

260269
if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) {
261-
return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing"));
270+
return ServerResponse.badRequest()
271+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
272+
.message("Session ID missing")
273+
.build());
262274
}
263275

264276
String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID);
265277
McpStreamableServerSession session = sessions.get(sessionId);
266278

267279
if (session == null) {
268280
return ServerResponse.status(HttpStatus.NOT_FOUND)
269-
.bodyValue(new McpError("Session not found: " + sessionId));
281+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
282+
.message("Session not found: " + sessionId)
283+
.build());
270284
}
271285

272286
if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) {
@@ -292,12 +306,18 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
292306
ServerSentEvent.class);
293307
}
294308
else {
295-
return ServerResponse.badRequest().bodyValue(new McpError("Unknown message type"));
309+
return ServerResponse.badRequest()
310+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
311+
.message("Unknown message type")
312+
.build());
296313
}
297314
}
298315
catch (IllegalArgumentException | IOException e) {
299316
logger.error("Failed to deserialize message: {}", e.getMessage());
300-
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
317+
return ServerResponse.badRequest()
318+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR)
319+
.message("Invalid message format")
320+
.build());
301321
}
302322
})
303323
.switchIfEmpty(ServerResponse.badRequest().build())
@@ -313,8 +333,10 @@ private Mono<ServerResponse> handleDelete(ServerRequest request) {
313333

314334
return Mono.defer(() -> {
315335
if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) {
316-
return ServerResponse.badRequest().build(); // TODO: say we need a session
317-
// id
336+
return ServerResponse.badRequest()
337+
.bodyValue(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
338+
.message("Missing header " + HttpHeaders.MCP_SESSION_ID)
339+
.build());
318340
}
319341

320342
if (this.disallowDelete) {

mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
*
7272
* @author Christian Tzolov
7373
* @author Alexandros Pappas
74+
* @author Yanming Zhou
7475
* @see McpServerTransportProvider
7576
* @see RouterFunction
7677
*/
@@ -386,14 +387,20 @@ private ServerResponse handleMessage(ServerRequest request) {
386387
}
387388

388389
if (request.param("sessionId").isEmpty()) {
389-
return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint"));
390+
return ServerResponse.badRequest()
391+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
392+
.message("Session ID missing in message endpoint")
393+
.build());
390394
}
391395

392396
String sessionId = request.param("sessionId").get();
393397
McpServerSession session = sessions.get(sessionId);
394398

395399
if (session == null) {
396-
return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId));
400+
return ServerResponse.status(HttpStatus.NOT_FOUND)
401+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
402+
.message("Session not found: " + sessionId)
403+
.build());
397404
}
398405

399406
try {
@@ -413,11 +420,13 @@ private ServerResponse handleMessage(ServerRequest request) {
413420
}
414421
catch (IllegalArgumentException | IOException e) {
415422
logger.error("Failed to deserialize message: {}", e.getMessage());
416-
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
423+
return ServerResponse.badRequest()
424+
.body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build());
417425
}
418426
catch (Exception e) {
419427
logger.error("Error handling message: {}", e.getMessage());
420-
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
428+
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
429+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
421430
}
422431
}
423432

mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* {@link io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport}
3535
*
3636
* @author Christian Tzolov
37+
* @author Yanming Zhou
3738
*/
3839
public class WebMvcStatelessServerTransport implements McpStatelessServerTransport {
3940

@@ -106,7 +107,10 @@ private ServerResponse handlePost(ServerRequest request) {
106107
List<MediaType> acceptHeaders = request.headers().asHttpHeaders().getAccept();
107108
if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON)
108109
&& acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM))) {
109-
return ServerResponse.badRequest().build();
110+
return ServerResponse.badRequest()
111+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
112+
.message("Invalid Accept headers. Expected application/json and text/event-stream")
113+
.build());
110114
}
111115

112116
try {
@@ -124,7 +128,9 @@ private ServerResponse handlePost(ServerRequest request) {
124128
catch (Exception e) {
125129
logger.error("Failed to handle request: {}", e.getMessage());
126130
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
127-
.body(new McpError("Failed to handle request: " + e.getMessage()));
131+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
132+
.message("Failed to handle request: " + e.getMessage())
133+
.build());
128134
}
129135
}
130136
else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
@@ -137,22 +143,29 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
137143
catch (Exception e) {
138144
logger.error("Failed to handle notification: {}", e.getMessage());
139145
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
140-
.body(new McpError("Failed to handle notification: " + e.getMessage()));
146+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
147+
.message("Failed to handle notification: " + e.getMessage())
148+
.build());
141149
}
142150
}
143151
else {
144152
return ServerResponse.badRequest()
145-
.body(new McpError("The server accepts either requests or notifications"));
153+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
154+
.message("The server accepts either requests or notifications")
155+
.build());
146156
}
147157
}
148158
catch (IllegalArgumentException | IOException e) {
149159
logger.error("Failed to deserialize message: {}", e.getMessage());
150-
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
160+
return ServerResponse.badRequest()
161+
.body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build());
151162
}
152163
catch (Exception e) {
153164
logger.error("Unexpected error handling message: {}", e.getMessage());
154165
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
155-
.body(new McpError("Unexpected error: " + e.getMessage()));
166+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR)
167+
.message("Unexpected error: " + e.getMessage())
168+
.build());
156169
}
157170
}
158171

mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
*
5151
* @author Christian Tzolov
5252
* @author Dariusz Jędrzejczyk
53+
* @author Yanming Zhou
5354
* @see McpStreamableServerTransportProvider
5455
* @see RouterFunction
5556
*/
@@ -235,7 +236,7 @@ private ServerResponse handleGet(ServerRequest request) {
235236

236237
List<MediaType> acceptHeaders = request.headers().asHttpHeaders().getAccept();
237238
if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM)) {
238-
return ServerResponse.badRequest().body("Invalid Accept header. Expected TEXT_EVENT_STREAM");
239+
return ServerResponse.badRequest().body("Invalid Accept header. Expected text/event-stream");
239240
}
240241

241242
McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext());
@@ -319,7 +320,9 @@ private ServerResponse handlePost(ServerRequest request) {
319320
if (!acceptHeaders.contains(MediaType.TEXT_EVENT_STREAM)
320321
|| !acceptHeaders.contains(MediaType.APPLICATION_JSON)) {
321322
return ServerResponse.badRequest()
322-
.body(new McpError("Invalid Accept headers. Expected TEXT_EVENT_STREAM and APPLICATION_JSON"));
323+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
324+
.message("Invalid Accept headers. Expected application/json and text/event-stream")
325+
.build());
323326
}
324327

325328
McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext());
@@ -349,21 +352,25 @@ private ServerResponse handlePost(ServerRequest request) {
349352
}
350353
catch (Exception e) {
351354
logger.error("Failed to initialize session: {}", e.getMessage());
352-
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
355+
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
356+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
353357
}
354358
}
355359

356360
// Handle other messages that require a session
357361
if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) {
358-
return ServerResponse.badRequest().body(new McpError("Session ID missing"));
362+
return ServerResponse.badRequest()
363+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Session ID missing").build());
359364
}
360365

361366
String sessionId = request.headers().asHttpHeaders().getFirst(HttpHeaders.MCP_SESSION_ID);
362367
McpStreamableServerSession session = this.sessions.get(sessionId);
363368

364369
if (session == null) {
365370
return ServerResponse.status(HttpStatus.NOT_FOUND)
366-
.body(new McpError("Session not found: " + sessionId));
371+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
372+
.message("Session not found: " + sessionId)
373+
.build());
367374
}
368375

369376
if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) {
@@ -404,16 +411,20 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
404411
}
405412
else {
406413
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
407-
.body(new McpError("Unknown message type"));
414+
.body(McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
415+
.message("Unknown message type")
416+
.build());
408417
}
409418
}
410419
catch (IllegalArgumentException | IOException e) {
411420
logger.error("Failed to deserialize message: {}", e.getMessage());
412-
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
421+
return ServerResponse.badRequest()
422+
.body(McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build());
413423
}
414424
catch (Exception e) {
415425
logger.error("Error handling message: {}", e.getMessage());
416-
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
426+
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
427+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
417428
}
418429
}
419430

@@ -451,7 +462,8 @@ private ServerResponse handleDelete(ServerRequest request) {
451462
}
452463
catch (Exception e) {
453464
logger.error("Failed to delete session {}: {}", sessionId, e.getMessage());
454-
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
465+
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
466+
.body(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
455467
}
456468
}
457469

0 commit comments

Comments
 (0)