feat: implement Streamable HTTP HttpClient transport and improve error handling (#337)
- Add HttpClientStreamableHttpTransport implementing the 2025-03-26 MCP specification - Add ResponseSubscribers utility for handling different HTTP response body types - Refactor HttpClientSseClientTransport to use reactive streams instead of FlowSseClient - Remove FlowSseClient in favor of reactive stream patterns - Use Disposable-based connection management instead of CountDownLatch - Replace message endpoint discovery with Sinks.One approach - Improve error handling by replacing doOnError/onErrorResume with onErrorComplete - Add test coverage for new Streamable HTTP transport - Add resiliency tests for connection handling and session invalidation BREAKING CHANGE: FlowSseClient has been removed in favor of reactive streams Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> Co-authored-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
This commit is contained in:
parent
b328051685
commit
c711f8315a
4 changed files with 1601 additions and 394 deletions
|
|
@ -129,11 +129,10 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
Function<String, Publisher<Void>> onClose = sessionId -> sessionId == null ? Mono.empty()
|
||||
: webClient.delete().uri(this.endpoint).headers(httpHeaders -> {
|
||||
httpHeaders.add("mcp-session-id", sessionId);
|
||||
})
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.doOnError(e -> logger.warn("Got error when closing transport", e))
|
||||
.then();
|
||||
}).retrieve().toBodilessEntity().onErrorComplete(e -> {
|
||||
logger.warn("Got error when closing transport", e);
|
||||
return true;
|
||||
}).then();
|
||||
return new DefaultMcpTransportSession(onClose);
|
||||
}
|
||||
|
||||
|
|
@ -305,12 +304,12 @@ public class WebClientStreamableHttpTransport implements McpClientTransport {
|
|||
}
|
||||
})
|
||||
.flatMap(jsonRpcMessage -> this.handler.get().apply(Mono.just(jsonRpcMessage)))
|
||||
.onErrorResume(t -> {
|
||||
.onErrorComplete(t -> {
|
||||
// handle the error first
|
||||
this.handleException(t);
|
||||
// inform the caller of sendMessage
|
||||
sink.error(t);
|
||||
return Flux.empty();
|
||||
return true;
|
||||
})
|
||||
.doFinally(s -> {
|
||||
Disposable ref = disposableRef.getAndSet(null);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package io.modelcontextprotocol.client;
|
||||
|
||||
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
|
||||
@Timeout(15)
|
||||
public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncClientTests {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package io.modelcontextprotocol.client;
|
||||
|
||||
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
|
||||
@Timeout(15)
|
||||
public class WebClientStreamableHttpSyncClientTests extends AbstractMcpSyncClientTests {
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@
|
|||
</appender>
|
||||
|
||||
<!-- Main MCP package -->
|
||||
<logger name="io.modelcontextprotocol" level="DEBUG"/>
|
||||
<logger name="io.modelcontextprotocol" level="INFO"/>
|
||||
|
||||
<!-- Client packages -->
|
||||
<logger name="io.modelcontextprotocol.client" level="DEBUG"/>
|
||||
<logger name="io.modelcontextprotocol.client" level="INFO"/>
|
||||
|
||||
<!-- Server transport package -->
|
||||
<logger name="io.modelcontextprotocol.server.transport" level="DEBUG"/>
|
||||
<logger name="io.modelcontextprotocol.server.transport" level="INFO"/>
|
||||
|
||||
<!-- Spec package -->
|
||||
<logger name="io.modelcontextprotocol.spec" level="DEBUG"/>
|
||||
<logger name="io.modelcontextprotocol.spec" level="INFO"/>
|
||||
|
||||
<!-- Root logger -->
|
||||
<root level="INFO">
|
||||
|
|
|
|||
Loading…
Reference in a new issue