feat(mcp): Add @McpClient qualifier for selective client injection#5202
feat(mcp): Add @McpClient qualifier for selective client injection#5202ultramancode wants to merge 2 commits intospring-projects:1.1.xfrom
@McpClient qualifier for selective client injection#5202Conversation
Enables selective injection of individual MCP clients by connection name using the @mcpclient qualifier annotation. This allows different ChatClient instances to use specific MCP servers in multi-agent systems via declarative injection. Changes: - Add @mcpclient custom qualifier annotation - Add McpConnectionBeanRegistrar for dynamic bean registration - Add McpSyncClientFactory to centralize client creation logic - Add NamedMcpSyncClientFactoryBean for lazy client instantiation - Refactor McpClientAutoConfiguration to utilize the new components - Add unit tests for the registrar and factory beans Signed-off-by: Taewoong Kim <ktw2172@gmail.com>
e1f433d to
01f170e
Compare
- Add McpAsyncClientFactory for async client creation - Add NamedMcpAsyncClientFactoryBean for named async injection - Update McpConnectionBeanRegistrar to register async beans when type=ASYNC - Add async client tests Signed-off-by: Taewoong Kim <ktw2172@gmail.com>
01f170e to
886bc40
Compare
|
I wanted to follow up and see if this I originally targeted 1.1.x to match my current production environment, but observing the transition of main toward 2.0.0, I believe this feature might be better suited for the new milestone. If you think this is a good addition to the project, would you prefer I open a fresh PR targeting main? Thanks! |
@McpClient qualifier for selective client injection
|
@ultramancode Thanks for the PR! The MCP annotations support is part of the Spring AI community project: https://github.com/spring-ai-community/mcp-annotations. Could you check and submit a PR at mcp-annotations project instead? |
|
Thanks for the review! @ilayaperumalg I understand your point regarding the annotations. However, I’d like to clarify the technical scope of this PR. This PR focuses on Spring Boot Auto-Configuration and Bean Registration. The goal is to enable named bean registration and selective injection for multiple The Mcp-annotations project focuses on the method handler programming model. In contrast, this PR modifies the infrastructure layer—specifically how client beans are instantiated at boot time. Could you please reconsider the assessment with this context in mind? |
feat(mcp): Add
@McpClientqualifier for selective client injectionCloses #5201
Summary
This PR adds a
@McpClient("connectionName")qualifier annotation that enables selective injection of individual MCP client beans (bothMcpSyncClientandMcpAsyncClient), addressing the need for multi-agent systems where different agents require different MCP servers.Changes
New Files
mcp/common/.../McpClient.javaauto-configurations/.../McpSyncClientFactory.javaauto-configurations/.../NamedMcpSyncClientFactoryBean.javaauto-configurations/.../McpAsyncClientFactory.javaauto-configurations/.../NamedMcpAsyncClientFactoryBean.javaauto-configurations/.../McpConnectionBeanRegistrar.javaBeanDefinitionRegistryPostProcessorfor early bean registrationModified Files
McpClientAutoConfiguration.javaMcpSyncClientFactory,McpAsyncClientFactory, and registrar beans for individual client injectionBefore & After
List<McpSyncClient> all@McpClient("name") McpSyncClientList<McpAsyncClient> all@McpClient("name") McpAsyncClientNote: The existing
List<McpSyncClient>andList<McpAsyncClient>beans remain unchanged. This PR adds an additional injection method, not a replacement.Usage
Sync Mode (default)
Async Mode
spring.ai.mcp.client.type=ASYNCTechnical Considerations
1. FactoryBean + BeanDefinitionRegistryPostProcessor Pattern
I tried simpler approaches first, but they all failed:
@Bean Map<String, McpSyncClient>@Qualifierresolution targets individual beans in the context, not entries within a Map-typed bean. This is by design—@QualifierusesBeanFactory.getBean(type, qualifier), not Map lookup.beanFactory.registerSingleton()BeanDefinition, preventing attachment of the custom@McpClientqualifier metadata. Also requires immediate object instantiation, which is impossible before dependencies are ready.BeanDefinitionRegistryPostProcessorruns before regular beans are instantiated. Dependencies likeMcpSyncClientConfigurerandClientMcpSyncHandlersRegistryare not yet available.The FactoryBean pattern solves this by:
2. Setter Injection in FactoryBean
The
McpConnectionBeanRegistraronly knows theconnectionNameat registration time. Complex dependencies likeMcpSyncClientConfigurer,McpClientCommonProperties, andClientMcpSyncHandlersRegistryare not available yet. Setter injection solves this:3. Centralized Client Creation Logic
I introduced
McpSyncClientFactoryandMcpAsyncClientFactoryto encapsulate all client creation logic. This eliminates code duplication and ensures consistent configuration.Separation of concerns:
4. Conditional Sync/Async Bean Registration
McpConnectionBeanRegistrarreadsspring.ai.mcp.client.typeand registers the appropriate FactoryBean:5. Dual Qualifier Registration
Both
@McpClientand@Qualifiermetadata are registered for flexibility:Both annotations are therefore enabled by this PR, which registers individual named beans.
@McpClient("name")@Qualifier("name")6. Static Bean Method for Registrar
The registrar bean method is
staticbecauseBeanDefinitionRegistryPostProcessorbeans must be registered very early in the Spring lifecycle. Static@Beanmethods are processed before instance methods, ensuring the registrar runs before other beans are created.7. No Explicit destroyMethod
I initially set
destroyMethod("close"), but it causedBeanDefinitionValidationException:Spring validates
destroyMethodagainst the FactoryBean class, not the produced object. SinceMcpSyncClientandMcpAsyncClientareAutoCloseable, Spring automatically infers and callsclose()on the created client (no explicit configuration needed).Testing
Integration Test:
Sample Configuration:
Bean Registration Log(Sync):

Bean Registration Log(Async):
