CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-starter-mcp-client-webflux

Spring Boot starter providing auto-configuration for Model Context Protocol (MCP) client with Spring WebFlux, enabling reactive AI applications to connect to MCP servers via SSE and Streamable HTTP transports

Overview
Eval results
Files

streamable-http-transport.mddocs/reference/

Streamable HTTP Transport Auto-Configuration

Auto-configuration for WebFlux-based Streamable HTTP client transport in the Model Context Protocol (MCP).

Overview

The Streamable HTTP transport auto-configuration provides reactive, bidirectional HTTP-based streaming for MCP communication. This transport is ideal for request-response patterns with streaming capabilities, allowing both the client and server to send and receive streaming data over HTTP.

Key Features

  • Bidirectional Streaming: Client and server can both stream data
  • HTTP-Based: Standard HTTP protocol with streaming
  • Reactive: Built on Spring WebClient and Project Reactor
  • Connection Pooling: Efficient connection reuse
  • WebFlux-Specific: Only available in WebFlux-based applications

Auto-Configuration Class

package org.springframework.ai.mcp.client.webflux.autoconfigure;

/**
 * Auto-configuration for WebFlux-based Streamable HTTP client transport.
 * Conditionally enabled based on class presence and properties.
 * Creates transport beans for each configured connection.
 * Thread-safe - transports use reactive streams.
 */
@org.springframework.boot.autoconfigure.AutoConfiguration
@org.springframework.boot.autoconfigure.condition.ConditionalOnClass({
    io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport.class,
    org.springframework.web.reactive.function.client.WebClient.class
})
@org.springframework.boot.context.properties.EnableConfigurationProperties({
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStreamableHttpClientProperties.class,
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties.class
})
@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(
    prefix = "spring.ai.mcp.client",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
public class StreamableHttpWebFluxTransportAutoConfiguration {
}

Conditional Activation

This auto-configuration is conditionally enabled when:

  1. Class Presence: Both io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport and org.springframework.web.reactive.function.client.WebClient are on the classpath
  2. Property Check: spring.ai.mcp.client.enabled is true (default) or not specified
  3. WebFlux Availability: Spring WebFlux dependencies are present

If these conditions are not met, the auto-configuration is skipped.

Required Dependencies

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.modelcontextprotocol</groupId>
    <artifactId>mcp-spring-webflux</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'io.modelcontextprotocol:mcp-spring-webflux'

Beans Created

Streamable HTTP WebFlux Client Transports

package org.springframework.ai.mcp.client.webflux.autoconfigure;

/**
 * Creates a list of WebFlux-based Streamable HTTP transports for MCP communication.
 *
 * Each transport is configured with:
 * - A cloned WebClient.Builder with server-specific base URL
 * - ObjectMapper for JSON processing
 * - Server connection parameters from properties
 *
 * Process:
 * 1. Get WebClient.Builder (provided or default)
 * 2. Get ObjectMapper (provided or default)
 * 3. For each configured connection:
 *    a. Clone WebClient.Builder
 *    b. Set base URL from connection parameters
 *    c. Create WebClientStreamableHttpTransport with builder and endpoint
 *    d. Wrap in NamedClientMcpTransport with connection name
 *
 * Thread-safe - uses immutable configurations.
 * Reactive - transports use Spring WebClient.
 *
 * @param streamableProperties Streamable HTTP client properties containing server configurations
 * @param webClientBuilderProvider Provider for WebClient.Builder (uses WebClient.builder() if not provided)
 * @param objectMapperProvider Provider for ObjectMapper (creates new ObjectMapper() if not provided)
 * @return List of named MCP transports, one for each configured connection (never null, may be empty)
 * @throws IllegalArgumentException if connection configuration is invalid
 */
@org.springframework.context.annotation.Bean
public java.util.List<org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport> streamableHttpWebFluxClientTransports(
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStreamableHttpClientProperties streamableProperties,
    org.springframework.beans.factory.ObjectProvider<org.springframework.web.reactive.function.client.WebClient.Builder> webClientBuilderProvider,
    org.springframework.beans.factory.ObjectProvider<com.fasterxml.jackson.databind.ObjectMapper> objectMapperProvider
);

Return Type: List<NamedClientMcpTransport>

Configuration

Basic Configuration

Configure Streamable HTTP connections in application.yml:

spring.ai.mcp.client.streamable-http:
  connections:
    analysis-service:
      url: http://localhost:8080
    report-service:
      url: http://localhost:9000
      endpoint: /custom/mcp

Properties format:

spring.ai.mcp.client.streamable-http.connections.analysis-service.url=http://localhost:8080
spring.ai.mcp.client.streamable-http.connections.report-service.url=http://localhost:9000
spring.ai.mcp.client.streamable-http.connections.report-service.endpoint=/custom/mcp

Configuration Properties Reference

See Configuration Properties for complete Streamable HTTP configuration options.

Default Values

  • Endpoint: /mcp (used if endpoint is not specified)
  • WebClient.Builder: Uses bean from application context, or creates WebClient.builder() if not available
  • ObjectMapper: Uses bean from application context, or creates new ObjectMapper() if not available

Usage Examples

Injecting Streamable HTTP Transports

import org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * Service demonstrating Streamable HTTP transport usage.
 * Typically you don't use transports directly - inject McpSyncClient or McpAsyncClient instead.
 * This example shows how to access transports for advanced scenarios.
 */
@Service
public class McpStreamableHttpService {

    private final List<NamedClientMcpTransport> streamableHttpTransports;

    /**
     * Constructor injection of Streamable HTTP transports.
     * If no connections configured, list will be empty.
     *
     * @param streamableHttpTransports Auto-configured transports
     */
    public McpStreamableHttpService(List<NamedClientMcpTransport> streamableHttpTransports) {
        this.streamableHttpTransports = streamableHttpTransports;
    }

    /**
     * Find specific transport by name.
     * Throws if not found - check existence first in production.
     *
     * @throws IllegalStateException if transport not found
     */
    public void connectToAnalysisService() {
        // Find the analysis-service transport
        NamedClientMcpTransport analysisTransport = streamableHttpTransports.stream()
            .filter(t -> "analysis-service".equals(t.name()))
            .findFirst()
            .orElseThrow(() -> new IllegalStateException("analysis-service transport not found"));

        // Use the transport (typically via an MCP client)
        io.modelcontextprotocol.spec.McpClientTransport transport = analysisTransport.transport();
        // ... connect and use transport
    }

    /**
     * List all configured Streamable HTTP connections.
     * Useful for debugging and monitoring.
     */
    public void listAllStreamableHttpConnections() {
        streamableHttpTransports.forEach(t ->
            System.out.println("Streamable HTTP Transport: " + t.name())
        );
    }
}

Filtering by Transport Type

If your application uses both SSE and Streamable HTTP transports, you can filter by checking the transport implementation type:

import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;

/**
 * Filter transports by type.
 * Useful when multiple transport types are configured.
 *
 * @param allTransports All configured transports
 */
public void findStreamableHttpTransports(List<NamedClientMcpTransport> allTransports) {
    List<NamedClientMcpTransport> streamableHttpTransports = allTransports.stream()
        .filter(t -> t.transport() instanceof WebClientStreamableHttpTransport)
        .toList();

    // Use only Streamable HTTP transports
    streamableHttpTransports.forEach(t -> {
        System.out.println("Streamable HTTP: " + t.name());
    });
}

Combining Multiple Transport Types

You can configure both SSE and Streamable HTTP transports in the same application:

spring.ai.mcp.client:
  # SSE connections for streaming updates
  sse:
    connections:
      notifications:
        url: http://localhost:8080
        sse-endpoint: /notifications/sse

  # Streamable HTTP connections for request-response
  streamable-http:
    connections:
      api:
        url: http://localhost:9000
        endpoint: /api/mcp
      data:
        url: http://localhost:9001

Then use them based on your needs:

import org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport;
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
import java.util.List;

/**
 * Service that uses both SSE and Streamable HTTP transports.
 * Demonstrates categorizing transports by type.
 */
@Service
public class McpService {

    private final List<NamedClientMcpTransport> allTransports;

    public McpService(List<NamedClientMcpTransport> allTransports) {
        this.allTransports = allTransports;
    }

    /**
     * Categorize transports by type.
     * Useful for routing requests to appropriate transport.
     */
    public void categorizeTransports() {
        allTransports.forEach(transport -> {
            if (transport.transport() instanceof WebFluxSseClientTransport) {
                System.out.println("SSE: " + transport.name());
            } else if (transport.transport() instanceof WebClientStreamableHttpTransport) {
                System.out.println("Streamable HTTP: " + transport.name());
            }
        });
    }
}

Custom WebClient Configuration

To customize the WebClient.Builder used by all Streamable HTTP transports:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.channel.ChannelOption;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;

/**
 * Configuration for customizing WebClient used by Streamable HTTP transports.
 * Applied to all Streamable HTTP connections.
 */
@Configuration
public class WebClientConfig {

    /**
     * Custom WebClient.Builder with connection pooling and timeouts.
     * Will be cloned for each connection with connection-specific base URL.
     * Thread-safe - builder is cloned, not shared.
     *
     * @return Configured WebClient.Builder
     */
    @Bean
    public WebClient.Builder webClientBuilder() {
        // Configure connection pool
        ConnectionProvider provider = ConnectionProvider.builder("mcp-pool")
            .maxConnections(100)            // Max total connections
            .maxIdleTime(Duration.ofSeconds(30))    // Idle timeout
            .maxLifeTime(Duration.ofMinutes(5))     // Max connection lifetime
            .pendingAcquireTimeout(Duration.ofSeconds(60)) // Wait timeout
            .evictInBackground(Duration.ofSeconds(120))    // Cleanup interval
            .build();
        
        // Configure HTTP client with custom timeouts
        HttpClient httpClient = HttpClient.create(provider)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 5 second connect timeout
            .option(ChannelOption.SO_KEEPALIVE, true)  // Enable TCP keepalive
            .option(ChannelOption.TCP_NODELAY, true)   // Disable Nagle's algorithm
            .responseTimeout(Duration.ofSeconds(30))   // 30 second response timeout
            .compress(true);                           // Enable compression
        
        return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .defaultHeader("User-Agent", "MyApp-MCP-Client/1.0")
            .defaultHeader("Accept", "application/json")
            .codecs(configurer -> configurer
                .defaultCodecs()
                .maxInMemorySize(16 * 1024 * 1024)); // 16MB buffer
    }
}

This WebClient.Builder will be cloned and customized for each Streamable HTTP connection with its specific base URL.

Custom ObjectMapper Configuration

To customize JSON serialization for all Streamable HTTP transports:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configuration for JSON serialization in Streamable HTTP transports.
 * Applied to all connections.
 */
@Configuration
public class JacksonConfig {

    /**
     * Custom ObjectMapper for JSON processing.
     * Configures date handling, unknown properties, etc.
     * Thread-safe after configuration.
     *
     * @return Configured ObjectMapper
     */
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        // Register Java 8 date/time module
        mapper.registerModule(new JavaTimeModule());
        
        // Configure serialization
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.configure(SerializationFeature.INDENT_OUTPUT, true); // Pretty print for debugging
        
        // Configure deserialization
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Lenient
        mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
        
        return mapper;
    }
}

Transport Implementation Details

Internally, the auto-configuration creates Streamable HTTP transports using the MCP SDK's builder pattern:

// Conceptual example (not for direct use) - shows internal process
package org.springframework.ai.mcp.client.webflux.autoconfigure;

// For each configured connection:
for (Map.Entry<String, ConnectionParameters> entry : connections.entrySet()) {
    String connectionName = entry.getKey();
    ConnectionParameters params = entry.getValue();
    
    // Clone WebClient.Builder and set base URL
    var webClientBuilder = webClientBuilderTemplate.clone()
        .baseUrl(params.url());
    
    // Determine endpoint (use default if not specified)
    String streamableHttpEndpoint = params.endpoint() != null
        ? params.endpoint()
        : "/mcp";
    
    // Create transport
    var transport = io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport.builder(webClientBuilder)
        .endpoint(streamableHttpEndpoint)
        .jsonMapper(new io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper(objectMapper))
        .build();
    
    // Wrap in named transport
    transports.add(new NamedClientMcpTransport(connectionName, transport));
}

Each configured connection gets its own transport instance, allowing independent configuration and lifecycle management.

HTTP Protocol Details

  • Content-Type: application/json for request/response
  • Transfer-Encoding: Chunked encoding for streaming
  • HTTP Method: POST for MCP requests
  • Connection: Keep-alive for connection reuse
  • Compression: Optional gzip/deflate compression
  • Timeouts: Configurable connect and response timeouts

