From b9aa8e0169c8eaeff0fb135453c7e8d09913258c Mon Sep 17 00:00:00 2001 From: jade Date: Sat, 11 Jul 2026 00:37:29 +0900 Subject: [PATCH] refactor: apply new Naming standard (uid, categoryKey, subToolName) across modules --- McpBridge.java | 6 ++-- .../biz/mcp/gateway/dto/ToolMetadata.java | 9 +++-- .../axhub/common/util/ToolScaffolder.java | 9 ++--- .../biz/mcp/gateway/dto/ToolMetadata.java | 9 +++-- .../presentation/McpRouterController.java | 22 ++++++------ .../registry/RedisRegistryService.java | 26 +++++++------- .../biz/mcp/gateway/service/ToolPlanner.java | 10 +++--- .../main/resources/static/admin/scaffold.html | 36 +++++++++---------- .../src/main/resources/static/catalog.html | 4 +-- .../biz/mcp/tool/annotation/McpFunction.java | 3 +- .../biz/mcp/tool/annotation/McpTool.java | 2 +- .../axhub/biz/mcp/tool/dto/ToolMetadata.java | 16 +++++++-- .../service/ToolRegistryHeartbeatSender.java | 30 +++++++++------- .../biz/mcp/tool/email/EmailToolService.java | 4 +-- .../biz/mcp/tool/service/BalanceService.java | 5 ++- .../tool/service/BillingProcessService.java | 6 ++-- .../mcp/tool/service/BondIssueService.java | 6 ++-- .../tool/service/CommonUtilityService.java | 8 ++--- .../tool/service/ContractInquiryService.java | 6 ++-- .../mcp/tool/service/CustomerInfoService.java | 6 ++-- .../tool/service/SampleStringToolService.java | 5 ++- .../tool/service/TemplateUtilityService.java | 5 ++- .../tool/service/PaymentApprovalService.java | 5 ++- .../biz/mcp/tool/sms/SmsToolService.java | 4 +-- docker-compose.yml | 10 +++--- 25 files changed, 135 insertions(+), 117 deletions(-) diff --git a/McpBridge.java b/McpBridge.java index 627e5ec..0f78109 100644 --- a/McpBridge.java +++ b/McpBridge.java @@ -39,12 +39,12 @@ public class McpBridge { HttpResponse response = client.send(req, HttpResponse.BodyHandlers.ofString()); // Parse tools from response string manually (basic string manipulation) - // axhub-gateway returns: {"result":{"tools":[{"toolName":"other_get_sample_string","description":"...","parametersSchema":{...}}]}} + // axhub-gateway returns: {"result":{"tools":[{"subToolName":"other_get_sample_string","description":"...","parametersSchema":{...}}]}} // We need to format it to standard MCP format String rawJson = response.body(); // For simplicity, we can just proxy the response if it matches closely, but it doesn't. - // Let's just do a naive conversion by replacing "toolName" with "name" and "parametersSchema" with "inputSchema" - String transformed = rawJson.replace("\"toolName\"", "\"name\"").replace("\"parametersSchema\"", "\"inputSchema\""); + // Let's just do a naive conversion by replacing "subToolName" with "name" and "parametersSchema" with "inputSchema" + String transformed = rawJson.replace("\"subToolName\"", "\"name\"").replace("\"parametersSchema\"", "\"inputSchema\""); // Add type: "object" to inputSchema if missing - too complex with string replace, let's hope axhub-gateway already provides correct schema // The result format expects: {"tools": [ ... ]} diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java b/axhub-common/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java index be3bd3e..0ea23cd 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java @@ -36,7 +36,10 @@ import java.util.Map; public class ToolMetadata { // 1. Tool 기본 정보 - private String toolName; // 툴 고유 명칭 (예: CustomerSearchTool) + private String uid; // UUID 형식의 고유 식별자 + private String semver; // 버전 (예: 1.0.0) + private String name; // 사람이 읽는 라벨 (1-128자) + private String subToolName; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool) private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능) // 2. 파라미터 스키마 (JSON Schema 형태의 Map) @@ -45,8 +48,8 @@ public class ToolMetadata { // 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨) private Map actionPrompts; - // 2-1. 도메인 부서 그룹명 (권한 관리에 사용) - private String domainGroup; + // 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식) + private String categoryKey; // 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info) private String endpoint; diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java index ef318c9..273b3b6 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolScaffolder.java @@ -181,12 +181,13 @@ public class ToolScaffolder { @Service @McpTool( routingType = "%s", - group = "%s" + categoryKey = "%s" ) public class %sService extends AbstractMcpToolService { @McpFunction( - name = "%s", + name = "%s 툴", + subToolName = "%s", description = "%s", prompt = "%s", mappingId = "%s", @@ -200,8 +201,8 @@ public class ToolScaffolder { """.formatted( BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, baseName, author, createDate, createDate, author, - routingType, group, baseName, - toolName, description, description + " 해줘.", interfaceId, register, + routingType, group.toLowerCase(), baseName, + baseName, toolName, description, description + " 해줘.", interfaceId, register, baseName, routingType, interfaceId ); diff --git a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java index e9f0dde..5b9827a 100644 --- a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java +++ b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/dto/ToolMetadata.java @@ -36,7 +36,10 @@ import java.util.Map; public class ToolMetadata { // 1. Tool 기본 정보 - private String toolName; // 툴 고유 명칭 (예: CustomerSearchTool) + private String uid; // UUID 형식의 고유 식별자 + private String semver; // 버전 (예: 1.0.0) + private String name; // 사람이 읽는 라벨 (1-128자) + private String subToolName; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool) private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능) // 2. 파라미터 스키마 (JSON Schema 형태의 Map) @@ -45,8 +48,8 @@ public class ToolMetadata { // 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨) private Map actionPrompts; - // 2-1. 도메인 부서 그룹명 (권한 관리에 사용) - private String domainGroup; + // 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식) + private String categoryKey; // 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info) private String endpoint; diff --git a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/McpRouterController.java b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/McpRouterController.java index 3db25da..b819fcb 100644 --- a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/McpRouterController.java +++ b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/McpRouterController.java @@ -109,11 +109,11 @@ public class McpRouterController { public ResponseEntity listTools() { List activeTools = redisRegistryService.getAllTools() .stream() - .filter(ToolMetadata::isVisible) + .filter(ToolMetadata::getVisible) .collect(java.util.stream.Collectors.toList()); java.util.Set knownTools = activeTools.stream() - .map(ToolMetadata::getToolName) + .map(ToolMetadata::getUid) .collect(java.util.stream.Collectors.toSet()); java.util.Set fallbackUrls = new java.util.HashSet<>(gatewayFallbackProperties.getRoutes().values()); @@ -130,9 +130,9 @@ public class McpRouterController { if (localTools != null) { for (ToolMetadata t : localTools) { - if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getToolName())) { + if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getUid())) { activeTools.add(t); - knownTools.add(t.getToolName()); + knownTools.add(t.getUid()); } } } @@ -152,7 +152,7 @@ public class McpRouterController { public ResponseEntity generateToolsMarkdown() { List tools = redisRegistryService.getAllTools() .stream() - .filter(ToolMetadata::isVisible) + .filter(ToolMetadata::getVisible) .collect(java.util.stream.Collectors.toList()); StringBuilder md = new StringBuilder(); @@ -165,7 +165,7 @@ public class McpRouterController { // Group by Domain Group Map> groupedTools = new java.util.HashMap<>(); for (ToolMetadata tool : tools) { - String group = tool.getDomainGroup() != null ? tool.getDomainGroup() : "기타 (Others)"; + String group = tool.getCategoryKey() != null ? tool.getCategoryKey() : "기타 (Others)"; groupedTools.computeIfAbsent(group, k -> new java.util.ArrayList<>()).add(tool); } @@ -174,7 +174,7 @@ public class McpRouterController { int index = 1; for (ToolMetadata tool : entry.getValue()) { - md.append("### ").append(index++).append(". ").append(tool.getToolName()).append("\n"); + md.append("### ").append(index++).append(". ").append(tool.getUid()).append("\n"); if (tool.getDescription() != null) { md.append("- **설명**: ").append(tool.getDescription()).append("\n"); } @@ -223,14 +223,14 @@ public class McpRouterController { } @PostMapping("/registry/deregister") - public ResponseEntity deregisterTool(@RequestBody String toolName) { - redisRegistryService.removeTool(toolName); + public ResponseEntity deregisterTool(@RequestBody String uid) { + redisRegistryService.removeTool(uid); return ResponseEntity.ok("Deregistered"); } @PostMapping("/registry/heartbeat") - public ResponseEntity heartbeat(@RequestBody String toolName) { - boolean success = redisRegistryService.refreshHeartbeat(toolName); + public ResponseEntity heartbeat(@RequestBody String uid) { + boolean success = redisRegistryService.refreshHeartbeat(uid); if (success) { return ResponseEntity.ok("Heartbeat updated"); } else { diff --git a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java index 2023454..81b95c9 100644 --- a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java +++ b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/registry/RedisRegistryService.java @@ -39,28 +39,28 @@ public class RedisRegistryService { * 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료) */ public void saveTool(ToolMetadata meta) { - String key = KEY_PREFIX + meta.getToolName(); + String key = KEY_PREFIX + meta.getUid(); meta.setLastHeartbeat(System.currentTimeMillis()); redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL); - log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getToolName()); + log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getUid()); } /** * 하트비트 갱신 (TTL 초기화) */ - public boolean refreshHeartbeat(String toolName) { - String key = KEY_PREFIX + toolName; + public boolean refreshHeartbeat(String uid) { + String key = KEY_PREFIX + uid; Boolean exists = redisTemplate.expire(key, DEFAULT_TTL); if (Boolean.TRUE.equals(exists)) { - log.debug(" [RedisRegistry] 하트비트 갱신: {}", toolName); + log.debug(" [RedisRegistry] 하트비트 갱신: {}", uid); return true; } else { - log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName); + log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid); return false; } } - public List getAvailablePods(String toolName) { - ToolMetadata tool = getTool(toolName); + public List getAvailablePods(String uid) { + ToolMetadata tool = getTool(uid); if (tool != null && tool.getPodUrl() != null) { return List.of(tool.getPodUrl()); @@ -71,8 +71,8 @@ public class RedisRegistryService { /** * 실행 시 툴 정보 조회 (Tool Execution 시 참조) */ - public ToolMetadata getTool(String toolName) { - return redisTemplate.opsForValue().get(KEY_PREFIX + toolName); + public ToolMetadata getTool(String uid) { + return redisTemplate.opsForValue().get(KEY_PREFIX + uid); } /** @@ -90,8 +90,8 @@ public class RedisRegistryService { /** * 툴 명시적 제거 (Deregister) */ - public void removeTool(String toolName) { - redisTemplate.delete(KEY_PREFIX + toolName); - log.info(" [RedisRegistry] 툴 삭제 완료: {}", toolName); + public void removeTool(String uid) { + redisTemplate.delete(KEY_PREFIX + uid); + log.info(" [RedisRegistry] 툴 삭제 완료: {}", uid); } } \ No newline at end of file diff --git a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/service/ToolPlanner.java b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/service/ToolPlanner.java index 0134551..36b09c6 100644 --- a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/service/ToolPlanner.java +++ b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/service/ToolPlanner.java @@ -73,19 +73,19 @@ public class ToolPlanner { log.info(" [Planner] Fallback 라우팅 매칭됨: {} -> {}", toolName, fallbackPodUrl); toolMetadata = ToolMetadata.builder() - .toolName(toolName) + .uid(toolName) .integrationType("DIRECT") .podUrl(fallbackPodUrl) .build(); } // 2-1. [신규] 도메인 그룹핑 기반 권한 검증 - if (tenantId != null && toolMetadata.getDomainGroup() != null) { + if (tenantId != null && toolMetadata.getCategoryKey() != null) { List allowedDomains = securityProperties.getTenantDomains().get(tenantId); if (allowedDomains == null || - (!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getDomainGroup()))) { - log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getDomainGroup()); - throw new SecurityException("해당 도메인(" + toolMetadata.getDomainGroup() + ")의 툴을 실행할 권한이 없습니다."); + (!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getCategoryKey()))) { + log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey()); + throw new SecurityException("해당 도메인(" + toolMetadata.getCategoryKey() + ")의 툴을 실행할 권한이 없습니다."); } } diff --git a/axhub-gateway/src/main/resources/static/admin/scaffold.html b/axhub-gateway/src/main/resources/static/admin/scaffold.html index 9b746c2..883bb40 100644 --- a/axhub-gateway/src/main/resources/static/admin/scaffold.html +++ b/axhub-gateway/src/main/resources/static/admin/scaffold.html @@ -283,7 +283,7 @@
- + @@ -353,7 +353,7 @@ 그룹 - Tool 이름 + 서브툴 이름 설명 Pod 모듈 인터페이스 (ID) @@ -386,12 +386,12 @@