- Notifications
You must be signed in to change notification settings - Fork 2k
Add McpClientHandlersRegistry #4802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add McpClientHandlersRegistry #4802
Conversation
cfce918 to 4835ccc Compare 4835ccc to 2b1f49b Compare 52676cd to 18e986a Compare Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
- In febf86c, we broke a dependency cycle ChatClient -> McpClient - With the introduction of ClientMcpSyncHandlersRegistry and the async variant, there is no dependency McpClient -> MCP handlers anymore, breaking the cycle in a simpler way. - Here, we revert most of the changes of febf86c, but keep the tests. Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
ad8d987 to 4ab6503 Compare
tzolov left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks @Kehrlann
There are some small formatting issues I'll resolve while merging
| if (!foundAnnotations.isEmpty()) { | ||
| this.allAnnotatedBeans.add(beanName); | ||
| } | ||
| for (var foundAnnotation : foundAnnotations) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this block can be nested in side the if (!foundAnnotations.isEmpty()) { above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's already pretty nested ; I'm reluctant to nest it more.
The code is functionally equivalent anyway.
But that's not a strong opinion.
...main/java/org/springframework/ai/mcp/annotation/spring/AbstractClientMcpHandlerRegistry.java Outdated Show resolved Hide resolved
...main/java/org/springframework/ai/mcp/annotation/spring/AbstractClientMcpHandlerRegistry.java Outdated Show resolved Hide resolved
...c/main/java/org/springframework/ai/mcp/annotation/spring/ClientMcpAsyncHandlersRegistry.java Outdated Show resolved Hide resolved
...rc/main/java/org/springframework/ai/mcp/annotation/spring/ClientMcpSyncHandlersRegistry.java Outdated Show resolved Hide resolved
- Remove custom class resolution method and use AutoProxyUtils instead Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
0bdeea7 to fa6a1f4 Compare Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
5baface to 3929649 Compare …es (#4802) Replace specification factory pattern with centralized handler registries that scan and register MCP client handlers (sampling, elicitation, logging, progress, and list-changed notifications). This simplifies the auto-configuration by: - Introducing ClientMcpSyncHandlersRegistry and ClientMcpAsyncHandlersRegistry that scan beans once for annotations and expose handlers by client name - Removing intermediate specification factory beans and customizers - Directly configuring MCP client specs from registries during client creation - Eliminating need for separate specification classes per handler type - Simplifying ToolCallingAutoConfiguration by removing BeanDefinitionRegistryPostProcessor complexity - In febf86c, we broke a dependency cycle ChatClient -> McpClient - With the introduction of ClientMcpSyncHandlersRegistry and the async variant, there is no dependency McpClient -> MCP handlers anymore, breaking the cycle in a simpler way. - Here, we revert most of the changes of febf86c, but keep the tests. - Remove unused MCP annotated beans auto-configuration - Introduce AbstractClientMcpHandlerRegistry - Find MCP Client annotations on @component beans - AbstractClientMcpHandlerRegistry also discovers proxied beans - Remove custom class resolution method and use AutoProxyUtils instead - Add logging to MCP handlers registry - Throw MCP Error on missing sampling and elicitation handlers in a client - Fix missing auto-configurations McpClientAutoConfigurationIT Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
| Rebased, squashed and merged at 2a2f155 |
Fixes #4670
This PR introduces two variants (sync and async) of a "registry" of handlers,
ClientMcpSyncHandlersRegistry.These registries have two main responsibilities:
This allows to break the dependency between MCP clients and MCP-annotated beans. This was a problem when doing sampling, where there was a cycle MCP Client -> MCP handlers -> ChatClient -> MCP Client.
With this PR, the MCP clients depend on the registry, but the registry does not depend on the MCP-handlers. Instead, it discovers them dynamically and makes handlers available by client name. See #4751 for the initial attempt at fixing the circular dependency.
The flow to capture annotations has two steps:
BeanFactoryPostProcessor. Bean types are scanned for annotations, and the bean names found are recorded for later use. Additionally,@McpSamplingand@McpElicitationannotations are tracked per client name. This allows to bootstrap MCP clients in theMcpClientAutoConfiguration: by the time the clients are created, we know their capabilities, even if the annotated beans have not been instantiated yet.SmartInitializingSingleton, actual bean instances are retrieved, and from the bean instances and annotated methods, MCP handlers are populated in the registry, stored by client name.Notes: