CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-spring-boot-autoconfigure

Spring AI Spring Boot Auto Configuration modules providing automatic setup for AI models, vector stores, MCP, and retry capabilities

Overview
Eval results
Files

mcp-client-api.mddocs/reference/

Model Context Protocol (MCP) - Client Modules

Model Context Protocol (MCP) client autoconfiguration provides automatic setup for MCP client connections with support for multiple transport mechanisms (Stdio, SSE, HTTP) and both synchronous and asynchronous operation modes.

Overview

MCP client modules enable Spring AI applications to connect to MCP servers and use their tools, prompts, and resources. The autoconfiguration handles client lifecycle, transport selection, tool callbacks, and resource management with comprehensive error handling and connection pooling.

Module Structure

The MCP client functionality is split across three modules for flexibility and minimal dependencies:

  1. mcp-client-common: Core client configuration, tool callbacks, stdio transport
  2. mcp-client-httpclient: Apache HttpClient-based SSE and HTTP transports
  3. mcp-client-webflux: Spring WebFlux-based SSE and HTTP transports

Maven Coordinates

Common Module (Required)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-mcp-client-common</artifactId>
    <version>1.1.2</version>
</dependency>

HttpClient Transport (Optional - for SSE/HTTP with Apache HttpClient)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-mcp-client-httpclient</artifactId>
    <version>1.1.2</version>
</dependency>

WebFlux Transport (Optional - for SSE/HTTP with Spring WebFlux)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-mcp-client-webflux</artifactId>
    <version>1.1.2</version>
</dependency>

Capabilities

MCP Client AutoConfiguration

Main autoconfiguration for MCP client support, creating sync or async clients based on configuration.

/**
 * Autoconfigures Model Context Protocol (MCP) client support
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: io.modelcontextprotocol.spec.McpSchema
 * - @ConditionalOnProperty: spring.ai.mcp.client.enabled=true (default)
 * 
 * Configuration Properties: spring.ai.mcp.client.*
 * 
 * Bean Creation Order:
 * 1. Transports (stdio, SSE, HTTP)
 * 2. Client configurer (optional customization)
 * 3. MCP clients (sync or async)
 * 4. Tool change event emitters
 * 5. Closeable wrappers for cleanup
 * 
 * @AutoConfiguration
 * @ConditionalOnClass(McpSchema.class)
 * @EnableConfigurationProperties(McpClientCommonProperties.class)
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "enabled", 
 *                        havingValue = "true", matchIfMissing = true)
 */
@AutoConfiguration
@ConditionalOnClass(McpSchema.class)
@EnableConfigurationProperties(McpClientCommonProperties.class)
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "enabled", 
                       havingValue = "true", matchIfMissing = true)
class McpClientAutoConfiguration {
    // Bean definitions for MCP client infrastructure
}

Synchronous Client Beans

Creates synchronous MCP clients for blocking operations with automatic resource management.

/**
 * Creates list of configured MCP synchronous clients
 * One client is created for each configured transport connection
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @param transports List of named transports (stdio, SSE, HTTP)
 * @param properties Client configuration properties
 * @param configurer Optional customizer for client creation
 * @return List of McpSyncClient instances, one per configured connection
 * 
 * Client Features:
 * - Blocking operations for synchronous workflows
 * - Tool listing and invocation
 * - Prompt template access
 * - Resource reading
 * - Server capability discovery
 * - Change notifications
 * 
 * Lifecycle:
 * - Clients are initialized on startup if properties.initialized=true
 * - Clients are automatically closed on application shutdown
 * - Connection failures are logged and retried based on transport
 * 
 * Thread Safety:
 * - Each client is thread-safe
 * - Multiple threads can share a single client instance
 * - Tool calls are serialized per client
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
List<McpSyncClient> mcpSyncClients(
    List<NamedClientMcpTransport> transports,
    McpClientCommonProperties properties,
    McpSyncClientConfigurer configurer
) {
    List<McpSyncClient> clients = new ArrayList<>();
    
    for (NamedClientMcpTransport transport : transports) {
        McpSyncClient.Builder builder = McpSyncClient.builder()
            .transport(transport.transport())
            .clientInfo(new McpSchema.Implementation(
                properties.getName(),
                properties.getVersion()
            ))
            .requestTimeout(properties.getRequestTimeout());
        
        // Apply custom configuration
        if (configurer != null) {
            configurer.configure(builder, transport.name());
        }
        
        McpSyncClient client = builder.build();
        
        // Initialize if configured
        if (properties.isInitialized()) {
            client.initialize();
        }
        
        clients.add(client);
    }
    
    return clients;
}

/**
 * Emits tool change events for sync clients
 * Notifies application when MCP server tools change
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @return Event emitter for tool list changes
 * 
 * Event Types:
 * - ToolsAdded: New tools available
 * - ToolsRemoved: Tools no longer available
 * - ToolsChanged: Tool definitions modified
 * 
 * Usage:
 * @EventListener
 * public void onToolsChanged(ToolsChangedEvent event) {
 *     // Refresh tool cache, update UI, etc.
 * }
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
McpSyncToolsChangeEventEmmiter mcpSyncToolChangeEventEmmiter() {
    return new McpSyncToolsChangeEventEmmiter();
}

/**
 * Closeable wrapper for MCP sync clients ensuring proper resource cleanup
 * Automatically closes all clients on application shutdown
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @param clients List of MCP sync clients to manage
 * @return Closeable wrapper for automatic cleanup
 * 
 * Cleanup Behavior:
 * - Closes all clients gracefully on shutdown
 * - Waits for in-flight requests to complete
 * - Releases transport resources
 * - Logs any cleanup errors
 * 
 * Shutdown Timeout:
 * - Default: 30 seconds
 * - Configurable via: spring.ai.mcp.client.shutdown-timeout
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
CloseableMcpSyncClients makeSyncClientsClosable(List<McpSyncClient> clients) {
    return new CloseableMcpSyncClients(clients);
}

/**
 * Default configurer for customizing MCP sync client creation
 * Can be overridden to customize client behavior per transport
 * 
 * @Bean
 * @ConditionalOnMissingBean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @return Default no-op configurer
 * 
 * Override Example:
 * @Bean
 * public McpSyncClientConfigurer customConfigurer() {
 *     return (builder, transportName) -> {
 *         if ("production".equals(transportName)) {
 *             builder.requestTimeout(Duration.ofMinutes(5));
 *         }
 *     };
 * }
 */
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
McpSyncClientConfigurer mcpSyncClientConfigurer() {
    return (builder, transportName) -> {
        // Default: no customization
    };
}

