refs #743: validate advertised ADB MCP tools at startup

This commit is contained in:
devmrko
2026-08-03 11:07:15 +09:00
parent 22c0b571e2
commit 750bfbab5b
6 changed files with 303 additions and 2 deletions

View File

@@ -0,0 +1,111 @@
package com.cloudhandson.vpdbackoffice.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/** Fails startup when an advertised ADB Agent Tool cannot actually be called. */
@Component
public class McpAgentToolStartupValidator implements SmartInitializingSingleton {
static final String TOOL_METADATA_SQL = """
SELECT TOOL_NAME, STATUS
FROM USER_AI_AGENT_TOOLS
""";
private static final Logger log =
LoggerFactory.getLogger(McpAgentToolStartupValidator.class);
private final McpToolCatalog toolCatalog;
private final JdbcTemplate jdbcTemplate;
public McpAgentToolStartupValidator(
McpToolCatalog toolCatalog,
JdbcTemplate jdbcTemplate
) {
this.toolCatalog = toolCatalog;
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void afterSingletonsInstantiated() {
validateAdvertisedAgentTools();
}
void validateAdvertisedAgentTools() {
Set<String> requiredTargets = new TreeSet<>();
toolCatalog.tools().stream()
.filter(McpToolDefinition::agentTool)
.map(McpToolDefinition::targetName)
.map(value -> value.toUpperCase(Locale.ROOT))
.forEach(requiredTargets::add);
if (requiredTargets.isEmpty()) {
log.info("MCP startup validation skipped: no ADB Agent Tool is advertised");
return;
}
Map<String, String> registered = loadRegisteredTools();
List<String> missing = new ArrayList<>();
List<String> notEnabled = new ArrayList<>();
for (String requiredTarget : requiredTargets) {
if (!registered.containsKey(requiredTarget)) {
missing.add(requiredTarget);
continue;
}
String status = registered.get(requiredTarget);
if (!"ENABLED".equals(status)) {
notEnabled.add(requiredTarget + "=" + status);
}
}
if (!missing.isEmpty() || !notEnabled.isEmpty()) {
throw new IllegalStateException(
"Advertised MCP Agent Tool validation failed: missing=" + missing
+ ", notEnabled=" + notEnabled);
}
log.info("Validated {} advertised ADB Agent Tool(s): {}",
requiredTargets.size(), requiredTargets);
}
private Map<String, String> loadRegisteredTools() {
try {
Map<String, String> registered = new TreeMap<>();
for (Map<String, Object> row : jdbcTemplate.queryForList(TOOL_METADATA_SQL)) {
String toolName = normalized(row, "TOOL_NAME");
if (!toolName.isBlank()) {
String status = normalized(row, "STATUS");
registered.put(toolName, status.isBlank() ? "<NULL>" : status);
}
}
return registered;
} catch (DataAccessException exception) {
throw new IllegalStateException(
"Unable to validate advertised MCP Agent Tools in USER_AI_AGENT_TOOLS",
exception);
}
}
private String normalized(Map<String, Object> row, String column) {
Object value = row.get(column);
if (value == null) {
value = row.entrySet().stream()
.filter(entry -> column.equalsIgnoreCase(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
return value == null ? "" : value.toString().trim().toUpperCase(Locale.ROOT);
}
}