2323import org .springaicommunity .mcp .security .client .sync .oauth2 .http .client .OAuth2AuthorizationCodeSyncHttpRequestCustomizer ;
2424
2525import org .springframework .ai .mcp .customizer .McpSyncClientCustomizer ;
26+ import org .springframework .ai .model .anthropic .autoconfigure .AnthropicChatAutoConfiguration ;
27+ import org .springframework .ai .model .tool .autoconfigure .ToolCallingAutoConfiguration ;
2628import org .springframework .ai .tool .resolution .StaticToolCallbackResolver ;
2729import org .springframework .ai .tool .resolution .ToolCallbackResolver ;
2830import org .springframework .context .annotation .Bean ;
3436@ Configuration
3537class McpConfiguration {
3638
39+ /**
40+ * If the default {@link ToolCallbackResolver} from
41+ * {@link ToolCallingAutoConfiguration} is imported, then all MCP-based tools are
42+ * added to the resolver. In order to do so, the {@link ToolCallbackResolver} bean
43+ * lists all MCP tools, therefore initializing MCP clients and listing the tools.
44+ * <p>
45+ * This is an issue when the MCP server is secured with OAuth2, because to obtain a
46+ * token, a user must be involved in the flow, and there is no user present on app
47+ * startup.
48+ * <p>
49+ * To avoid this issue, we must exclude the default {@link ToolCallbackResolver}. We
50+ * can't easily disable the entire {@link ToolCallingAutoConfiguration} class, because
51+ * it is imported directly by the chat model configurations, such as
52+ * {@link AnthropicChatAutoConfiguration}. Instead, we provide a default, no-op bean.
53+ */
54+ @ Bean
55+ ToolCallbackResolver resolver () {
56+ return new StaticToolCallbackResolver (List .of ());
57+ }
58+
3759 @ Bean
3860 McpSyncHttpClientRequestCustomizer requestCustomizer (OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager ,
3961 ClientRegistrationRepository clientRegistrationRepository ) {
@@ -46,6 +68,10 @@ McpSyncClientCustomizer syncClientCustomizer() {
4668 return (name , syncSpec ) -> syncSpec .transportContextProvider (new AuthenticationMcpTransportContextProvider ());
4769 }
4870
71+ /**
72+ * Returns the ID of the {@code spring.security.oauth2.client.registration}, if
73+ * unique.
74+ */
4975 private static String findUniqueClientRegistration (ClientRegistrationRepository clientRegistrationRepository ) {
5076 String registrationId ;
5177 if (!(clientRegistrationRepository instanceof InMemoryClientRegistrationRepository repo )) {
0 commit comments