fix: align tool service routing manifest schema
All checks were successful
Deploy Tools / deploy (push) Successful in 1m24s

This commit is contained in:
jade
2026-09-02 14:34:35 +09:00
parent 10aa14ccf4
commit b6603d7449
8 changed files with 63 additions and 20 deletions

View File

@@ -39,7 +39,7 @@ public class McpProperties {
@Data
public static class RoutingFunction {
private String name;
private String description;
private String descriptionSerialization;
private String serverId;
private String categoryKey;
private String productBoundary;
@@ -49,7 +49,7 @@ public class McpProperties {
private List<String> capabilities = List.of();
private String selectIf;
private String rejectIf;
private List<String> confidenceServerIds = List.of();
private List<String> confusableServers = List.of();
private String decisionPolicy;
}

View File

@@ -65,8 +65,11 @@ public class ToolManifestService {
public ToolServiceManifestResponse currentToolServiceManifest() {
McpProperties.Manifest manifest = properties.getManifest();
String bundleId = manifest == null ? null : manifest.getBundleId();
List<McpProperties.RoutingFunction> routingFunctions = manifest == null
List<McpProperties.RoutingFunction> configured = manifest == null
? List.of() : defaultRoutingList(manifest.getRoutingFunctions());
List<Map<String, Object>> routingFunctions = configured.stream()
.map(this::toRoutingFunctionEnvelope)
.toList();
try {
String source = objectMapper.writeValueAsString(Map.of("bundleId", bundleId,
"routingFunctions", routingFunctions));
@@ -187,10 +190,50 @@ public class ToolManifestService {
return value == null ? List.of() : List.copyOf(value);
}
private Map<String, Object> toRoutingFunctionEnvelope(McpProperties.RoutingFunction route) {
Map<String, Object> contract = new LinkedHashMap<>();
contract.put("schema_version", "3.0");
contract.put("server_id", route.getServerId());
contract.put("category_key", route.getCategoryKey());
contract.put("product_boundary", route.getProductBoundary());
contract.put("business_domain", route.getBusinessDomain());
contract.put("business_outcome", route.getBusinessOutcome());
contract.put("primary_entities", defaultList(route.getPrimaryEntities()));
contract.put("select_if", route.getSelectIf());
contract.put("reject_if", route.getRejectIf());
contract.put("capability_index", defaultList(route.getCapabilities()));
contract.put("confusable_servers", defaultList(route.getConfusableServers()));
contract.put("decision_policy", route.getDecisionPolicy());
Map<String, Object> function = new LinkedHashMap<>();
function.put("name", route.getName());
function.put("description_serialization", routingDescription(route, contract));
function.put("routing_contract", contract);
function.put("parameters", emptySchema());
Map<String, Object> envelope = new LinkedHashMap<>();
envelope.put("type", "function");
envelope.put("function", function);
return envelope;
}
private String routingDescription(McpProperties.RoutingFunction route, Map<String, Object> contract) {
try {
return defaultString(route.getDescriptionSerialization(), String.join(" ",
nonBlank(route.getBusinessOutcome()), nonBlank(route.getDecisionPolicy())).trim());
} catch (Exception exception) {
throw new IllegalStateException("Failed to serialize routing contract", exception);
}
}
private String sha256(String value) throws Exception {
byte[] digest = MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder();
for (byte item : digest) result.append(String.format("%02x", item));
return result.toString();
}
private String nonBlank(String value) {
return value == null ? "" : value.trim();
}
}

View File

@@ -1,9 +1,9 @@
package io.shinhanlife.dat.lib.manifest;
import io.shinhanlife.dat.lib.config.McpProperties;
import java.util.List;
import java.util.Map;
/** Server-level routing metadata loaded from tool-service-manifest.yml. */
public record ToolServiceManifestResponse(String bundleId, String revision,
List<McpProperties.RoutingFunction> routingFunctions) {
List<Map<String, Object>> routingFunctions) {
}

View File

@@ -389,7 +389,7 @@ public class PodScaffolder {
manifest:
routing-functions:
- name: route_to_%s
description: %s 업무 서버로 요청을 라우팅합니다.
description-serialization: %s 업무 서버로 요청을 라우팅합니다. %s 관련 업무를 처리합니다. 요청의 주요 업무 영역을 기준으로 서버를 선택합니다.
server-id: %s
category-key: %s
product-boundary: insurance
@@ -399,9 +399,9 @@ public class PodScaffolder {
capabilities: []
select-if: %s 관련 요청인 경우
reject-if: 다른 업무 영역이 주된 요청인 경우
confidence-server-ids: [%s]
confusable-servers: []
decision-policy: 요청의 주요 업무 영역을 기준으로 서버를 선택합니다.
""".formatted(moduleName.replace("-", "_"), key, moduleName, key, key, key, key, moduleName);
""".formatted(moduleName, key, key, moduleName, key, key, key, key);
}
private static String capitalize(String str) {