|
13 | 13 | import java.time.Duration; |
14 | 14 | import java.util.List; |
15 | 15 | import java.util.Optional; |
16 | | -import java.util.concurrent.CompletableFuture; |
17 | 16 | import java.util.concurrent.atomic.AtomicReference; |
18 | 17 | import java.util.function.Consumer; |
19 | 18 | import java.util.function.Function; |
|
60 | 59 | * "https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse">"HTTP |
61 | 60 | * with SSE" transport</a>. In order to communicate over the phased-out |
62 | 61 | * <code>2024-11-05</code> protocol, use {@link HttpClientSseClientTransport} or |
63 | | - * {@link WebFluxSseClientTransport}. |
| 62 | + * {@code WebFluxSseClientTransport}. |
64 | 63 | * </p> |
65 | 64 | * |
66 | 65 | * @author Christian Tzolov |
@@ -234,7 +233,9 @@ private Mono<Disposable> reconnect(McpTransportStream<Disposable> stream) { |
234 | 233 | else { |
235 | 234 | logger.debug("SSE connection established successfully"); |
236 | 235 | } |
237 | | - })).flatMap(responseEvent -> { |
| 236 | + })) |
| 237 | + .map(responseEvent -> (ResponseSubscribers.SseResponseEvent) responseEvent) |
| 238 | + .flatMap(responseEvent -> { |
238 | 239 | int statusCode = responseEvent.responseInfo().statusCode(); |
239 | 240 |
|
240 | 241 | if (statusCode >= 200 && statusCode < 300) { |
@@ -319,12 +320,11 @@ private BodyHandler<Void> toSendMessageBodySubscriber(FluxSink<ResponseEvent> si |
319 | 320 | else if (contentType.contains(APPLICATION_JSON)) { |
320 | 321 | // For JSON responses and others, use string subscriber |
321 | 322 | logger.debug("Received response, using string subscriber"); |
322 | | - return ResponseSubscribers.jsonoBodySubscriber(responseInfo, sink); |
| 323 | + return ResponseSubscribers.aggregateBodySubscriber(responseInfo, sink); |
323 | 324 | } |
324 | 325 |
|
325 | 326 | logger.debug("Received Bodyless response, using discarding subscriber"); |
326 | | - // return HttpResponse.BodySubscribers.discarding(); |
327 | | - return ResponseSubscribers.bodylessBodySubscriber(responseInfo, sink); |
| 327 | + return ResponseSubscribers.bodilessBodySubscriber(responseInfo, sink); |
328 | 328 | }; |
329 | 329 |
|
330 | 330 | return responseBodyHandler; |
@@ -404,34 +404,42 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sendMessage) { |
404 | 404 | return Flux.empty(); |
405 | 405 | } |
406 | 406 | else if (contentType.contains(TEXT_EVENT_STREAM)) { |
407 | | - try { |
408 | | - // We don't support batching ATM and probably won't since the |
409 | | - // next version considers removing it. |
410 | | - McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, |
411 | | - responseEvent.sseEvent().data()); |
412 | | - |
413 | | - Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> idWithMessages = Tuples |
414 | | - .of(Optional.ofNullable(responseEvent.sseEvent().id()), List.of(message)); |
415 | | - |
416 | | - McpTransportStream<Disposable> sessionStream = new DefaultMcpTransportStream<>( |
417 | | - this.resumableStreams, this::reconnect); |
418 | | - |
419 | | - logger.debug("Connected stream {}", sessionStream.streamId()); |
420 | | - |
421 | | - messageSink.success(); |
422 | | - |
423 | | - return Flux.from(sessionStream.consumeSseStream(Flux.just(idWithMessages))); |
424 | | - |
425 | | - } |
426 | | - catch (IOException ioException) { |
427 | | - return Flux.<McpSchema.JSONRPCMessage>error( |
428 | | - new McpError("Error parsing JSON-RPC message: " + responseEvent.sseEvent().data())); |
429 | | - } |
| 407 | + return Flux.just(((ResponseSubscribers.SseResponseEvent) responseEvent).sseEvent()) |
| 408 | + .flatMap(sseEvent -> { |
| 409 | + try { |
| 410 | + // We don't support batching ATM and probably won't |
| 411 | + // since the |
| 412 | + // next version considers removing it. |
| 413 | + McpSchema.JSONRPCMessage message = McpSchema |
| 414 | + .deserializeJsonRpcMessage(this.objectMapper, sseEvent.data()); |
| 415 | + |
| 416 | + Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> idWithMessages = Tuples |
| 417 | + .of(Optional.ofNullable(sseEvent.id()), List.of(message)); |
| 418 | + |
| 419 | + McpTransportStream<Disposable> sessionStream = new DefaultMcpTransportStream<>( |
| 420 | + this.resumableStreams, this::reconnect); |
| 421 | + |
| 422 | + logger.debug("Connected stream {}", sessionStream.streamId()); |
| 423 | + |
| 424 | + messageSink.success(); |
| 425 | + |
| 426 | + return Flux.from(sessionStream.consumeSseStream(Flux.just(idWithMessages))); |
| 427 | + } |
| 428 | + catch (IOException ioException) { |
| 429 | + return Flux.<McpSchema.JSONRPCMessage>error( |
| 430 | + new McpError("Error parsing JSON-RPC message: " + sseEvent.data())); |
| 431 | + } |
| 432 | + }); |
430 | 433 | } |
431 | 434 | else if (contentType.contains(APPLICATION_JSON)) { |
432 | | - McpSchema.JSONRPCMessage jsonRpcResponse = responseEvent.jsonRpcMessage(); |
433 | 435 | messageSink.success(); |
434 | | - return Flux.just(jsonRpcResponse); // ??? |
| 436 | + String data = ((ResponseSubscribers.AggregateResponseEvent) responseEvent).data(); |
| 437 | + try { |
| 438 | + return Mono.just(McpSchema.deserializeJsonRpcMessage(objectMapper, data)); |
| 439 | + } |
| 440 | + catch (IOException e) { |
| 441 | + return Mono.error(e); |
| 442 | + } |
435 | 443 | } |
436 | 444 | logger.warn("Unknown media type {} returned for POST in session {}", contentType, |
437 | 445 | sessionRepresentation); |
@@ -489,9 +497,9 @@ public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) { |
489 | 497 | */ |
490 | 498 | public static class Builder { |
491 | 499 |
|
492 | | - private ObjectMapper objectMapper; |
| 500 | + private final String baseUri; |
493 | 501 |
|
494 | | - private String baseUri; |
| 502 | + private ObjectMapper objectMapper; |
495 | 503 |
|
496 | 504 | private HttpClient.Builder clientBuilder = HttpClient.newBuilder() |
497 | 505 | .version(HttpClient.Version.HTTP_1_1) |
|
0 commit comments