diff --git a/dat-was-cus/src/main/resources/tool-service-manifest.yml b/dat-was-cus/src/main/resources/tool-service-manifest.yml index dea88a7f5..21acb9e98 100644 --- a/dat-was-cus/src/main/resources/tool-service-manifest.yml +++ b/dat-was-cus/src/main/resources/tool-service-manifest.yml @@ -1,8 +1,8 @@ mcp: manifest: routing-functions: - - name: route_to_dat_was_cus - description: 고객 업무 서버로 요청을 라우팅합니다. + - name: route_to_dat-was-cus + description-serialization: 고객 업무 서버로 요청을 라우팅합니다. 고객 정보와 고객 상담 업무를 처리합니다. 요청의 주요 엔티티와 업무 영역을 기준으로 고객 서버를 선택합니다. server-id: dat-was-cus category-key: cus product-boundary: insurance @@ -12,5 +12,5 @@ mcp: capabilities: [고객 조회, 고객 정보 수정, 상담 이력 조회] select-if: 고객 또는 상담 관련 요청인 경우 reject-if: 계약, 상품, 시스템 운영 업무가 주된 요청인 경우 - confidence-server-ids: [dat-was-cus] + confusable-servers: [dat-was-sal, dat-was-pro, dat-was-sys] decision-policy: 요청의 주요 엔티티와 업무 영역을 기준으로 고객 서버를 선택합니다. diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/config/McpProperties.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/config/McpProperties.java index bf8a64c28..ff53822ec 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/config/McpProperties.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/config/McpProperties.java @@ -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 capabilities = List.of(); private String selectIf; private String rejectIf; - private List confidenceServerIds = List.of(); + private List confusableServers = List.of(); private String decisionPolicy; } diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java index 4c479a5f0..c5fbf23fa 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolManifestService.java @@ -65,8 +65,11 @@ public class ToolManifestService { public ToolServiceManifestResponse currentToolServiceManifest() { McpProperties.Manifest manifest = properties.getManifest(); String bundleId = manifest == null ? null : manifest.getBundleId(); - List routingFunctions = manifest == null + List configured = manifest == null ? List.of() : defaultRoutingList(manifest.getRoutingFunctions()); + List> 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 toRoutingFunctionEnvelope(McpProperties.RoutingFunction route) { + Map 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 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 envelope = new LinkedHashMap<>(); + envelope.put("type", "function"); + envelope.put("function", function); + return envelope; + } + + private String routingDescription(McpProperties.RoutingFunction route, Map 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(); + } } diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java index 589e287b0..4857ecbd0 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest/ToolServiceManifestResponse.java @@ -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 routingFunctions) { + List> routingFunctions) { } diff --git a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java index 680d184de..b016397da 100644 --- a/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java +++ b/dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/PodScaffolder.java @@ -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) { diff --git a/dat-was-pro/src/main/resources/tool-service-manifest.yml b/dat-was-pro/src/main/resources/tool-service-manifest.yml index 080f19397..bf0fc262e 100644 --- a/dat-was-pro/src/main/resources/tool-service-manifest.yml +++ b/dat-was-pro/src/main/resources/tool-service-manifest.yml @@ -1,8 +1,8 @@ mcp: manifest: routing-functions: - - name: route_to_dat_was_pro - description: 처리계 업무 서버로 요청을 라우팅합니다. + - name: route_to_dat-was-pro + description-serialization: 처리계 업무 서버로 요청을 라우팅합니다. 보험 계약, 청약, 변경 및 보험금 지급 업무를 처리합니다. 요청의 주요 엔티티와 업무 영역을 기준으로 처리계 서버를 선택합니다. server-id: dat-was-pro category-key: pro product-boundary: insurance @@ -12,5 +12,5 @@ mcp: capabilities: [계약 조회, 청약 처리, 계약 변경, 보험금 지급 조회] select-if: 보험 계약, 청약, 계약 변경, 보험금 또는 지급 관련 요청인 경우 reject-if: 상품 소개, 고객 일반정보, 영업조직, 시스템 운영 업무가 주된 요청인 경우 - confidence-server-ids: [dat-was-pro] + confusable-servers: [dat-was-cus, dat-was-sal, dat-was-sys] decision-policy: 요청의 주요 엔티티와 업무 영역을 기준으로 처리계 서버를 선택합니다. diff --git a/dat-was-sal/src/main/resources/tool-service-manifest.yml b/dat-was-sal/src/main/resources/tool-service-manifest.yml index 1060c9da9..d37a7ff13 100644 --- a/dat-was-sal/src/main/resources/tool-service-manifest.yml +++ b/dat-was-sal/src/main/resources/tool-service-manifest.yml @@ -1,8 +1,8 @@ mcp: manifest: routing-functions: - - name: route_to_dat_was_sal - description: 영업 업무 서버로 요청을 라우팅합니다. + - name: route_to_dat-was-sal + description-serialization: 영업 업무 서버로 요청을 라우팅합니다. 영업 조직과 설계사 업무를 처리합니다. 요청의 주요 엔티티와 업무 영역을 기준으로 영업 서버를 선택합니다. server-id: dat-was-sal category-key: sal product-boundary: insurance @@ -12,5 +12,5 @@ mcp: capabilities: [설계사 조회, 영업조직 조회, 실적 조회] select-if: 설계사, 영업조직 또는 영업실적 관련 요청인 경우 reject-if: 고객, 계약, 상품, 시스템 운영 업무가 주된 요청인 경우 - confidence-server-ids: [dat-was-sal] + confusable-servers: [dat-was-cus, dat-was-pro, dat-was-sys] decision-policy: 요청의 주요 엔티티와 업무 영역을 기준으로 영업 서버를 선택합니다. diff --git a/dat-was-sys/src/main/resources/tool-service-manifest.yml b/dat-was-sys/src/main/resources/tool-service-manifest.yml index d9446ac81..2f47d9547 100644 --- a/dat-was-sys/src/main/resources/tool-service-manifest.yml +++ b/dat-was-sys/src/main/resources/tool-service-manifest.yml @@ -1,8 +1,8 @@ mcp: manifest: routing-functions: - - name: route_to_dat_was_sys - description: 시스템 업무 서버로 요청을 라우팅합니다. + - name: route_to_dat-was-sys + description-serialization: 시스템 업무 서버로 요청을 라우팅합니다. 시스템 상태와 공통 운영 업무를 처리합니다. 요청의 주요 엔티티와 업무 영역을 기준으로 시스템 서버를 선택합니다. server-id: dat-was-sys category-key: sys product-boundary: insurance @@ -12,5 +12,5 @@ mcp: capabilities: [시스템 조회, 사용자 조회, 권한 조회] select-if: 시스템 운영, 사용자, 권한 관련 요청인 경우 reject-if: 고객, 계약, 상품, 영업 업무가 주된 요청인 경우 - confidence-server-ids: [dat-was-sys] + confusable-servers: [dat-was-cus, dat-was-pro, dat-was-sal] decision-policy: 요청의 주요 엔티티와 업무 영역을 기준으로 시스템 서버를 선택합니다.