Skip to content

Commit 1a356be

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents 902ceff + b5a37b6 commit 1a356be

File tree

19 files changed

+282
-119
lines changed

19 files changed

+282
-119
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,30 @@
2424
import org.springaicommunity.mcp.annotation.McpProgress;
2525
import org.springaicommunity.mcp.annotation.McpSampling;
2626

27+
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor;
2728
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor;
2829
import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans;
30+
import org.springframework.aot.hint.MemberCategory;
31+
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2933
import org.springframework.boot.autoconfigure.AutoConfiguration;
3034
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3135
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3236
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3337
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3438
import org.springframework.context.annotation.Bean;
39+
import org.springframework.context.annotation.ImportRuntimeHints;
3540

3641
/**
3742
* @author Christian Tzolov
43+
* @author Josh Long
3844
*/
3945
@AutoConfiguration
4046
@ConditionalOnClass(McpLogging.class)
4147
@ConditionalOnProperty(prefix = McpClientAnnotationScannerProperties.CONFIG_PREFIX, name = "enabled",
4248
havingValue = "true", matchIfMissing = true)
4349
@EnableConfigurationProperties(McpClientAnnotationScannerProperties.class)
50+
@ImportRuntimeHints(McpClientAnnotationScannerAutoConfiguration.AnnotationHints.class)
4451
public class McpClientAnnotationScannerAutoConfiguration {
4552

4653
private static final Set<Class<? extends Annotation>> CLIENT_MCP_ANNOTATIONS = Set.of(McpLogging.class,
@@ -54,15 +61,30 @@ public ClientMcpAnnotatedBeans clientAnnotatedBeans() {
5461

5562
@Bean
5663
@ConditionalOnMissingBean
57-
public ClientAnnotatedMethodBeanPostProcessor clientAnnotatedMethodBeanPostProcessor(
64+
public static ClientAnnotatedMethodBeanPostProcessor clientAnnotatedMethodBeanPostProcessor(
5865
ClientMcpAnnotatedBeans clientMcpAnnotatedBeans, McpClientAnnotationScannerProperties properties) {
5966
return new ClientAnnotatedMethodBeanPostProcessor(clientMcpAnnotatedBeans, CLIENT_MCP_ANNOTATIONS);
6067
}
6168

69+
@Bean
70+
static ClientAnnotatedBeanFactoryInitializationAotProcessor clientAnnotatedBeanFactoryInitializationAotProcessor() {
71+
return new ClientAnnotatedBeanFactoryInitializationAotProcessor(CLIENT_MCP_ANNOTATIONS);
72+
}
73+
6274
public static class ClientMcpAnnotatedBeans extends AbstractMcpAnnotatedBeans {
6375

6476
}
6577

78+
public static class ClientAnnotatedBeanFactoryInitializationAotProcessor
79+
extends AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor {
80+
81+
public ClientAnnotatedBeanFactoryInitializationAotProcessor(
82+
Set<Class<? extends Annotation>> targetAnnotations) {
83+
super(targetAnnotations);
84+
}
85+
86+
}
87+
6688
public static class ClientAnnotatedMethodBeanPostProcessor extends AbstractAnnotatedMethodBeanPostProcessor {
6789

6890
public ClientAnnotatedMethodBeanPostProcessor(ClientMcpAnnotatedBeans clientMcpAnnotatedBeans,
@@ -72,4 +94,13 @@ public ClientAnnotatedMethodBeanPostProcessor(ClientMcpAnnotatedBeans clientMcpA
7294

7395
}
7496

97+
static class AnnotationHints implements RuntimeHintsRegistrar {
98+
99+
@Override
100+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
101+
CLIENT_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values()));
102+
}
103+
104+
}
105+
75106
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/SseHttpClientTransportAutoConfigurationIT.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ void streamableHttpTest() {
9494

9595
mcpClient.ping();
9696

97-
System.out.println("mcpClient = " + mcpClient.getServerInfo());
98-
9997
ListToolsResult toolsResult = mcpClient.listTools();
10098

10199
assertThat(toolsResult).isNotNull();

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ void streamableHttpTest() {
9595

9696
mcpClient.ping();
9797

98-
System.out.println("mcpClient = " + mcpClient.getServerInfo());
99-
10098
ListToolsResult toolsResult = mcpClient.listTools();
10199

102100
assertThat(toolsResult).isNotNull();

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfigurationIT.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ void streamableHttpTest() {
8181

8282
mcpClient.ping();
8383

84-
System.out.println("mcpClient = " + mcpClient.getServerInfo());
85-
8684
ListToolsResult toolsResult = mcpClient.listTools();
8785

8886
assertThat(toolsResult).isNotNull();

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ void streamableHttpTest() {
8282

8383
mcpClient.ping();
8484

85-
System.out.println("mcpClient = " + mcpClient.getServerInfo());
86-
8785
ListToolsResult toolsResult = mcpClient.listTools();
8886

8987
assertThat(toolsResult).isNotNull();

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,30 @@
2424
import org.springaicommunity.mcp.annotation.McpResource;
2525
import org.springaicommunity.mcp.annotation.McpTool;
2626

27+
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor;
2728
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor;
2829
import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans;
30+
import org.springframework.aot.hint.MemberCategory;
31+
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2933
import org.springframework.boot.autoconfigure.AutoConfiguration;
3034
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3135
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3236
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3337
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3438
import org.springframework.context.annotation.Bean;
39+
import org.springframework.context.annotation.ImportRuntimeHints;
3540

3641
/**
3742
* @author Christian Tzolov
43+
* @author Josh Long
3844
*/
3945
@AutoConfiguration
4046
@ConditionalOnClass(McpTool.class)
4147
@ConditionalOnProperty(prefix = McpServerAnnotationScannerProperties.CONFIG_PREFIX, name = "enabled",
4248
havingValue = "true", matchIfMissing = true)
4349
@EnableConfigurationProperties(McpServerAnnotationScannerProperties.class)
50+
@ImportRuntimeHints(McpServerAnnotationScannerAutoConfiguration.AnnotationHints.class)
4451
public class McpServerAnnotationScannerAutoConfiguration {
4552

4653
private static final Set<Class<? extends Annotation>> SERVER_MCP_ANNOTATIONS = Set.of(McpTool.class,
@@ -54,15 +61,30 @@ public ServerMcpAnnotatedBeans serverAnnotatedBeanRegistry() {
5461

5562
@Bean
5663
@ConditionalOnMissingBean
57-
public ServerAnnotatedMethodBeanPostProcessor serverAnnotatedMethodBeanPostProcessor(
64+
public static ServerAnnotatedMethodBeanPostProcessor serverAnnotatedMethodBeanPostProcessor(
5865
ServerMcpAnnotatedBeans serverMcpAnnotatedBeans, McpServerAnnotationScannerProperties properties) {
5966
return new ServerAnnotatedMethodBeanPostProcessor(serverMcpAnnotatedBeans, SERVER_MCP_ANNOTATIONS);
6067
}
6168

69+
@Bean
70+
public static ServerAnnotatedBeanFactoryInitializationAotProcessor serverAnnotatedBeanFactoryInitializationAotProcessor() {
71+
return new ServerAnnotatedBeanFactoryInitializationAotProcessor(SERVER_MCP_ANNOTATIONS);
72+
}
73+
6274
public static class ServerMcpAnnotatedBeans extends AbstractMcpAnnotatedBeans {
6375

6476
}
6577

78+
public static class ServerAnnotatedBeanFactoryInitializationAotProcessor
79+
extends AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor {
80+
81+
public ServerAnnotatedBeanFactoryInitializationAotProcessor(
82+
Set<Class<? extends Annotation>> targetAnnotations) {
83+
super(targetAnnotations);
84+
}
85+
86+
}
87+
6688
public static class ServerAnnotatedMethodBeanPostProcessor extends AbstractAnnotatedMethodBeanPostProcessor {
6789

6890
public ServerAnnotatedMethodBeanPostProcessor(ServerMcpAnnotatedBeans serverMcpAnnotatedBeans,
@@ -72,4 +94,13 @@ public ServerAnnotatedMethodBeanPostProcessor(ServerMcpAnnotatedBeans serverMcpA
7294

7395
}
7496

97+
static class AnnotationHints implements RuntimeHintsRegistrar {
98+
99+
@Override
100+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
101+
SERVER_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values()));
102+
}
103+
104+
}
105+
75106
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ static class SyncServerSpecificationConfiguration {
5454
@Bean
5555
public List<McpServerFeatures.SyncResourceSpecification> resourceSpecs(
5656
ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
57-
return SyncMcpAnnotationProviders
57+
58+
List<McpServerFeatures.SyncResourceSpecification> syncResourceSpecifications = SyncMcpAnnotationProviders
5859
.resourceSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpResource.class));
60+
return syncResourceSpecifications;
5961
}
6062

6163
@Bean
@@ -75,8 +77,10 @@ public List<McpServerFeatures.SyncCompletionSpecification> completionSpecs(
7577
@Bean
7678
public List<McpServerFeatures.SyncToolSpecification> toolSpecs(
7779
ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
78-
return SyncMcpAnnotationProviders
79-
.toolSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class));
80+
List<Object> beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class);
81+
List<McpServerFeatures.SyncToolSpecification> syncToolSpecifications = SyncMcpAnnotationProviders
82+
.toolSpecifications(beansByAnnotation);
83+
return syncToolSpecifications;
8084
}
8185

8286
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ public List<McpStatelessServerFeatures.SyncCompletionSpecification> completionSp
7777
@Bean
7878
public List<McpStatelessServerFeatures.SyncToolSpecification> toolSpecs(
7979
ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
80-
return SyncMcpAnnotationProviders
81-
.statelessToolSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class));
80+
List<Object> beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class);
81+
List<McpStatelessServerFeatures.SyncToolSpecification> syncToolSpecifications = SyncMcpAnnotationProviders
82+
.statelessToolSpecifications(beansByAnnotation);
83+
return syncToolSpecifications;
8284
}
8385

8486
}

auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,33 @@ public class OpenAiChatAutoConfiguration {
6666

6767
@Bean
6868
@ConditionalOnMissingBean
69-
public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties,
70-
OpenAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
71-
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ToolCallingManager toolCallingManager,
72-
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
69+
public OpenAiApi openAiApi(OpenAiConnectionProperties commonProperties, OpenAiChatProperties chatProperties,
70+
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
71+
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
72+
73+
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
74+
commonProperties, chatProperties, "chat");
75+
76+
return OpenAiApi.builder()
77+
.baseUrl(resolved.baseUrl())
78+
.apiKey(new SimpleApiKey(resolved.apiKey()))
79+
.headers(resolved.headers())
80+
.completionsPath(chatProperties.getCompletionsPath())
81+
.embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
82+
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
83+
.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
84+
.responseErrorHandler(responseErrorHandler)
85+
.build();
86+
}
87+
88+
@Bean
89+
@ConditionalOnMissingBean
90+
public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi, OpenAiChatProperties chatProperties,
91+
ToolCallingManager toolCallingManager, RetryTemplate retryTemplate,
7392
ObjectProvider<ObservationRegistry> observationRegistry,
7493
ObjectProvider<ChatModelObservationConvention> observationConvention,
7594
ObjectProvider<ToolExecutionEligibilityPredicate> openAiToolExecutionEligibilityPredicate) {
7695

77-
var openAiApi = openAiApi(chatProperties, commonProperties,
78-
restClientBuilderProvider.getIfAvailable(RestClient::builder),
79-
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat");
80-
8196
var chatModel = OpenAiChatModel.builder()
8297
.openAiApi(openAiApi)
8398
.defaultOptions(chatProperties.getOptions())
@@ -93,23 +108,4 @@ public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperti
93108
return chatModel;
94109
}
95110

96-
private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties,
97-
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
98-
ResponseErrorHandler responseErrorHandler, String modelType) {
99-
100-
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
101-
commonProperties, chatProperties, modelType);
102-
103-
return OpenAiApi.builder()
104-
.baseUrl(resolved.baseUrl())
105-
.apiKey(new SimpleApiKey(resolved.apiKey()))
106-
.headers(resolved.headers())
107-
.completionsPath(chatProperties.getCompletionsPath())
108-
.embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
109-
.restClientBuilder(restClientBuilder)
110-
.webClientBuilder(webClientBuilder)
111-
.responseErrorHandler(responseErrorHandler)
112-
.build();
113-
}
114-
115111
}

auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.ai.openai.OpenAiEmbeddingModel;
2525
import org.springframework.ai.openai.OpenAiImageModel;
2626
import org.springframework.ai.openai.OpenAiModerationModel;
27+
import org.springframework.ai.openai.api.OpenAiApi;
2728
import org.springframework.boot.autoconfigure.AutoConfigurations;
2829
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2930

@@ -42,6 +43,7 @@ public class OpenAiModelConfigurationTests {
4243
@Test
4344
void chatModelActivation() {
4445
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class)).run(context -> {
46+
assertThat(context.getBeansOfType(OpenAiApi.class)).isNotEmpty();
4547
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
4648
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
4749
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
@@ -303,4 +305,14 @@ void moderationModelActivation() {
303305
});
304306
}
305307

308+
@Test
309+
void openAiApiBean() {
310+
// Test that OpenAiApi bean is registered and can be injected
311+
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class)).run(context -> {
312+
assertThat(context.getBeansOfType(OpenAiApi.class)).hasSize(1);
313+
OpenAiApi openAiApi = context.getBean(OpenAiApi.class);
314+
assertThat(openAiApi).isNotNull();
315+
});
316+
}
317+
306318
}

0 commit comments

Comments
 (0)