Asynchronous Client Beans

Creates asynchronous MCP clients for non-blocking operations using CompletableFuture.

/**
 * Creates list of configured MCP asynchronous clients
 * One client is created for each configured transport connection
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @param transports List of named transports (stdio, SSE, HTTP)
 * @param properties Client configuration properties
 * @param configurer Optional customizer for client creation
 * @return List of McpAsyncClient instances using CompletableFuture
 * 
 * Client Features:
 * - Non-blocking operations with CompletableFuture
 * - Async tool listing and invocation
 * - Async prompt template access
 * - Async resource reading
 * - Server capability discovery
 * - Change notifications
 * 
 * Async Benefits:
 * - Better resource utilization
 * - Higher throughput for I/O-bound operations
 * - Easier composition of multiple operations
 * - Reactive integration support
 * 
 * Thread Model:
 * - Operations execute on transport-specific thread pools
 * - Callbacks execute on completion thread
 * - Can integrate with virtual threads (Java 21+)
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
List<McpAsyncClient> mcpAsyncClients(
    List<NamedClientMcpTransport> transports,
    McpClientCommonProperties properties,
    McpAsyncClientConfigurer configurer
);

/**
 * Emits tool change events for async clients
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @return Event emitter for async tool list changes
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
McpAsyncToolsChangeEventEmmiter mcpAsyncToolChangeEventEmmiter();

/**
 * Closeable wrapper for MCP async clients
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @param clients List of MCP async clients to manage
 * @return Closeable wrapper for automatic cleanup
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
CloseableMcpAsyncClients makeAsyncClientsClosable(List<McpAsyncClient> clients);

/**
 * Default configurer for customizing MCP async client creation
 * 
 * @Bean
 * @ConditionalOnMissingBean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @return Default no-op configurer
 */
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
McpAsyncClientConfigurer mcpAsyncClientConfigurer();

MCP Tool Callback AutoConfiguration

Integrates MCP tools with Spring AI's function calling mechanism for seamless tool usage.

/**
 * Autoconfigures tool callbacks for MCP clients
 * Converts MCP tools to Spring AI FunctionCallback format
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: io.modelcontextprotocol.spec.McpSchema
 * - @ConditionalOnProperty: spring.ai.mcp.client.toolcallback.enabled=true (default)
 * 
 * Integration:
 * - MCP tools automatically available to ChatClient
 * - Tool names prefixed to avoid conflicts
 * - Tool schemas converted to JSON Schema
 * - Tool results converted to Spring AI format
 * 
 * @AutoConfiguration
 * @ConditionalOnClass(McpSchema.class)
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client.toolcallback", 
 *                        name = "enabled", havingValue = "true", matchIfMissing = true)
 */
@AutoConfiguration
@ConditionalOnClass(McpSchema.class)
@ConditionalOnProperty(prefix = "spring.ai.mcp.client.toolcallback", 
                       name = "enabled", havingValue = "true", matchIfMissing = true)
class McpToolCallbackAutoConfiguration {
    // Bean definitions for tool callback integration
}

/**
 * Generates prefixes for MCP tool names to avoid conflicts
 * When multiple MCP servers provide tools with same names
 * 
 * @Bean
 * @ConditionalOnMissingBean
 * @return Tool name prefix generator
 * 
 * Default Behavior:
 * - Uses transport name as prefix: "filesystem_read_file"
 * - Can be customized to use server name, connection name, etc.
 * 
 * Override Example:
 * @Bean
 * public McpToolNamePrefixGenerator customPrefixGenerator() {
 *     return transportName -> transportName.toUpperCase() + "_";
 * }
 */
@Bean
@ConditionalOnMissingBean
McpToolNamePrefixGenerator defaultMcpToolNamePrefixGenerator() {
    return transportName -> transportName + "_";
}