Advanced Configuration

Environment-Specific Configuration

Configure different servers for different environments:

# application-dev.yml
spring.ai.mcp.client.streamable-http:
  connections:
    api:
      url: http://localhost:9000

# application-prod.yml
spring.ai.mcp.client.streamable-http:
  connections:
    api:
      url: https://api.production.com
      endpoint: /v1/mcp

Dynamic Connection Configuration

You can provide connections programmatically by creating a custom bean:

import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStreamableHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configuration providing dynamic connection details.
 * Useful for service discovery, database-driven configuration, etc.
 */
@Configuration
public class DynamicConnectionConfig {

    /**
     * Provide connections dynamically.
     * Can load from database, service registry, etc.
     *
     * @return Streamable HTTP properties with dynamic connections
     */
    @Bean
    public McpStreamableHttpClientProperties streamableHttpClientProperties() {
        McpStreamableHttpClientProperties properties = new McpStreamableHttpClientProperties();

        // Add connections programmatically
        properties.getConnections().put(
            "dynamic-server",
            new McpStreamableHttpClientProperties.ConnectionParameters(
                discoverServerUrl(), // Dynamic URL discovery
                "/mcp/v2"
            )
        );

        // Conditional connection based on runtime logic
        if (System.getenv("ENABLE_BACKUP_SERVER") != null) {
            properties.getConnections().put(
                "backup-server",
                new McpStreamableHttpClientProperties.ConnectionParameters(
                    "http://backup-host:8080",
                    "/mcp"
                )
            );
        }

        return properties;
    }
    
    private String discoverServerUrl() {
        // Service discovery logic
        return "http://discovered-host:8080";
    }
}

Connection Pooling and Performance

The WebFlux WebClient uses Reactor Netty under the hood, which provides connection pooling by default. You can customize the connection pool:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;

/**
 * Configuration optimizing connection pooling for high-performance scenarios.
 */
@Configuration
public class ConnectionPoolConfig {

    /**
     * Configure optimized connection pool.
     * Tuned for high-concurrency MCP operations.
     *
     * @return WebClient.Builder with optimized connection pool
     */
    @Bean
    public WebClient.Builder webClientBuilder() {
        // Configure connection pool
        ConnectionProvider provider = ConnectionProvider.builder("mcp-pool")
            .maxConnections(100)                      // Increase for high concurrency
            .maxIdleTime(Duration.ofSeconds(30))     // Keep connections alive
            .maxLifeTime(Duration.ofMinutes(5))      // Recycle old connections
            .pendingAcquireTimeout(Duration.ofSeconds(60)) // Wait for available connection
            .evictInBackground(Duration.ofSeconds(120))    // Background cleanup
            .metrics(true)                           // Enable metrics
            .build();

        HttpClient httpClient = HttpClient.create(provider);

        return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient));
    }
}

Troubleshooting

No Streamable HTTP Transports Created

If no Streamable HTTP transports are created, check:

  1. Dependencies: Ensure both io.modelcontextprotocol.sdk:mcp-spring-webflux and Spring WebFlux dependencies are on the classpath
  2. Configuration: Verify spring.ai.mcp.client.streamable-http.connections is properly configured
  3. Enabled: Check spring.ai.mcp.client.enabled=true (or not set, as true is default)
  4. WebClient: Ensure WebClient class is available (should be with Spring WebFlux)
  5. Logs: Enable debug logging: logging.level.org.springframework.ai.mcp=DEBUG

