Skip to content

Commit 5001d19

Browse files
committed
feat(config): Add profile support and change primitive types to wrapper types in configuration classes
1 parent f8b7bb4 commit 5001d19

File tree

12 files changed

+248
-21
lines changed

12 files changed

+248
-21
lines changed

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/McpServerCapabilities.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* @author codeboyzhou
1212
*/
1313
public record McpServerCapabilities(
14-
@JsonProperty("resource") boolean resource,
15-
@JsonProperty("prompt") boolean prompt,
16-
@JsonProperty("tool") boolean tool) {
14+
@JsonProperty("resource") Boolean resource,
15+
@JsonProperty("prompt") Boolean prompt,
16+
@JsonProperty("tool") Boolean tool) {
1717

1818
/**
1919
* Creates a new instance of {@code McpServerCapabilities} with default values.

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/McpServerChangeNotification.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
* @author codeboyzhou
1313
*/
1414
public record McpServerChangeNotification(
15-
@JsonProperty("resource") boolean resource,
16-
@JsonProperty("prompt") boolean prompt,
17-
@JsonProperty("tool") boolean tool) {
15+
@JsonProperty("resource") Boolean resource,
16+
@JsonProperty("prompt") Boolean prompt,
17+
@JsonProperty("tool") Boolean tool) {
1818

1919
/**
2020
* Creates a new instance of {@code McpServerChangeNotification} with default values.

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/McpServerConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
* @author codeboyzhou
1717
*/
1818
public record McpServerConfiguration(
19-
@JsonProperty("enabled") boolean enabled,
19+
@JsonProperty("profile") String profile,
20+
@JsonProperty("enabled") Boolean enabled,
2021
@JsonProperty("mode") ServerMode mode,
2122
@JsonProperty("name") String name,
2223
@JsonProperty("version") String version,
2324
@JsonProperty("type") ServerType type,
2425
@JsonProperty("instructions") String instructions,
25-
@JsonProperty("request-timeout") long requestTimeout,
26+
@JsonProperty("request-timeout") Long requestTimeout,
2627
@JsonProperty("capabilities") McpServerCapabilities capabilities,
2728
@JsonProperty("change-notification") McpServerChangeNotification changeNotification,
2829
@JsonProperty("sse") McpServerSSE sse,

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/McpServerSSE.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ public record McpServerSSE(
1616
@JsonProperty("message-endpoint") String messageEndpoint,
1717
@JsonProperty("endpoint") String endpoint,
1818
@JsonProperty("base-url") String baseUrl,
19-
@JsonProperty("port") int port) {}
19+
@JsonProperty("port") Integer port) {}

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/McpServerStreamable.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
*/
1616
public record McpServerStreamable(
1717
@JsonProperty("mcp-endpoint") String mcpEndpoint,
18-
@JsonProperty("disallow-delete") boolean disallowDelete,
19-
@JsonProperty("keep-alive-interval") long keepAliveInterval,
20-
@JsonProperty("port") int port) {}
18+
@JsonProperty("disallow-delete") Boolean disallowDelete,
19+
@JsonProperty("keep-alive-interval") Long keepAliveInterval,
20+
@JsonProperty("port") Integer port) {}

src/main/java/com/github/codeboyzhou/mcp/declarative/configuration/YAMLConfigurationLoader.java

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.codeboyzhou.mcp.declarative.exception.McpServerConfigurationException;
44
import com.github.codeboyzhou.mcp.declarative.util.JacksonHelper;
5+
import com.github.codeboyzhou.mcp.declarative.util.StringHelper;
56
import java.io.File;
67
import java.net.URISyntaxException;
78
import java.net.URL;
@@ -42,9 +43,23 @@ public YAMLConfigurationLoader() {
4243
public McpServerConfiguration loadConfig() {
4344
Path configFilePath = getConfigFilePath(configFileName);
4445
File file = configFilePath.toFile();
45-
McpServerConfiguration config = JacksonHelper.fromYaml(file, McpServerConfiguration.class);
46+
McpServerConfiguration baseConfig = JacksonHelper.fromYaml(file, McpServerConfiguration.class);
4647
log.info("Configuration loaded successfully from file: {}", configFileName);
47-
return config;
48+
49+
final String profile = baseConfig.profile();
50+
if (StringHelper.isBlank(profile)) {
51+
log.info("No profile specified in configuration file: {}", configFileName);
52+
return baseConfig;
53+
}
54+
55+
final String profileConfigFileName = configFileName.replace(".yml", "-" + profile + ".yml");
56+
Path profileConfigFilePath = getConfigFilePath(profileConfigFileName);
57+
File profileConfigFile = profileConfigFilePath.toFile();
58+
McpServerConfiguration profileConfig =
59+
JacksonHelper.fromYaml(profileConfigFile, McpServerConfiguration.class);
60+
log.info("Profile configuration loaded successfully from file: {}", profileConfigFileName);
61+
62+
return mergeConfigurations(baseConfig, profileConfig);
4863
}
4964

5065
/**
@@ -67,4 +82,147 @@ private Path getConfigFilePath(String fileName) {
6782
throw new McpServerConfigurationException("Invalid configuration file: " + fileName, e);
6883
}
6984
}
85+
86+
/**
87+
* Merges the base configuration with the profile configuration.
88+
*
89+
* @param base the base configuration
90+
* @param profile the profile configuration
91+
* @return the merged configuration
92+
*/
93+
private McpServerConfiguration mergeConfigurations(
94+
McpServerConfiguration base, McpServerConfiguration profile) {
95+
return new McpServerConfiguration(
96+
base.profile(),
97+
mergeValue(base.enabled(), profile.enabled()),
98+
mergeValue(base.mode(), profile.mode()),
99+
mergeString(base.name(), profile.name()),
100+
mergeString(base.version(), profile.version()),
101+
mergeValue(base.type(), profile.type()),
102+
mergeString(base.instructions(), profile.instructions()),
103+
mergeValue(base.requestTimeout(), profile.requestTimeout()),
104+
mergeCapabilities(base.capabilities(), profile.capabilities()),
105+
mergeChangeNotification(base.changeNotification(), profile.changeNotification()),
106+
mergeSSE(base.sse(), profile.sse()),
107+
mergeStreamable(base.streamable(), profile.streamable()));
108+
}
109+
110+
/**
111+
* Merges the base value with the profile value.
112+
*
113+
* @param baseValue the base value
114+
* @param profileValue the profile value
115+
* @return the merged value
116+
*/
117+
private <T> T mergeValue(T baseValue, T profileValue) {
118+
return profileValue == null ? baseValue : profileValue;
119+
}
120+
121+
/**
122+
* Merges the base string with the profile string.
123+
*
124+
* @param baseValue the base string
125+
* @param profileValue the profile string
126+
* @return the merged string
127+
*/
128+
private String mergeString(String baseValue, String profileValue) {
129+
return StringHelper.isBlank(profileValue) ? baseValue : profileValue;
130+
}
131+
132+
/**
133+
* Merges the base capabilities with the profile capabilities.
134+
*
135+
* @param base the base capabilities
136+
* @param profile the profile capabilities
137+
* @return the merged capabilities
138+
*/
139+
private McpServerCapabilities mergeCapabilities(
140+
McpServerCapabilities base, McpServerCapabilities profile) {
141+
142+
if (base == null) {
143+
return profile;
144+
}
145+
146+
if (profile == null) {
147+
return base;
148+
}
149+
150+
return new McpServerCapabilities(
151+
mergeValue(base.resource(), profile.resource()),
152+
mergeValue(base.prompt(), profile.prompt()),
153+
mergeValue(base.tool(), profile.tool()));
154+
}
155+
156+
/**
157+
* Merges the base change notification with the profile change notification.
158+
*
159+
* @param base the base change notification
160+
* @param profile the profile change notification
161+
* @return the merged change notification
162+
*/
163+
private McpServerChangeNotification mergeChangeNotification(
164+
McpServerChangeNotification base, McpServerChangeNotification profile) {
165+
166+
if (base == null) {
167+
return profile;
168+
}
169+
170+
if (profile == null) {
171+
return base;
172+
}
173+
174+
return new McpServerChangeNotification(
175+
mergeValue(base.resource(), profile.resource()),
176+
mergeValue(base.prompt(), profile.prompt()),
177+
mergeValue(base.tool(), profile.tool()));
178+
}
179+
180+
/**
181+
* Merges the base SSE with the profile SSE.
182+
*
183+
* @param base the base SSE
184+
* @param profile the profile SSE
185+
* @return the merged SSE
186+
*/
187+
private McpServerSSE mergeSSE(McpServerSSE base, McpServerSSE profile) {
188+
189+
if (base == null) {
190+
return profile;
191+
}
192+
193+
if (profile == null) {
194+
return base;
195+
}
196+
197+
return new McpServerSSE(
198+
mergeString(base.messageEndpoint(), profile.messageEndpoint()),
199+
mergeString(base.endpoint(), profile.endpoint()),
200+
mergeString(base.baseUrl(), profile.baseUrl()),
201+
mergeValue(base.port(), profile.port()));
202+
}
203+
204+
/**
205+
* Merges the base streamable with the profile streamable.
206+
*
207+
* @param base the base streamable
208+
* @param profile the profile streamable
209+
* @return the merged streamable
210+
*/
211+
private McpServerStreamable mergeStreamable(
212+
McpServerStreamable base, McpServerStreamable profile) {
213+
214+
if (base == null) {
215+
return profile;
216+
}
217+
218+
if (profile == null) {
219+
return base;
220+
}
221+
222+
return new McpServerStreamable(
223+
mergeString(base.mcpEndpoint(), profile.mcpEndpoint()),
224+
mergeValue(base.disallowDelete(), profile.disallowDelete()),
225+
mergeValue(base.keepAliveInterval(), profile.keepAliveInterval()),
226+
mergeValue(base.port(), profile.port()));
227+
}
70228
}

src/test/java/com/github/codeboyzhou/mcp/declarative/McpServersTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static org.junit.jupiter.api.Assertions.assertEquals;
55
import static org.junit.jupiter.api.Assertions.assertFalse;
66
import static org.junit.jupiter.api.Assertions.assertNotNull;
7-
import static org.junit.jupiter.api.Assertions.assertSame;
87
import static org.junit.jupiter.api.Assertions.assertThrows;
98

109
import com.github.codeboyzhou.mcp.declarative.configuration.McpServerConfiguration;
@@ -124,7 +123,7 @@ void testStartServer_enableStdioMode_shouldSucceed() {
124123
YAMLConfigurationLoader configLoader = new YAMLConfigurationLoader(configFileName);
125124
McpServerConfiguration configuration = configLoader.loadConfig();
126125
assertDoesNotThrow(() -> servers.startServer(configFileName));
127-
assertSame(ServerMode.STDIO, configuration.mode());
126+
assertEquals(ServerMode.STDIO, configuration.mode());
128127
}
129128

130129
@Test
@@ -133,7 +132,7 @@ void testStartServer_enableHttpSseMode_shouldSucceed() {
133132
YAMLConfigurationLoader configLoader = new YAMLConfigurationLoader(configFileName);
134133
McpServerConfiguration configuration = configLoader.loadConfig();
135134
assertDoesNotThrow(() -> servers.startServer(configFileName));
136-
assertSame(ServerMode.SSE, configuration.mode());
135+
assertEquals(ServerMode.SSE, configuration.mode());
137136
}
138137

139138
@Test
@@ -142,7 +141,7 @@ void testStartServer_enableStreamableHttpMode_shouldSucceed() {
142141
YAMLConfigurationLoader configLoader = new YAMLConfigurationLoader(configFileName);
143142
McpServerConfiguration configuration = configLoader.loadConfig();
144143
assertDoesNotThrow(() -> servers.startServer(configFileName));
145-
assertSame(ServerMode.STREAMABLE, configuration.mode());
144+
assertEquals(ServerMode.STREAMABLE, configuration.mode());
146145
}
147146

148147
@Test
@@ -156,7 +155,7 @@ void testStartServer_useDefaultConfigFileName_shouldSucceed() {
156155
String configFileName = "mcp-server.yml";
157156
YAMLConfigurationLoader configLoader = new YAMLConfigurationLoader(configFileName);
158157
McpServerConfiguration configuration = configLoader.loadConfig();
159-
assertSame(ServerMode.STREAMABLE, configuration.mode());
158+
assertEquals(ServerMode.STREAMABLE, configuration.mode());
160159
assertDoesNotThrow(() -> servers.startServer());
161160
}
162161

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.github.codeboyzhou.mcp.declarative.configuration;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import com.github.codeboyzhou.mcp.declarative.enums.ServerMode;
9+
import com.github.codeboyzhou.mcp.declarative.enums.ServerType;
10+
import org.junit.jupiter.api.Test;
11+
12+
class YAMLConfigurationLoaderTest {
13+
14+
@Test
15+
void testLoadConfig_withProfile() {
16+
final String configFileName = "test-mcp-server-with-profile.yml";
17+
YAMLConfigurationLoader loader = new YAMLConfigurationLoader(configFileName);
18+
McpServerConfiguration configuration = loader.loadConfig();
19+
assertNotNull(configuration);
20+
assertEquals("dev", configuration.profile());
21+
assertTrue(configuration.enabled());
22+
assertEquals(ServerMode.STREAMABLE, configuration.mode());
23+
assertEquals("mcp-server-dev", configuration.name());
24+
assertEquals("1.0.0-dev", configuration.version());
25+
assertEquals(ServerType.SYNC, configuration.type());
26+
assertEquals(60000L, configuration.requestTimeout());
27+
assertFalse(configuration.capabilities().resource());
28+
assertTrue(configuration.capabilities().prompt());
29+
assertTrue(configuration.capabilities().tool());
30+
assertFalse(configuration.changeNotification().resource());
31+
assertTrue(configuration.changeNotification().prompt());
32+
assertTrue(configuration.changeNotification().tool());
33+
assertEquals("/mcp/message/dev", configuration.streamable().mcpEndpoint());
34+
assertTrue(configuration.streamable().disallowDelete());
35+
assertEquals(30000L, configuration.streamable().keepAliveInterval());
36+
assertEquals(9000, configuration.streamable().port());
37+
}
38+
}

src/test/java/com/github/codeboyzhou/mcp/declarative/util/StringHelperTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.github.codeboyzhou.mcp.declarative.util;
22

3-
import static org.junit.jupiter.api.Assertions.*;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
47

58
import org.junit.jupiter.api.Test;
69

src/test/java/com/github/codeboyzhou/mcp/declarative/util/TypeConverterTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.github.codeboyzhou.mcp.declarative.util;
22

3-
import static org.junit.jupiter.api.Assertions.*;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
46

57
import org.junit.jupiter.api.Test;
68

0 commit comments

Comments
 (0)