/**
 * Provides synchronous tool callbacks for Spring AI integration
 * Converts MCP tools to Spring AI FunctionCallback format
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @param clients List of MCP sync clients
 * @param prefixGenerator Generator for tool name prefixes
 * @return Provider for sync tool callbacks
 * 
 * Tool Conversion:
 * - MCP tool name -> Spring AI function name (with prefix)
 * - MCP input schema -> Spring AI function input type
 * - MCP tool result -> Spring AI function result
 * - MCP tool errors -> Spring AI exceptions
 * 
 * Usage with ChatClient:
 * ChatClient client = ChatClient.builder()
 *     .defaultFunctions(mcpToolCallbacks.getToolCallbacks())
 *     .build();
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
SyncMcpToolCallbackProvider mcpToolCallbacks(
    List<McpSyncClient> clients,
    McpToolNamePrefixGenerator prefixGenerator
);

/**
 * Provides asynchronous tool callbacks for Spring AI integration
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @param clients List of MCP async clients
 * @param prefixGenerator Generator for tool name prefixes
 * @return Provider for async tool callbacks
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
AsyncMcpToolCallbackProvider mcpAsyncToolCallbacks(
    List<McpAsyncClient> clients,
    McpToolNamePrefixGenerator prefixGenerator
);

Transport Configurations

Stdio Transport AutoConfiguration

Configures stdio transport for communicating with local MCP server processes.

/**
 * Autoconfigures Stdio transport for MCP clients
 * Stdio transport spawns local processes and communicates via stdin/stdout
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: io.modelcontextprotocol.spec.McpSchema
 * - @ConditionalOnProperty: spring.ai.mcp.client.enabled=true (default)
 * 
 * Use Cases:
 * - Local Node.js MCP servers
 * - Python MCP servers
 * - Any executable that implements MCP stdio protocol
 * 
 * Process Management:
 * - Processes started on client initialization
 * - Processes terminated on client close
 * - Process output logged for debugging
 * - Process errors captured and reported
 * 
 * @AutoConfiguration
 * @ConditionalOnClass(McpSchema.class)
 * @EnableConfigurationProperties({McpStdioClientProperties.class, McpClientCommonProperties.class})
 */
@AutoConfiguration
@ConditionalOnClass(McpSchema.class)
@EnableConfigurationProperties({McpStdioClientProperties.class, McpClientCommonProperties.class})
class StdioTransportAutoConfiguration {
    // Bean definitions for stdio transport
}

/**
 * Creates Stdio transports from configured server parameters
 * Each connection configuration spawns a separate process
 * 
 * @Bean
 * @param properties Stdio client properties with connection configurations
 * @return List of named stdio transports
 * 
 * Process Lifecycle:
 * 1. Process spawned with configured command and args
 * 2. Environment variables set from configuration
 * 3. Stdin/stdout connected to transport
 * 4. Process monitored for exit
 * 5. Process terminated on transport close
 * 
 * Error Handling:
 * - Process spawn failures throw TransportException
 * - Process crashes trigger reconnection attempts
 * - Stderr logged for debugging
 * 
 * Platform Support:
 * - Windows: Uses cmd.exe for shell commands
 * - Unix/Linux: Uses /bin/sh for shell commands
 * - macOS: Uses /bin/sh for shell commands
 */
@Bean
List<NamedClientMcpTransport> stdioTransports(McpStdioClientProperties properties) {
    List<NamedClientMcpTransport> transports = new ArrayList<>();
    
    for (Map.Entry<String, Parameters> entry : properties.getConnections().entrySet()) {
        String name = entry.getKey();
        Parameters params = entry.getValue();
        
        StdioClientTransport transport = new StdioClientTransport(
            params.command(),
            params.args(),
            params.env()
        );
        
        transports.add(new NamedClientMcpTransport(name, transport));
    }
    
    return transports;
}

SSE Transport AutoConfiguration (HttpClient)

Configures Server-Sent Events transport using Apache HttpClient for remote MCP servers.

/**
 * Autoconfigures SSE transport using Apache HttpClient
 * SSE provides persistent connections with server-push capabilities
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: McpSchema, McpSyncClient
 * - @ConditionalOnProperty: spring.ai.mcp.client.enabled=true (default)
 * 
 * Use Cases:
 * - Remote MCP servers with SSE support
 * - Long-lived connections with server notifications
 * - Real-time tool/prompt updates
 * 
 * Connection Management:
 * - Persistent HTTP connections
 * - Automatic reconnection on disconnect
 * - Heartbeat for connection health
 * - Connection pooling for efficiency
 * 
 * @AutoConfiguration
 * @ConditionalOnClass({McpSchema.class, McpSyncClient.class})
 * @EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
 */
@AutoConfiguration
@ConditionalOnClass({McpSchema.class, McpSyncClient.class})
@EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
class SseHttpClientTransportAutoConfiguration {
    // Bean definitions for SSE transport with HttpClient
}

/**
 * Creates SSE transports using Apache HttpClient
 * 
 * @Bean
 * @param properties SSE client properties with connection configurations
 * @return List of named SSE transports
 * 
 * Connection Features:
 * - HTTP/1.1 or HTTP/2 support
 * - TLS/SSL for secure connections
 * - Custom headers for authentication
 * - Proxy support
 * - Connection timeout configuration
 * 
 * SSE Protocol:
 * - Event stream parsing
 * - Automatic event type handling
 * - Reconnection with last-event-id
 * - Error event handling
 */
@Bean
List<NamedClientMcpTransport> sseHttpClientTransports(McpSseClientProperties properties);

SSE Transport AutoConfiguration (WebFlux)

Configures SSE transport using Spring WebFlux for reactive applications.

