Make MCP servers catalog path configurable
This commit is contained in:
parent
f4dbf306b8
commit
ab2567ed83
|
|
@ -4,8 +4,12 @@ import java.io.InputStream;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
|
@ -16,8 +20,13 @@ public class MCPServersProvider {
|
|||
|
||||
private final List<MCPServerDefinition> servers;
|
||||
|
||||
public MCPServersProvider(ObjectMapper objectMapper) {
|
||||
this.servers = loadServers(Objects.requireNonNull(objectMapper, "objectMapper cannot be null"));
|
||||
public MCPServersProvider(ObjectMapper objectMapper,
|
||||
ResourceLoader resourceLoader,
|
||||
@Value("${app.mcp.servers.file:}") String catalogLocation) {
|
||||
this.servers = loadServers(
|
||||
Objects.requireNonNull(objectMapper, "objectMapper cannot be null"),
|
||||
Objects.requireNonNull(resourceLoader, "resourceLoader cannot be null"),
|
||||
catalogLocation);
|
||||
}
|
||||
|
||||
public List<MCPServerDefinition> getServers() {
|
||||
|
|
@ -31,9 +40,10 @@ public class MCPServersProvider {
|
|||
.orElseThrow(() -> new IllegalArgumentException("Unknown MCP server: " + id));
|
||||
}
|
||||
|
||||
private List<MCPServerDefinition> loadServers(ObjectMapper objectMapper) {
|
||||
private List<MCPServerDefinition> loadServers(ObjectMapper objectMapper, ResourceLoader resourceLoader,
|
||||
String catalogLocation) {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource("mcp-servers.json");
|
||||
Resource resource = resolveResource(resourceLoader, catalogLocation);
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
MCPServersCatalog catalog = objectMapper.readValue(inputStream, MCPServersCatalog.class);
|
||||
if (catalog.servers() == null) {
|
||||
|
|
@ -42,10 +52,28 @@ public class MCPServersProvider {
|
|||
return List.copyOf(catalog.servers());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to load MCP servers catalog from classpath:mcp-servers.json", ex);
|
||||
throw new IllegalStateException("Unable to load MCP servers catalog from " + describeLocation(catalogLocation), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Resource resolveResource(ResourceLoader resourceLoader, String catalogLocation) {
|
||||
if (!StringUtils.hasText(catalogLocation)) {
|
||||
return new ClassPathResource("mcp-servers.json");
|
||||
}
|
||||
String resolvedLocation = catalogLocation.startsWith("classpath:") || catalogLocation.startsWith("file:")
|
||||
? catalogLocation
|
||||
: "file:" + catalogLocation;
|
||||
Resource resource = resourceLoader.getResource(resolvedLocation);
|
||||
if (!resource.exists()) {
|
||||
throw new IllegalStateException("MCP servers catalog not found at " + resolvedLocation);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
private String describeLocation(String catalogLocation) {
|
||||
return StringUtils.hasText(catalogLocation) ? catalogLocation : "classpath:mcp-servers.json";
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record MCPServersCatalog(List<MCPServerDefinition> servers) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ app.security.key=${WFEDITOR_SECRET_KEY:088c65fd2a5ca418a79cd10df5dff15c0a79781c0
|
|||
app.ollama.internal.key=${OLLAMA_INTERNAL_KEY:ollama}
|
||||
app.ollama.internal.url=${OLLAMA_INTERNAL_URL:https://ollama.internal/api}
|
||||
app.mcp.bridge.url=${MCP_BRIDGE_URL:http://localhost:8000}
|
||||
app.mcp.servers.file=${MCP_SERVERS_FILE:}
|
||||
app.assistant.default-model=${ASSISTANT_DEFAULT_MODEL:gemma3:12b}
|
||||
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:4200}
|
||||
app.import.path=${IMPORT_PATH:/workflow-editor-init}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package it.cnr.isti.workflow.manager.mcp;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class MCPServersProviderTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void loadsCustomCatalogFromConfiguredFile() throws Exception {
|
||||
Path customCatalog = tempDir.resolve("custom-mcp-servers.json");
|
||||
Files.writeString(customCatalog, """
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"id": "custom",
|
||||
"name": "Custom",
|
||||
"description": "Custom test server",
|
||||
"transport": "stdio",
|
||||
"command": "echo",
|
||||
"args": ["hello"],
|
||||
"configurationSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
MCPServersProvider provider = new MCPServersProvider(
|
||||
new ObjectMapper(),
|
||||
new DefaultResourceLoader(),
|
||||
customCatalog.toString());
|
||||
|
||||
List<MCPServersProvider.MCPServerDefinition> servers = provider.getServers();
|
||||
assertEquals(1, servers.size());
|
||||
assertEquals("custom", servers.getFirst().id());
|
||||
assertEquals("echo", servers.getFirst().command());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallsBackToClasspathCatalogWhenPropertyIsBlank() {
|
||||
MCPServersProvider provider = new MCPServersProvider(
|
||||
new ObjectMapper(),
|
||||
new DefaultResourceLoader(),
|
||||
"");
|
||||
|
||||
List<MCPServersProvider.MCPServerDefinition> servers = provider.getServers();
|
||||
assertFalse(servers.isEmpty());
|
||||
assertTrue(servers.stream().anyMatch(server -> server.id().equals("filesystem")));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue