-
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 <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
- 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 <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
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 <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
0bdeea7 to
fa6a1f4
Compare
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
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 <[email protected]>
|
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: