Spring AI Spring Boot Auto Configuration modules providing automatic setup for AI models, vector stores, MCP, and retry capabilities
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.
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.
The MCP client functionality is split across three modules for flexibility and minimal dependencies:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-client-common</artifactId>
<version>1.1.2</version>
</dependency><dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-client-httpclient</artifactId>
<version>1.1.2</version>
</dependency><dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-client-webflux</artifactId>
<version>1.1.2</version>
</dependency>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
}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
};
}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();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
);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;
}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);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);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
);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
);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 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
}
}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
) {}
}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
) {}
}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
) {}
}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<>();
}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=DEBUGYAML 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: DEBUGConnecting 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=/eventsYAML 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# 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=/mcpspring:
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# 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.servicesspring:
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.handlersimport 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);
}
}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();
}
}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())
);
}
}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()
);
}
};
}
}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
}
}MCP client modules activate when:
io.modelcontextprotocol.spec.McpSchema on classpathspring.ai.mcp.client.enabled=true (default)Choose transport based on your deployment scenario:
| Transport | Use Case | Pros | Cons |
|---|---|---|---|
| Stdio | Local MCP servers (Node.js, Python) | Simple, no network, fast | Local only, process management |
| SSE | Remote MCP servers, real-time updates | Persistent connection, server push | Requires SSE support, stateful |
| HTTP Streaming | Load-balanced deployments | Stateless, scalable | No server push, request overhead |
You can use multiple transports simultaneously - the autoconfiguration will create clients for all configured connections.
# For simple, synchronous workflows
spring.ai.mcp.client.type=SYNC
# For high-throughput, async workflows
spring.ai.mcp.client.type=ASYNC# 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@Bean
public McpToolNamePrefixGenerator prefixGenerator() {
return transportName -> transportName.toUpperCase() + "_";
}@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");
});
}
}@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();
}
}
}Diagnostic:
# Enable debug logging
logging.level.org.springframework.ai.mcp=DEBUG
logging.level.io.modelcontextprotocol=DEBUGCommon Causes:
Check:
# Ensure tool callbacks are enabled
spring.ai.mcp.client.toolcallback.enabled=true
# Verify client type matches provider
spring.ai.mcp.client.type=SYNCSolutions:
# 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 operationsMCP client autoconfiguration provides comprehensive support for connecting to MCP servers with: