Skip to content

Commit cd0ac9c

Browse files
authored
GH-2633: MCP client autoconfiguration runtime hints (#2683)
Fixes: #2633 - Create McpClientAutoConfigurationRuntimeHints class - Implement registerHints method to support JSON resources - Add test class with comprehensive assertions - Update aot.factories to register the new runtime hints - Add sample JSON configuration files for testing Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
1 parent 186175a commit cd0ac9c

File tree

5 files changed

+159
-0
lines changed

5 files changed

+159
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.client.autoconfigure.aot;
18+
19+
import org.springframework.aot.hint.MemberCategory;
20+
import org.springframework.aot.hint.RuntimeHints;
21+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
22+
23+
import static org.springframework.ai.aot.AiRuntimeHints.findJsonAnnotatedClassesInPackage;
24+
25+
/**
26+
* @author Josh Long
27+
* @author Soby Chacko
28+
*/
29+
public class McpClientAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar {
30+
31+
@Override
32+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
33+
hints.resources().registerPattern("**.json");
34+
35+
var mcs = MemberCategory.values();
36+
for (var tr : findJsonAnnotatedClassesInPackage("org.springframework.ai.mcp.client.autoconfigure")) {
37+
hints.reflection().registerType(tr, mcs);
38+
}
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.aot.hint.RuntimeHintsRegistrar=\
2+
org.springframework.ai.mcp.client.autoconfigure.aot.McpClientAutoConfigurationRuntimeHints
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.client.autoconfigure;
18+
19+
import java.io.IOException;
20+
import java.util.HashSet;
21+
import java.util.Set;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.ai.mcp.client.autoconfigure.aot.McpClientAutoConfigurationRuntimeHints;
26+
import org.springframework.ai.mcp.client.autoconfigure.properties.McpStdioClientProperties;
27+
import org.springframework.aot.hint.RuntimeHints;
28+
import org.springframework.aot.hint.TypeReference;
29+
import org.springframework.core.io.Resource;
30+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
31+
32+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
33+
import static org.springframework.ai.aot.AiRuntimeHints.findJsonAnnotatedClassesInPackage;
34+
35+
/**
36+
* @author Soby Chacko
37+
*/
38+
public class McpClientAutoConfigurationRuntimeHintsTests {
39+
40+
@Test
41+
void registerHints() throws IOException {
42+
43+
RuntimeHints runtimeHints = new RuntimeHints();
44+
45+
McpClientAutoConfigurationRuntimeHints mcpRuntimeHints = new McpClientAutoConfigurationRuntimeHints();
46+
mcpRuntimeHints.registerHints(runtimeHints, null);
47+
48+
boolean hasJsonPattern = runtimeHints.resources()
49+
.resourcePatternHints()
50+
.anyMatch(resourceHints -> resourceHints.getIncludes()
51+
.stream()
52+
.anyMatch(pattern -> "**.json".equals(pattern.getPattern())));
53+
54+
assertThat(hasJsonPattern).as("The **.json resource pattern should be registered").isTrue();
55+
56+
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
57+
Resource[] resources = resolver.getResources("classpath*:**/*.json");
58+
59+
assertThat(resources.length).isGreaterThan(1);
60+
61+
boolean foundRootJson = false;
62+
boolean foundSubfolderJson = false;
63+
64+
for (Resource resource : resources) {
65+
try {
66+
String path = resource.getURL().getPath();
67+
if (path.endsWith("/test-config.json")) {
68+
foundRootJson = true;
69+
}
70+
else if (path.endsWith("/nested/nested-config.json")) {
71+
foundSubfolderJson = true;
72+
}
73+
}
74+
catch (IOException e) {
75+
// nothing to do
76+
}
77+
}
78+
79+
assertThat(foundRootJson).as("test-config.json should exist in the root test resources directory").isTrue();
80+
81+
assertThat(foundSubfolderJson).as("nested-config.json should exist in the nested subfolder").isTrue();
82+
83+
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage(
84+
"org.springframework.ai.mcp.client.autoconfigure");
85+
86+
Set<TypeReference> registeredTypes = new HashSet<>();
87+
runtimeHints.reflection().typeHints().forEach(typeHint -> registeredTypes.add(typeHint.getType()));
88+
89+
for (TypeReference jsonAnnotatedClass : jsonAnnotatedClasses) {
90+
assertThat(registeredTypes.contains(jsonAnnotatedClass))
91+
.as("JSON-annotated class %s should be registered for reflection", jsonAnnotatedClass.getName())
92+
.isTrue();
93+
}
94+
95+
assertThat(registeredTypes.contains(TypeReference.of(McpStdioClientProperties.Parameters.class)))
96+
.as("McpStdioClientProperties.Parameters class should be registered")
97+
.isTrue();
98+
}
99+
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "nested-config",
3+
"description": "Test JSON file in nested subfolder of test resources",
4+
"version": "1.0.0",
5+
"nestedProperties": {
6+
"nestedProperty1": "nestedValue1"
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "test-config",
3+
"description": "Test JSON file in root test resources folder",
4+
"version": "1.0.0",
5+
"properties": {
6+
"testProperty1": "value1"
7+
}
8+
}

0 commit comments

Comments
 (0)