3535import java .util .function .Function ;
3636import java .util .function .Supplier ;
3737
38+ /**
39+ * An implementation of the Streamable HTTP protocol as defined by the
40+ * <code>2025-03-26</code> version of the MCP specification.
41+ *
42+ * <p>
43+ * The transport is capable of resumability and reconnects. It reacts to transport-level
44+ * session invalidation and will propagate {@link McpTransportSessionNotFoundException
45+ * appropriate exceptions} to the higher level abstraction layer when needed in order to
46+ * allow proper state management. The implementation handles servers that are stateful and
47+ * provide session meta information, but can also communicate with stateless servers that
48+ * do not provide a session identifier and do not support SSE streams.
49+ * </p>
50+ * <p>
51+ * This implementation does not handle backwards compatibility with the <a href=
52+ * "https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse">"HTTP
53+ * with SSE" transport</a>. In order to communicate over the phased-out
54+ * <code>2024-11-05</code> protocol, use {@link HttpClientSseClientTransport} or
55+ * {@link WebFluxSseClientTransport}.
56+ * </p>
57+ *
58+ * @author Dariusz Jędrzejczyk
59+ * @see <a href=
60+ * "https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http">Streamable
61+ * HTTP transport specification</a>
62+ */
3863public class WebClientStreamableHttpTransport implements McpClientTransport {
3964
4065 private static final Logger logger = LoggerFactory .getLogger (WebClientStreamableHttpTransport .class );
@@ -47,7 +72,7 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
4772 */
4873 private static final String MESSAGE_EVENT_TYPE = "message" ;
4974
50- public static final ParameterizedTypeReference <ServerSentEvent <String >> PARAMETERIZED_TYPE_REF = new ParameterizedTypeReference <>() {
75+ private static final ParameterizedTypeReference <ServerSentEvent <String >> PARAMETERIZED_TYPE_REF = new ParameterizedTypeReference <>() {
5176 };
5277
5378 private final ObjectMapper objectMapper ;
@@ -66,7 +91,6 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
6691
6792 private final AtomicReference <Consumer <Throwable >> exceptionHandler = new AtomicReference <>();
6893
69- // TODO: builder
7094 private WebClientStreamableHttpTransport (ObjectMapper objectMapper , WebClient .Builder webClientBuilder ,
7195 String endpoint , boolean resumableStreams , boolean openConnectionOnStartup ) {
7296 this .objectMapper = objectMapper ;
@@ -77,6 +101,13 @@ private WebClientStreamableHttpTransport(ObjectMapper objectMapper, WebClient.Bu
77101 this .activeSession .set (createTransportSession ());
78102 }
79103
104+ /**
105+ * Create a stateful builder for creating {@link WebClientStreamableHttpTransport}
106+ * instances.
107+ * @param webClientBuilder the {@link WebClient.Builder} to use
108+ * @return a builder which will create an instance of
109+ * {@link WebClientStreamableHttpTransport} once {@link Builder#build()} is called
110+ */
80111 public static Builder builder (WebClient .Builder webClientBuilder ) {
81112 return new Builder (webClientBuilder );
82113 }
@@ -392,6 +423,9 @@ private Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> parse(Serve
392423 }
393424 }
394425
426+ /**
427+ * Builder for {@link WebClientStreamableHttpTransport}.
428+ */
395429 public static class Builder {
396430
397431 private ObjectMapper objectMapper ;
@@ -409,34 +443,71 @@ private Builder(WebClient.Builder webClientBuilder) {
409443 this .webClientBuilder = webClientBuilder ;
410444 }
411445
446+ /**
447+ * Configure the {@link ObjectMapper} to use.
448+ * @param objectMapper instance to use
449+ * @return the builder instance
450+ */
412451 public Builder objectMapper (ObjectMapper objectMapper ) {
413452 Assert .notNull (objectMapper , "ObjectMapper must not be null" );
414453 this .objectMapper = objectMapper ;
415454 return this ;
416455 }
417456
457+ /**
458+ * Configure the {@link WebClient.Builder} to construct the {@link WebClient}.
459+ * @param webClientBuilder instance to use
460+ * @return the builder instance
461+ */
418462 public Builder webClientBuilder (WebClient .Builder webClientBuilder ) {
419463 Assert .notNull (webClientBuilder , "WebClient.Builder must not be null" );
420464 this .webClientBuilder = webClientBuilder ;
421465 return this ;
422466 }
423467
468+ /**
469+ * Configure the endpoint to make HTTP requests against.
470+ * @param endpoint endpoint to use
471+ * @return the builder instance
472+ */
424473 public Builder endpoint (String endpoint ) {
425474 Assert .hasText (endpoint , "endpoint must be a non-empty String" );
426475 this .endpoint = endpoint ;
427476 return this ;
428477 }
429478
479+ /**
480+ * Configure whether to use the stream resumability feature by keeping track of
481+ * SSE event ids.
482+ * @param resumableStreams if {@code true} event ids will be tracked and upon
483+ * disconnection, the last seen id will be used upon reconnection as a header to
484+ * resume consuming messages.
485+ * @return the builder instance
486+ */
430487 public Builder resumableStreams (boolean resumableStreams ) {
431488 this .resumableStreams = resumableStreams ;
432489 return this ;
433490 }
434491
492+ /**
493+ * Configure whether the client should open an SSE connection upon startup. Not
494+ * all servers support this (although it is in theory possible with the current
495+ * specification), so use with caution. By default, this value is {@code false}.
496+ * @param openConnectionOnStartup if {@code true} the {@link #connect(Function)}
497+ * method call will try to open an SSE connection before sending any JSON-RPC
498+ * request
499+ * @return the builder instance
500+ */
435501 public Builder openConnectionOnStartup (boolean openConnectionOnStartup ) {
436502 this .openConnectionOnStartup = openConnectionOnStartup ;
437503 return this ;
438504 }
439505
506+ /**
507+ * Construct a fresh instance of {@link WebClientStreamableHttpTransport} using
508+ * the current builder configuration.
509+ * @return a new instance of {@link WebClientStreamableHttpTransport}
510+ */
440511 public WebClientStreamableHttpTransport build () {
441512 ObjectMapper objectMapper = this .objectMapper != null ? this .objectMapper : new ObjectMapper ();
442513
0 commit comments