/**
 * Autoconfigures SSE transport using Spring WebFlux
 * Reactive SSE transport for non-blocking I/O
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: WebFluxSseClientTransport
 * 
 * Benefits:
 * - Non-blocking I/O
 * - Better resource utilization
 * - Reactive streams integration
 * - Backpressure support
 * 
 * @AutoConfiguration
 * @ConditionalOnClass(WebFluxSseClientTransport.class)
 * @EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
 */
@AutoConfiguration
@ConditionalOnClass(WebFluxSseClientTransport.class)
@EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
class SseWebFluxTransportAutoConfiguration {
    // Bean definitions for SSE transport with WebFlux
}

/**
 * Creates SSE transports using Spring WebFlux WebClient
 * 
 * @Bean
 * @param properties SSE client properties
 * @return List of named SSE transports using WebClient
 * 
 * WebClient Features:
 * - Reactive streams
 * - Connection pooling
 * - Retry and timeout configuration
 * - Custom codecs
 */
@Bean
List<NamedClientMcpTransport> sseWebFluxClientTransports(McpSseClientProperties properties);

Streamable HTTP Transport AutoConfiguration (HttpClient)

Configures HTTP streaming transport using Apache HttpClient for stateless MCP communication.

/**
 * Autoconfigures Streamable HTTP transport using Apache HttpClient
 * HTTP streaming for stateless request/response patterns
 * 
 * Use Cases:
 * - Load-balanced MCP servers
 * - Stateless deployments
 * - Request/response workflows
 * 
 * @AutoConfiguration
 * @ConditionalOnClass({McpSchema.class, McpSyncClient.class})
 * @EnableConfigurationProperties({McpStreamableHttpClientProperties.class, McpClientCommonProperties.class})
 */
@AutoConfiguration
@ConditionalOnClass({McpSchema.class, McpSyncClient.class})
@EnableConfigurationProperties({McpStreamableHttpClientProperties.class, McpClientCommonProperties.class})
class StreamableHttpHttpClientTransportAutoConfiguration {
    // Bean definitions for HTTP streaming transport
}

/**
 * Creates streamable HTTP transports using Apache HttpClient
 * 
 * @Bean
 * @param properties HTTP streaming client properties
 * @return List of named HTTP streaming transports
 */
@Bean
List<NamedClientMcpTransport> streamableHttpHttpClientTransports(
    McpStreamableHttpClientProperties properties
);

Streamable HTTP Transport AutoConfiguration (WebFlux)

Configures HTTP streaming transport using Spring WebFlux.

/**
 * Autoconfigures Streamable HTTP transport using Spring WebFlux
 * 
 * @AutoConfiguration
 * @ConditionalOnClass({WebClientStreamableHttpTransport.class, WebClient.class})
 * @EnableConfigurationProperties({McpStreamableHttpClientProperties.class, McpClientCommonProperties.class})
 */
@AutoConfiguration
@ConditionalOnClass({WebClientStreamableHttpTransport.class, WebClient.class})
@EnableConfigurationProperties({McpStreamableHttpClientProperties.class, McpClientCommonProperties.class})
class StreamableHttpWebFluxTransportAutoConfiguration {
    // Bean definitions for HTTP streaming transport with WebFlux
}

/**
 * Creates streamable HTTP transports using WebClient
 * 
 * @Bean
 * @param properties HTTP streaming client properties
 * @return List of named HTTP streaming transports using WebClient
 */
@Bean
List<NamedClientMcpTransport> streamableHttpWebFluxClientTransports(
    McpStreamableHttpClientProperties properties
);

MCP Client Annotation Scanner

Scans for and registers MCP client handler annotations for logging, progress, and custom handlers.

/**
 * Scans and registers MCP client annotations for handlers
 * Enables declarative MCP client event handling
 * 
 * Conditional Requirements:
 * - @ConditionalOnClass: McpLogging
 * - @ConditionalOnProperty: spring.ai.mcp.client.annotation-scanner.enabled=true (default)
 * 
 * Supported Annotations:
 * - @McpLogging: Handle logging messages from server
 * - @McpProgress: Handle progress notifications
 * - @McpResourceUpdate: Handle resource change notifications
 * - @McpPromptUpdate: Handle prompt change notifications
 * 
 * @AutoConfiguration
 * @ConditionalOnClass(McpLogging.class)
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client.annotation-scanner", 
 *                        name = "enabled", havingValue = "true", matchIfMissing = true)
 */
@AutoConfiguration
@ConditionalOnClass(McpLogging.class)
@ConditionalOnProperty(prefix = "spring.ai.mcp.client.annotation-scanner", 
                       name = "enabled", havingValue = "true", matchIfMissing = true)
class McpClientAnnotationScannerAutoConfiguration {
    // Bean definitions for annotation scanning
}

/**
 * Registry for synchronous MCP client handlers
 * Handles @McpLogging, @McpProgress, etc. annotations
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "SYNC", matchIfMissing = true)
 * @return Registry for sync client handlers
 * 
 * Handler Registration:
 * - Scans base packages for annotated methods
 * - Registers handlers with appropriate clients
 * - Validates handler signatures
 * - Supports multiple handlers per event type
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "SYNC", matchIfMissing = true)
ClientMcpSyncHandlersRegistry clientMcpSyncHandlersRegistry();

/**
 * Registry for asynchronous MCP client handlers
 * 
 * @Bean
 * @ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
 *                        havingValue = "ASYNC")
 * @return Registry for async client handlers
 */
@Bean
@ConditionalOnProperty(prefix = "spring.ai.mcp.client", name = "type", 
                       havingValue = "ASYNC")
ClientMcpAsyncHandlersRegistry clientMcpAsyncHandlersRegistry();

Configuration Properties

McpClientCommonProperties

Configuration prefix: spring.ai.mcp.client

/**
 * Common properties for MCP client configuration
 * 
 * @ConfigurationProperties(prefix = "spring.ai.mcp.client")
 */
class McpClientCommonProperties {
    /** 
     * Enable/disable the MCP client
     * Default: true
     */
    private boolean enabled = true;
    
    /** 
     * The name of the MCP client instance
     * Used in client info sent to server
     * Default: "spring-ai-mcp-client"
     */
    private String name = "spring-ai-mcp-client";
    
    /** 
     * The version of the MCP client instance
     * Used in client info sent to server
     * Default: "1.0.0"
     */
    private String version = "1.0.0";
    
    /** 
     * Whether to initialize the MCP client on startup
     * If true, client connects and exchanges capabilities immediately
     * If false, client connects on first use (lazy initialization)
     * Default: true
     */
    private boolean initialized = true;
    
    /** 
     * Request timeout duration
     * Maximum time to wait for server response
     * Default: 20s
     * Range: 1s - 5m (recommended)
     */
    private Duration requestTimeout = Duration.ofSeconds(20);
    
    /** 
     * Client type: SYNC or ASYNC
     * SYNC: Blocking operations, simpler code
     * ASYNC: Non-blocking operations, better throughput
     * Default: SYNC
     */
    private ClientType type = ClientType.SYNC;
    
    /** 
     * Enable/disable root change notifications
     * If true, client receives notifications when server roots change
     * Default: true
     */
    private boolean rootChangeNotification = true;
    
    /** 
     * Tool callback configuration
     */
    private ToolCallback toolcallback = new ToolCallback();
    
    /**
     * Tool callback configuration
     */
    static class ToolCallback {
        /** 
         * Enable/disable tool callbacks
         * If true, MCP tools are automatically registered with Spring AI
         * Default: true
         */
        private boolean enabled = true;
    }
    
    /**
     * Client type enumeration
     */
    enum ClientType {
        /** Synchronous client with blocking operations */
        SYNC,
        /** Asynchronous client with CompletableFuture */
        ASYNC
    }
}

McpStdioClientProperties

Configuration prefix: spring.ai.mcp.client.stdio

/**
 * Properties for Stdio transport configuration
 * 
 * @ConfigurationProperties(prefix = "spring.ai.mcp.client.stdio")
 */
class McpStdioClientProperties {
    /** 
     * Resource containing MCP servers configuration JSON
     * Alternative to inline connections configuration
     * 
     * JSON Format:
     * {
     *   "servers": {
     *     "filesystem": {
     *       "command": "node",
     *       "args": ["server.js"],
     *       "env": {"NODE_ENV": "production"}
     *     }
     *   }
     * }
     */
    private Resource serversConfiguration;
    
    /** 
     * Map of named Stdio connection configurations
     * Key: Connection name (used as transport name)
     * Value: Process parameters (command, args, env)
     */
    private Map<String, Parameters> connections = new HashMap<>();
    
    /**
     * Server connection parameters for Stdio transport
     * 
     * @param command Command to execute (e.g., "node", "python", "/usr/bin/my-server")
     * @param args Command arguments (e.g., ["server.js", "--port", "8080"])
     * @param env Environment variables for the process (e.g., {"NODE_ENV": "production"})
     */
    record Parameters(
        String command,
        List<String> args,
        Map<String, String> env
    ) {}
}

McpSseClientProperties

Configuration prefix: spring.ai.mcp.client.sse

/**
 * Properties for SSE transport configuration
 * 
 * @ConfigurationProperties(prefix = "spring.ai.mcp.client.sse")
 */
class McpSseClientProperties {
    /** 
     * Map of named SSE connection configurations
     * Key: Connection name (used as transport name)
     * Value: SSE parameters (url, endpoint)
     */
    private Map<String, SseParameters> connections = new HashMap<>();
    
    /**
     * SSE connection parameters
     * 
     * @param url Base URL of the SSE server (e.g., "https://mcp-server.example.com")
     * @param sseEndpoint SSE endpoint path (e.g., "/mcp/sse", "/events")
     */
    record SseParameters(
        String url,
        String sseEndpoint
    ) {}
}

McpStreamableHttpClientProperties

Configuration prefix: spring.ai.mcp.client.streamable-http

/**
 * Properties for Streamable HTTP transport configuration
 * 
 * @ConfigurationProperties(prefix = "spring.ai.mcp.client.streamable-http")
 */
class McpStreamableHttpClientProperties {
    /** 
     * Map of named HTTP streaming connection configurations
     * Key: Connection name (used as transport name)
     * Value: HTTP parameters (url, endpoint)
     */
    private Map<String, StreamableHttpParameters> connections = new HashMap<>();
    
    /**
     * HTTP streaming connection parameters
     * 
     * @param url Base URL of the HTTP server (e.g., "https://api.example.com")
     * @param endpoint HTTP endpoint path (e.g., "/mcp", "/api/mcp")
     */
    record StreamableHttpParameters(
        String url,
        String endpoint
    ) {}
}

McpClientAnnotationScannerProperties

Configuration prefix: spring.ai.mcp.client.annotation-scanner