Connection Failures

If Streamable HTTP connections fail:

  1. URL Validation: Verify the url values are correct and accessible
    • Test with curl: curl -X POST http://localhost:9000/mcp
  2. Endpoint Path: Ensure the endpoint matches the server's expected path
  3. Server Availability: Check that the MCP server is running and responding on the HTTP endpoint
  4. Network: Verify no firewalls or proxies are blocking HTTP connections
  5. Timeouts: Check if request timeouts are appropriate for your use case
  6. CORS: If cross-origin, check CORS headers
  7. Content-Type: Verify server accepts application/json

Performance Issues

If experiencing performance problems:

  1. Connection Pool: Tune connection pool settings (see Connection Pooling section)
  2. Buffer Size: Adjust maxInMemorySize in codec configuration
  3. Timeouts: Review and adjust connection and response timeouts
  4. Backpressure: Ensure proper reactive stream backpressure handling
  5. Monitoring: Monitor connection pool metrics
  6. Compression: Enable HTTP compression for large payloads
  7. Keep-Alive: Ensure HTTP keep-alive is enabled

Memory Issues

If experiencing memory problems:

  1. Buffer Size: Reduce maxInMemorySize in WebClient codec configuration
  2. Connection Limit: Reduce maximum connections in pool
  3. Backpressure: Ensure proper backpressure handling
  4. Leak Detection: Enable Netty leak detection
  5. Heap Analysis: Use heap dump analysis to identify leaks

Bean Conflicts

If you see bean definition conflicts:

  1. Ensure you're not manually defining beans with the same names
  2. Use @Primary or @Qualifier annotations to resolve ambiguity
  3. Consider excluding the auto-configuration and configuring manually if needed
  4. Check for multiple WebClient.Builder beans

Comparison: SSE vs Streamable HTTP

FeatureSSE TransportStreamable HTTP Transport
DirectionUnidirectional (server to client)Bidirectional
Use CaseServer push, notifications, live updatesRequest-response with streaming
ConnectionPersistent connectionPer-request connection (can be kept alive)
ProtocolServer-Sent EventsHTTP with streaming
OverheadLower (persistent connection)Slightly higher (HTTP overhead)
Firewall FriendlyLess (long-lived connections)More (standard HTTP)
ReconnectionAutomaticPer-request
ComplexitySimple (server push)More flexible (bidirectional)

Choose SSE for:

  • Server-push scenarios
  • Live notifications and updates
  • Long-lived connections
  • One-way data flow

Choose Streamable HTTP for:

  • Traditional request-response patterns
  • Bidirectional streaming
  • Standard HTTP infrastructure
  • Better firewall compatibility
  • Load balancer friendly

Security

TLS/SSL Configuration

For HTTPS connections:

import io.netty.handler.ssl.SslContextBuilder;

@Bean
public WebClient.Builder secureWebClientBuilder() throws Exception {
    SslContext sslContext = SslContextBuilder
        .forClient()
        .trustManager(trustManagerFactory) // Use proper trust store
        .build();
    
    HttpClient httpClient = HttpClient.create()
        .secure(sslSpec -> sslSpec.sslContext(sslContext));
    
    return WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient));
}

Authentication

Add authentication:

@Bean
public WebClient.Builder authenticatedWebClientBuilder() {
    return WebClient.builder()
        .defaultHeader("Authorization", "Bearer " + getToken())
        .filter((request, next) -> {
            // Refresh token if needed
            String token = getToken();
            ClientRequest authenticated = ClientRequest.from(request)
                .header("Authorization", "Bearer " + token)
                .build();
            return next.exchange(authenticated);
        });
}

Related Documentation

tessl i tessl/maven-org-springframework-ai--spring-ai-starter-mcp-client-webflux@1.1.0

docs

index.md

tile.json