From 67b83ff35680be26d3e73ee81ba57d44efd1c2dd Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 9 Jul 2026 23:58:42 +0900 Subject: [PATCH] feat: Tool metadata register flag and dynamic source updater UI --- .../biz/mcp/gateway/dto/ToolMetadata.java | 17 +- .../axhub/common/util/ToolScaffolder.java | 54 +++--- .../axhub/common/util/ToolSourceUpdater.java | 81 +++++++++ .../biz/mcp/gateway/dto/ToolMetadata.java | 17 +- .../presentation/ScaffoldingController.java | 18 +- .../main/resources/static/admin/scaffold.html | 159 +++++++++++++++++- .../axhub/biz/mcp/tool/dto/ToolMetadata.java | 16 +- 7 files changed, 327 insertions(+), 35 deletions(-) create mode 100644 axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java 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 bf44216..c3350bb 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 @@ -55,11 +55,24 @@ public class ToolMetadata { private String podUrl; // 2-4. 가시성 여부 - private boolean visible = true; + @Builder.Default + private Boolean visible = true; // 2-5. Redis 등록 여부 (UI 표출용) @Builder.Default - private boolean isRegistered = true; + private Boolean isRegistered = true; + + public boolean isVisible() { + return visible != null ? visible : true; + } + + public boolean isRegistered() { + return isRegistered != null ? isRegistered : true; + } + + public void setRegistered(Boolean isRegistered) { + this.isRegistered = isRegistered; + } // 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI) private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI" 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 0a41694..aabca1a 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 @@ -68,7 +68,7 @@ public class ToolScaffolder { String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): "); if (createDate.trim().isEmpty()) createDate = defaultDate; - String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate); + String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true); System.out.println(result); } @@ -80,7 +80,7 @@ public class ToolScaffolder { return scanner.nextLine().trim(); } - public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate) throws IOException { + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register) throws IOException { Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service"); Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto"); @@ -186,39 +186,55 @@ public class ToolScaffolder { name = "%s", description = "%s", prompt = "%s", - mappingId = "%s" + mappingId = "%s", + register = %s ) public Object execute(%sReq req) { return executeLegacy("%s", "%s", req); } } """.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, + toolName, description, description + " 해줘.", interfaceId, register, baseName, routingType, interfaceId ); Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent); - // Append to YAML if exists - Path yamlPath = Paths.get(moduleName, "src/main/resources", "application-local.yml"); + // Append to YAML + Path resourcesDir = Paths.get(moduleName, "src/main/resources"); + Files.createDirectories(resourcesDir); + Path yamlPath = resourcesDir.resolve("application-local.yml"); + + String yamlContent = ""; if (Files.exists(yamlPath)) { - String yamlContent = Files.readString(yamlPath); - if (yamlContent.contains("functions:")) { - String newFunctionYaml = """ - - %s: - description: "%s" - prompt: "%s" - mappingId: "%s" - """.formatted(toolName, description, description + " 해줘.", interfaceId); - yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml); - Files.writeString(yamlPath, yamlContent); - log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n"); - } + yamlContent = Files.readString(yamlPath); } + String newFunctionYaml = """ + + %s: + description: "%s" + prompt: "%s" + mappingId: "%s" + """.formatted(toolName, description, description + " 해줘.", interfaceId); + + if (yamlContent.contains("functions:")) { + yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml); + } else { + if (!yamlContent.isEmpty() && !yamlContent.endsWith("\n")) { + yamlContent += "\n"; + } + yamlContent += """ +mcp: + functions:%s""".formatted(newFunctionYaml); + } + + Files.writeString(yamlPath, yamlContent); + log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n"); + log.append("\n=========================================\n"); log.append(" Scaffolding Complete!\n"); log.append("=========================================\n"); diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java new file mode 100644 index 0000000..bf3b65d --- /dev/null +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/util/ToolSourceUpdater.java @@ -0,0 +1,81 @@ +package io.shinhanlife.axhub.common.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ToolSourceUpdater { + + public static void updateToolSource(String toolName, String domainGroup, String description, boolean register) throws Exception { + // 1. Find all *Service.java files in axhub-tool-* directories + Path rootDir = Paths.get("."); + + List javaFiles; + try (Stream paths = Files.walk(rootDir)) { + javaFiles = paths + .filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith("Service.java")) + .filter(p -> p.toString().contains("axhub-tool-")) + .collect(Collectors.toList()); + } + + Path targetFile = null; + String content = null; + + // 2. Find the specific file for the tool + Pattern namePattern = Pattern.compile("@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"", Pattern.DOTALL); + + for (Path path : javaFiles) { + String text = Files.readString(path); + if (namePattern.matcher(text).find()) { + targetFile = path; + content = text; + break; + } + } + + if (targetFile == null) { + throw new Exception("소스 코드를 찾을 수 없습니다: " + toolName); + } + + // 3. Update @McpTool group + if (domainGroup != null && !domainGroup.trim().isEmpty()) { + Pattern groupPattern = Pattern.compile("(@McpTool\\\\s*\\\\([^)]*group\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL); + Matcher groupMatcher = groupPattern.matcher(content); + if (groupMatcher.find()) { + content = groupMatcher.replaceFirst("$1" + domainGroup + "$3"); + } + } + + // 4. Update @McpFunction description + if (description != null) { + Pattern funcPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*description\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL); + Matcher funcMatcher = funcPattern.matcher(content); + if (funcMatcher.find()) { + content = funcMatcher.replaceFirst("$1" + description.replace("\\", "\\\\").replace("$", "\\\\$") + "$3"); + } + } + + // 5. Update register flag + Pattern regPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*register\\\\s*=\\\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL); + Matcher regMatcher = regPattern.matcher(content); + if (regMatcher.find()) { + content = regMatcher.replaceFirst("$1" + register + "$3"); + } else { + Pattern addRegPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\")", Pattern.DOTALL); + Matcher addRegMatcher = addRegPattern.matcher(content); + if (addRegMatcher.find()) { + content = addRegMatcher.replaceFirst("$1, register = " + register); + } + } + + // 6. Write back to file + Files.writeString(targetFile, content); + } +} 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 6b3d808..08060aa 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 @@ -55,11 +55,24 @@ public class ToolMetadata { private String podUrl; // 2-4. 가시성 여부 - private boolean visible = true; + @Builder.Default + private Boolean visible = true; // 2-5. Redis 등록 여부 (UI 표출용) @Builder.Default - private boolean isRegistered = true; + private Boolean isRegistered = true; + + public boolean isVisible() { + return visible != null ? visible : true; + } + + public boolean isRegistered() { + return isRegistered != null ? isRegistered : true; + } + + public void setRegistered(Boolean isRegistered) { + this.isRegistered = isRegistered; + } // 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI) private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI" diff --git a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/ScaffoldingController.java b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/ScaffoldingController.java index 3af0cfc..0ab7c20 100644 --- a/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/ScaffoldingController.java +++ b/axhub-gateway/src/main/java/io/shinhanlife/axhub/biz/mcp/gateway/presentation/ScaffoldingController.java @@ -36,8 +36,24 @@ public class ScaffoldingController { String moduleName = req.getOrDefault("moduleName", "axhub-tool-other"); String author = req.getOrDefault("author", "System"); String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd"))); + boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true")); - return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date); + return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register); + } catch (Exception e) { + return "오류 발생: " + e.getMessage(); + } + } + + @PostMapping("/tool/update") + public String updateTool(@RequestBody Map req) { + try { + String toolName = req.get("toolName"); + String domainGroup = req.get("domainGroup"); + String description = req.get("description"); + boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true")); + + io.shinhanlife.axhub.common.util.ToolSourceUpdater.updateToolSource(toolName, domainGroup, description, register); + return "성공"; } catch (Exception e) { return "오류 발생: " + e.getMessage(); } diff --git a/axhub-gateway/src/main/resources/static/admin/scaffold.html b/axhub-gateway/src/main/resources/static/admin/scaffold.html index a9cfd2d..9b746c2 100644 --- a/axhub-gateway/src/main/resources/static/admin/scaffold.html +++ b/axhub-gateway/src/main/resources/static/admin/scaffold.html @@ -301,11 +301,21 @@ - -
- - -
코드가 생성될 대상 프로젝트(Pod) 폴더명입니다.
+ +
+
+ + +
코드가 생성될 대상 프로젝트(Pod) 폴더명입니다.
+
+
+ + +
Gateway에서 자동 라우팅될지 여부입니다.
+
@@ -331,6 +341,9 @@ Redis에 등록되지 않은(비공개) Tool도 포함되어 조회됩니다.
+
@@ -362,6 +375,48 @@
+ + + diff --git a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/dto/ToolMetadata.java b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/dto/ToolMetadata.java index fd57bde..ac84b20 100644 --- a/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/dto/ToolMetadata.java +++ b/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/tool/dto/ToolMetadata.java @@ -40,9 +40,21 @@ public class ToolMetadata { @Builder.Default - private boolean visible = true; + private Boolean visible = true; @Builder.Default - private boolean isRegistered = true; + private Boolean isRegistered = true; + + public boolean isVisible() { + return visible != null ? visible : true; + } + + public boolean isRegistered() { + return isRegistered != null ? isRegistered : true; + } + + public void setRegistered(Boolean isRegistered) { + this.isRegistered = isRegistered; + } } \ No newline at end of file