/**
 * Properties for MCP client annotation scanning
 * 
 * @ConfigurationProperties(prefix = "spring.ai.mcp.client.annotation-scanner")
 */
class McpClientAnnotationScannerProperties {
    /** 
     * Enable/disable annotation scanning
     * Default: true
     */
    private boolean enabled = true;
    
    /** 
     * Base packages to scan for MCP annotations
     * If empty, scans all packages (slower startup)
     * Recommended: Specify application base packages
     * 
     * Example: ["com.example.mcp.handlers", "com.example.services"]
     */
    private List<String> basePackages = new ArrayList<>();
}

Configuration Examples

Stdio Transport Configuration

Connecting to a Node.js MCP Server:

# application.properties
spring.ai.mcp.client.enabled=true
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.name=my-app-client
spring.ai.mcp.client.request-timeout=30s

# Stdio connection to local Node.js server
spring.ai.mcp.client.stdio.connections.filesystem.command=node
spring.ai.mcp.client.stdio.connections.filesystem.args[0]=/path/to/filesystem-server.js

# Stdio connection to Python server with environment variables
spring.ai.mcp.client.stdio.connections.python-tools.command=python
spring.ai.mcp.client.stdio.connections.python-tools.args[0]=/path/to/server.py
spring.ai.mcp.client.stdio.connections.python-tools.env.PYTHONPATH=/custom/path
spring.ai.mcp.client.stdio.connections.python-tools.env.LOG_LEVEL=DEBUG

YAML Configuration:

spring:
  ai:
    mcp:
      client:
        enabled: true
        type: SYNC
        name: my-app-client
        request-timeout: 30s
        initialized: true
        root-change-notification: true
        stdio:
          connections:
            filesystem:
              command: node
              args:
                - /path/to/filesystem-server.js
                - --verbose
            python-tools:
              command: python
              args:
                - /path/to/server.py
              env:
                PYTHONPATH: /custom/path
                LOG_LEVEL: DEBUG

SSE Transport Configuration

Connecting to SSE MCP Server:

# application.properties
spring.ai.mcp.client.enabled=true
spring.ai.mcp.client.type=SYNC

# SSE connection
spring.ai.mcp.client.sse.connections.remote-server.url=https://mcp-server.example.com
spring.ai.mcp.client.sse.connections.remote-server.sse-endpoint=/mcp/sse

# Multiple SSE connections
spring.ai.mcp.client.sse.connections.server1.url=https://server1.example.com
spring.ai.mcp.client.sse.connections.server1.sse-endpoint=/sse
spring.ai.mcp.client.sse.connections.server2.url=https://server2.example.com
spring.ai.mcp.client.sse.connections.server2.sse-endpoint=/events

YAML Configuration:

spring:
  ai:
    mcp:
      client:
        enabled: true
        type: SYNC
        sse:
          connections:
            remote-server:
              url: https://mcp-server.example.com
              sse-endpoint: /mcp/sse
            server1:
              url: https://server1.example.com
              sse-endpoint: /sse
            server2:
              url: https://server2.example.com
              sse-endpoint: /events

HTTP Streaming Transport Configuration

# application.properties
spring.ai.mcp.client.enabled=true
spring.ai.mcp.client.type=SYNC

# HTTP streaming connection
spring.ai.mcp.client.streamable-http.connections.api-server.url=https://api.example.com
spring.ai.mcp.client.streamable-http.connections.api-server.endpoint=/mcp

Asynchronous Client Configuration

spring:
  ai:
    mcp:
      client:
        enabled: true
        type: ASYNC  # Use async client
        name: async-mcp-client
        request-timeout: 45s
        stdio:
          connections:
            async-server:
              command: node
              args:
                - /path/to/async-server.js

Tool Callback Configuration

# Enable/disable tool callback integration
spring.ai.mcp.client.toolcallback.enabled=true

# Annotation scanner configuration
spring.ai.mcp.client.annotation-scanner.enabled=true
spring.ai.mcp.client.annotation-scanner.base-packages[0]=com.example.mcp.handlers
spring.ai.mcp.client.annotation-scanner.base-packages[1]=com.example.services

Complete Configuration Example

spring:
  ai:
    mcp:
      client:
        # Core settings
        enabled: true
        type: SYNC
        name: production-mcp-client
        version: 2.0.0
        initialized: true
        request-timeout: 60s
        root-change-notification: true
        
        # Stdio connections
        stdio:
          connections:
            filesystem:
              command: node
              args:
                - /opt/mcp/filesystem-server.js
                - --root
                - /data
              env:
                NODE_ENV: production
                LOG_LEVEL: info
            
            database:
              command: python
              args:
                - /opt/mcp/database-server.py
                - --config
                - /etc/mcp/db-config.json
              env:
                PYTHONPATH: /opt/mcp/lib
                DB_CONNECTION_POOL_SIZE: "20"
        
        # SSE connections
        sse:
          connections:
            remote-api:
              url: https://mcp-api.example.com
              sse-endpoint: /mcp/stream
            
            backup-api:
              url: https://backup-mcp.example.com
              sse-endpoint: /events
        
        # HTTP streaming connections
        streamable-http:
          connections:
            stateless-api:
              url: https://stateless-mcp.example.com
              endpoint: /mcp/http
        
        # Tool callbacks
        toolcallback:
          enabled: true
        
        # Annotation scanner
        annotation-scanner:
          enabled: true
          base-packages:
            - com.example.mcp
            - com.example.handlers

Usage Examples

Using MCP Sync Client

import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.stereotype.Service;

@Service
public class McpClientService {
    private final List<McpSyncClient> mcpClients;
    
    public McpClientService(List<McpSyncClient> mcpClients) {
        this.mcpClients = mcpClients;
    }
    
    public void listTools() {
        for (McpSyncClient client : mcpClients) {
            // List available tools from this MCP server
            McpSchema.ListToolsResult tools = client.listTools();
            tools.tools().forEach(tool -> {
                System.out.println("Tool: " + tool.name());
                System.out.println("Description: " + tool.description());
                System.out.println("Input Schema: " + tool.inputSchema());
            });
        }
    }
    
    public McpSchema.CallToolResult callTool(String toolName, Map<String, Object> arguments) {
        // Find client that has this tool
        for (McpSyncClient client : mcpClients) {
            McpSchema.ListToolsResult tools = client.listTools();
            boolean hasTool = tools.tools().stream()
                .anyMatch(t -> t.name().equals(toolName));
                
            if (hasTool) {
                // Call the tool
                return client.callTool(
                    new McpSchema.CallToolRequest(toolName, arguments)
                );
            }
        }
        throw new IllegalArgumentException("Tool not found: " + toolName);
    }
    
    public List<McpSchema.Prompt> listPrompts() {
        List<McpSchema.Prompt> allPrompts = new ArrayList<>();
        for (McpSyncClient client : mcpClients) {
            McpSchema.ListPromptsResult prompts = client.listPrompts();
            allPrompts.addAll(prompts.prompts());
        }
        return allPrompts;
    }
    
    public McpSchema.GetPromptResult getPrompt(String promptName, Map<String, String> arguments) {
        for (McpSyncClient client : mcpClients) {
            try {
                return client.getPrompt(
                    new McpSchema.GetPromptRequest(promptName, arguments)
                );
            } catch (Exception e) {
                // Try next client
            }
        }
        throw new IllegalArgumentException("Prompt not found: " + promptName);
    }
}

Using MCP Tools with Spring AI ChatClient

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.client.McpToolCallbackProvider;
import org.springframework.stereotype.Service;

@Service
public class ChatWithMcpToolsService {
    private final ChatClient chatClient;
    
    public ChatWithMcpToolsService(
            ChatClient.Builder chatClientBuilder,
            SyncMcpToolCallbackProvider mcpToolProvider) {
        
        // ChatClient automatically has access to MCP tools via toolcallback provider
        this.chatClient = chatClientBuilder
            .defaultFunctions(mcpToolProvider.getToolCallbacks())
            .build();
    }
    
    public String chat(String userMessage) {
        // AI can automatically call MCP tools as needed
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
    
    public String chatWithSpecificTools(String userMessage, List<String> toolNames) {
        // Use only specific tools for this conversation
        return chatClient.prompt()
            .user(userMessage)
            .functions(toolNames.toArray(new String[0]))
            .call()
            .content();
    }
}

Using MCP Async Client

import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.concurrent.CompletableFuture;

@Service
public class McpAsyncClientService {
    private final List<McpAsyncClient> mcpClients;
    
    public McpAsyncClientService(List<McpAsyncClient> mcpClients) {
        this.mcpClients = mcpClients;
    }
    
    public CompletableFuture<McpSchema.ListToolsResult> listToolsAsync() {
        if (mcpClients.isEmpty()) {
            return CompletableFuture.completedFuture(null);
        }
        
        McpAsyncClient client = mcpClients.get(0);
        return client.listTools();
    }
    
    public CompletableFuture<McpSchema.CallToolResult> callToolAsync(
            String toolName, 
            Map<String, Object> arguments) {
        
        if (mcpClients.isEmpty()) {
            return CompletableFuture.failedFuture(
                new IllegalStateException("No MCP clients available")
            );
        }
        
        McpAsyncClient client = mcpClients.get(0);
        McpSchema.CallToolRequest request = new McpSchema.CallToolRequest(
            toolName, 
            arguments
        );
        
        return client.callTool(request);
    }
    
    public CompletableFuture<List<McpSchema.CallToolResult>> callMultipleToolsAsync(
            List<String> toolNames,
            Map<String, Object> arguments) {
        
        List<CompletableFuture<McpSchema.CallToolResult>> futures = toolNames.stream()
            .map(toolName -> callToolAsync(toolName, arguments))
            .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList())
            );
    }
}

Custom Client Configurer

import io.modelcontextprotocol.client.McpSyncClient;
import org.springframework.ai.mcp.client.common.autoconfigure.McpSyncClientConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomMcpClientConfig {
    
    @Bean
    public McpSyncClientConfigurer customClientConfigurer() {
        return new McpSyncClientConfigurer() {
            @Override
            public void configure(McpSyncClient.Builder builder, String transportName) {
                // Customize client configuration based on transport name
                builder.clientInfo(
                    new McpSchema.Implementation(
                        "custom-client-" + transportName,
                        "2.0.0"
                    )
                );
                
                // Add custom configuration for specific transports
                if ("filesystem".equals(transportName)) {
                    builder.requestTimeout(Duration.ofMinutes(5));
                } else if ("database".equals(transportName)) {
                    builder.requestTimeout(Duration.ofSeconds(30));
                }
                
                // Add custom capabilities
                builder.capabilities(
                    McpSchema.ClientCapabilities.builder()
                        .roots(McpSchema.RootsCapability.builder()
                            .listChanged(true)
                            .build())
                        .build()
                );
            }
        };
    }
}

MCP Client Handler Annotations

import org.springframework.ai.mcp.client.annotation.McpLogging;
import org.springframework.ai.mcp.client.annotation.McpProgress;
import org.springframework.stereotype.Component;

@Component
@McpLogging
public class McpClientHandlers {
    
    @McpProgress
    public void handleProgress(McpSchema.ProgressNotification progress) {
        System.out.println("Progress: " + progress.progress() + 
                         " of " + progress.total());
        System.out.println("Token: " + progress.progressToken());
    }
    
    public void handleLog(McpSchema.LoggingMessageNotification log) {
        System.out.println("[" + log.level() + "] " + log.data());
        if (log.logger() != null) {
            System.out.println("Logger: " + log.logger());
        }
    }
    
    @McpResourceUpdate
    public void handleResourceUpdate(McpSchema.ResourceUpdatedNotification notification) {
        System.out.println("Resource updated: " + notification.uri());
        // Refresh resource cache
    }
    
    @McpPromptUpdate
    public void handlePromptUpdate(McpSchema.PromptListChangedNotification notification) {
        System.out.println("Prompt list changed");
        // Refresh prompt cache
    }
}

Conditional Requirements

MCP client modules activate when:

  1. Class Present: io.modelcontextprotocol.spec.McpSchema on classpath
  2. Property Enabled: spring.ai.mcp.client.enabled=true (default)
  3. Transport Available:
    • Stdio: Always available with common module
    • SSE (HttpClient): Apache HttpClient on classpath
    • SSE (WebFlux): Spring WebFlux on classpath
    • HTTP (HttpClient): Apache HttpClient on classpath
    • HTTP (WebFlux): Spring WebFlux and WebClient on classpath

Transport Selection

Choose transport based on your deployment scenario:

TransportUse CaseProsCons
StdioLocal MCP servers (Node.js, Python)Simple, no network, fastLocal only, process management
SSERemote MCP servers, real-time updatesPersistent connection, server pushRequires SSE support, stateful
HTTP StreamingLoad-balanced deploymentsStateless, scalableNo server push, request overhead

You can use multiple transports simultaneously - the autoconfiguration will create clients for all configured connections.

Best Practices

1. Use Appropriate Client Type

# For simple, synchronous workflows
spring.ai.mcp.client.type=SYNC

# For high-throughput, async workflows
spring.ai.mcp.client.type=ASYNC

2. Configure Timeouts Appropriately

# Short timeout for fast operations
spring.ai.mcp.client.request-timeout=10s

# Long timeout for slow operations (file processing, etc.)
spring.ai.mcp.client.request-timeout=5m

3. Use Tool Name Prefixes

@Bean
public McpToolNamePrefixGenerator prefixGenerator() {
    return transportName -> transportName.toUpperCase() + "_";
}

4. Handle Connection Failures

@Service
public class ResilientMcpService {
    private final List<McpSyncClient> clients;
    private final RetryTemplate retryTemplate;
    
    public McpSchema.CallToolResult callToolWithRetry(String toolName, Map<String, Object> args) {
        return retryTemplate.execute(context -> {
            for (McpSyncClient client : clients) {
                try {
                    return client.callTool(new McpSchema.CallToolRequest(toolName, args));
                } catch (Exception e) {
                    // Try next client
                }
            }
            throw new RuntimeException("All clients failed");
        });
    }
}

5. Monitor Client Health

@Component
public class McpClientHealthIndicator implements HealthIndicator {
    private final List<McpSyncClient> clients;
    
    @Override
    public Health health() {
        int healthy = 0;
        for (McpSyncClient client : clients) {
            try {
                client.ping();
                healthy++;
            } catch (Exception e) {
                // Client unhealthy
            }
        }
        
        if (healthy == clients.size()) {
            return Health.up().withDetail("clients", healthy).build();
        } else if (healthy > 0) {
            return Health.degraded()
                .withDetail("healthy", healthy)
                .withDetail("total", clients.size())
                .build();
        } else {
            return Health.down()
                .withDetail("healthy", 0)
                .withDetail("total", clients.size())
                .build();
        }
    }
}

Troubleshooting

Issue: Client Not Connecting

Diagnostic:

# Enable debug logging
logging.level.org.springframework.ai.mcp=DEBUG
logging.level.io.modelcontextprotocol=DEBUG

Common Causes:

  1. Incorrect command/args for stdio
  2. Server not running for SSE/HTTP
  3. Network/firewall issues
  4. Timeout too short

Issue: Tools Not Available in ChatClient

Check:

# Ensure tool callbacks are enabled
spring.ai.mcp.client.toolcallback.enabled=true

# Verify client type matches provider
spring.ai.mcp.client.type=SYNC

Issue: Slow Performance

Solutions:

# Use async client for better throughput
spring.ai.mcp.client.type=ASYNC

# Increase timeout for slow operations
spring.ai.mcp.client.request-timeout=2m

# Use HTTP streaming for stateless operations

Summary

MCP client autoconfiguration provides comprehensive support for connecting to MCP servers with:

  • Multiple transport mechanisms (stdio, SSE, HTTP)
  • Synchronous and asynchronous operation modes
  • Automatic tool integration with Spring AI
  • Flexible configuration and customization
  • Production-ready error handling and monitoring

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-spring-boot-autoconfigure

docs

index.md

tile.json