fix: resolve workspace path resolution and enhance GrowToolHint generation in ToolScaffolder
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m8s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m8s
This commit is contained in:
@@ -108,6 +108,13 @@ public class ScaffoldingController {
|
|||||||
parseDelimited(req.get("tags")),
|
parseDelimited(req.get("tags")),
|
||||||
req.get("ownerOrg"));
|
req.get("ownerOrg"));
|
||||||
|
|
||||||
|
String defaultWorkspace = "c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dap-was-dapmt";
|
||||||
|
String workspacePath = req.getOrDefault("workspacePath", defaultWorkspace);
|
||||||
|
if (workspacePath.trim().isEmpty()) {
|
||||||
|
workspacePath = defaultWorkspace;
|
||||||
|
}
|
||||||
|
System.setProperty("AXHUB_SOURCE_DIR", workspacePath);
|
||||||
|
|
||||||
return ToolScaffolder.scaffold(baseName, interfaceId, title, description, group, routingType,
|
return ToolScaffolder.scaffold(baseName, interfaceId, title, description, group, routingType,
|
||||||
moduleName, author, date, register, clientSystemCode, inputSchemaResource,
|
moduleName, author, date, register, clientSystemCode, inputSchemaResource,
|
||||||
outputSchemaResource, inputFields, outputFields, httpApiName, definitionOptions);
|
outputSchemaResource, inputFields, outputFields, httpApiName, definitionOptions);
|
||||||
@@ -129,6 +136,9 @@ public class ScaffoldingController {
|
|||||||
? System.getProperty("user.name") : request.author().trim();
|
? System.getProperty("user.name") : request.author().trim();
|
||||||
String date = request.date() == null || request.date().isBlank()
|
String date = request.date() == null || request.date().isBlank()
|
||||||
? LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")) : request.date().trim();
|
? LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")) : request.date().trim();
|
||||||
|
String workspacePath = request.workspacePath() != null && !request.workspacePath().isBlank()
|
||||||
|
? request.workspacePath().trim() : "c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dap-was-dapmt";
|
||||||
|
System.setProperty("AXHUB_SOURCE_DIR", workspacePath);
|
||||||
return ToolScaffolder.scaffoldUseCase(request.useCaseName().trim(), moduleName, author, date,
|
return ToolScaffolder.scaffoldUseCase(request.useCaseName().trim(), moduleName, author, date,
|
||||||
request.tools() == null ? List.of() : request.tools());
|
request.tools() == null ? List.of() : request.tools());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -137,14 +147,15 @@ public class ScaffoldingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/usecases")
|
@GetMapping("/usecases")
|
||||||
public List<String> listUseCases(@RequestParam String moduleName, @RequestParam String categoryKey) {
|
public List<String> listUseCases(@RequestParam String moduleName, @RequestParam String categoryKey, @RequestParam(required = false) String workspacePath) {
|
||||||
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
||||||
throw new IllegalArgumentException("Invalid target module.");
|
throw new IllegalArgumentException("Invalid target module.");
|
||||||
}
|
}
|
||||||
if (categoryKey == null || !categoryKey.matches("^[a-z0-9]{3}$")) {
|
if (categoryKey == null || !categoryKey.matches("^[a-z0-9]{3}$")) {
|
||||||
throw new IllegalArgumentException("Invalid domain category.");
|
throw new IllegalArgumentException("Invalid domain category.");
|
||||||
}
|
}
|
||||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String sourceDir = (workspacePath != null && !workspacePath.isBlank()) ? workspacePath : System.getProperty("AXHUB_SOURCE_DIR");
|
||||||
|
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
||||||
File useCaseDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz/"
|
File useCaseDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz/"
|
||||||
+ categoryKey + "/usecase");
|
+ categoryKey + "/usecase");
|
||||||
@@ -156,11 +167,12 @@ public class ScaffoldingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/categories")
|
@GetMapping("/categories")
|
||||||
public List<String> listCategories(@RequestParam String moduleName) {
|
public List<String> listCategories(@RequestParam String moduleName, @RequestParam(required = false) String workspacePath) {
|
||||||
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
if (moduleName == null || !moduleName.matches("^dap-was-[a-z0-9-]+$")) {
|
||||||
throw new IllegalArgumentException("Invalid target module.");
|
throw new IllegalArgumentException("Invalid target module.");
|
||||||
}
|
}
|
||||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String sourceDir = (workspacePath != null && !workspacePath.isBlank()) ? workspacePath : System.getProperty("AXHUB_SOURCE_DIR");
|
||||||
|
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
if (sourceDir == null || sourceDir.isBlank()) sourceDir = System.getProperty("user.dir");
|
||||||
File bizDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz");
|
File bizDir = new File(sourceDir, moduleName + "/src/main/java/io/shinhanlife/dap/mcc/biz");
|
||||||
File[] dirs = bizDir.listFiles(File::isDirectory);
|
File[] dirs = bizDir.listFiles(File::isDirectory);
|
||||||
@@ -256,9 +268,10 @@ public class ScaffoldingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/modules")
|
@GetMapping("/modules")
|
||||||
public List<String> listModules() {
|
public List<String> listModules(@RequestParam(required = false) String workspacePath) {
|
||||||
try {
|
try {
|
||||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String sourceDir = (workspacePath != null && !workspacePath.isBlank()) ? workspacePath : System.getProperty("AXHUB_SOURCE_DIR");
|
||||||
|
if (sourceDir == null) sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
if (sourceDir == null) sourceDir = System.getProperty("user.dir");
|
if (sourceDir == null) sourceDir = System.getProperty("user.dir");
|
||||||
|
|
||||||
File dir = new File(sourceDir);
|
File dir = new File(sourceDir);
|
||||||
@@ -426,7 +439,7 @@ public class ScaffoldingController {
|
|||||||
List<ToolScaffolder.FieldDefinition> outputFields) {
|
List<ToolScaffolder.FieldDefinition> outputFields) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ToolGroupRequest(String useCaseName, String moduleName, String author, String date,
|
private record ToolGroupRequest(String useCaseName, String moduleName, String author, String date, String workspacePath,
|
||||||
List<ToolScaffolder.ToolMethodDefinition> tools) {
|
List<ToolScaffolder.ToolMethodDefinition> tools) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -825,6 +825,20 @@
|
|||||||
<!-- Tool Creation Form -->
|
<!-- Tool Creation Form -->
|
||||||
<div class="tab-pane fade" id="tool" role="tabpanel">
|
<div class="tab-pane fade" id="tool" role="tabpanel">
|
||||||
<form id="toolForm">
|
<form id="toolForm">
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="form-label">Workspace Path</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" name="workspacePath" id="workspacePathInput" placeholder="직접 폴더 경로를 입력(붙여넣기) 하거나 선택하세요" value="c:\eGovFrameDev-4.3.1-64bit\workspace-egov\dap-was-dapmt">
|
||||||
|
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="border-color: #3f3f46; color: #a1a1aa;">폴더 선택</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark" style="background-color: #18181b; border-color: #3f3f46;">
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('workspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dap-was-dapmt'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">dap-was-dapmt (기본)</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="const input = document.getElementById('workspacePathInput'); input.value='c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\axhub-backend-main'; input.dispatchEvent(new Event('change')); return false;" style="color: #f4f4f5;">axhub-backend-main</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="input-hint">직접 경로를 붙여넣거나 우측 <b>[폴더 선택]</b> 버튼을 눌러 선택하세요.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Target Module</label>
|
<label class="form-label">Target Module</label>
|
||||||
@@ -1521,7 +1535,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
fetch('/api/v1/scaffold/modules')
|
const loadModules = () => {
|
||||||
|
const workspacePath = document.querySelector('input[name="workspacePath"]')?.value || '';
|
||||||
|
fetch(`/api/v1/scaffold/modules?workspacePath=${encodeURIComponent(workspacePath)}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(modules => {
|
.then(modules => {
|
||||||
const select = document.getElementById('targetModuleSelect');
|
const select = document.getElementById('targetModuleSelect');
|
||||||
@@ -1533,6 +1549,13 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => console.error('Failed to load modules:', err));
|
.catch(err => console.error('Failed to load modules:', err));
|
||||||
|
};
|
||||||
|
loadModules();
|
||||||
|
const wsInput = document.querySelector('input[name="workspacePath"]');
|
||||||
|
if (wsInput) {
|
||||||
|
wsInput.addEventListener('change', loadModules);
|
||||||
|
wsInput.addEventListener('blur', loadModules); // Typing custom path and clicking away
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
|
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
|
||||||
@@ -1603,12 +1626,13 @@
|
|||||||
|
|
||||||
async function loadUseCasesForSelection() {
|
async function loadUseCasesForSelection() {
|
||||||
const moduleName = document.getElementById('targetModuleSelect').value;
|
const moduleName = document.getElementById('targetModuleSelect').value;
|
||||||
|
const workspacePath = document.querySelector('input[name="workspacePath"]')?.value || '';
|
||||||
const categoryKey = document.querySelector('#toolForm [name="categoryKey"]').value.trim();
|
const categoryKey = document.querySelector('#toolForm [name="categoryKey"]').value.trim();
|
||||||
const select = document.getElementById('toolGroupUseCaseSelect');
|
const select = document.getElementById('toolGroupUseCaseSelect');
|
||||||
select.innerHTML = '<option value="">새 UseCase 생성</option>';
|
select.innerHTML = '<option value="">새 UseCase 생성</option>';
|
||||||
if (!/^[a-z0-9]{3}$/.test(categoryKey)) return;
|
if (!/^[a-z0-9]{3}$/.test(categoryKey)) return;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/scaffold/usecases?moduleName=${encodeURIComponent(moduleName)}&categoryKey=${encodeURIComponent(categoryKey)}`);
|
const response = await fetch(`/api/v1/scaffold/usecases?moduleName=${encodeURIComponent(moduleName)}&categoryKey=${encodeURIComponent(categoryKey)}&workspacePath=${encodeURIComponent(workspacePath)}`);
|
||||||
if (!response.ok) throw new Error('UseCase 목록 조회 실패');
|
if (!response.ok) throw new Error('UseCase 목록 조회 실패');
|
||||||
const useCases = await response.json();
|
const useCases = await response.json();
|
||||||
useCases.forEach(useCaseName => select.add(new Option(`기존 ${useCaseName}에 함수 추가`, useCaseName)));
|
useCases.forEach(useCaseName => select.add(new Option(`기존 ${useCaseName}에 함수 추가`, useCaseName)));
|
||||||
@@ -1619,11 +1643,12 @@
|
|||||||
|
|
||||||
async function loadCategoriesForSelection() {
|
async function loadCategoriesForSelection() {
|
||||||
const moduleName = document.getElementById('targetModuleSelect').value;
|
const moduleName = document.getElementById('targetModuleSelect').value;
|
||||||
|
const workspacePath = document.querySelector('input[name="workspacePath"]')?.value || '';
|
||||||
const select = document.getElementById('domainCategorySelect');
|
const select = document.getElementById('domainCategorySelect');
|
||||||
const nameInput = document.getElementById('domainCategoryName');
|
const nameInput = document.getElementById('domainCategoryName');
|
||||||
select.innerHTML = '<option value="">새 도메인 생성</option>';
|
select.innerHTML = '<option value="">새 도메인 생성</option>';
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/scaffold/categories?moduleName=${encodeURIComponent(moduleName)}`);
|
const response = await fetch(`/api/v1/scaffold/categories?moduleName=${encodeURIComponent(moduleName)}&workspacePath=${encodeURIComponent(workspacePath)}`);
|
||||||
if (!response.ok) throw new Error('Category 목록 조회 실패');
|
if (!response.ok) throw new Error('Category 목록 조회 실패');
|
||||||
const categories = await response.json();
|
const categories = await response.json();
|
||||||
categories.forEach(cat => select.add(new Option(`기존 ${cat} 도메인`, cat)));
|
categories.forEach(cat => select.add(new Option(`기존 ${cat} 도메인`, cat)));
|
||||||
@@ -1687,7 +1712,7 @@
|
|||||||
|
|
||||||
fetch('/api/v1/scaffold/tool-group', {
|
fetch('/api/v1/scaffold/tool-group', {
|
||||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({useCaseName, moduleName: data.moduleName, author: data.author, date: data.date, tools})
|
body: JSON.stringify({useCaseName, moduleName: data.moduleName, author: data.author, date: data.date, workspacePath: data.workspacePath, tools})
|
||||||
}).then(response => response.text()).then(result => {
|
}).then(response => response.text()).then(result => {
|
||||||
const resultBox = document.getElementById('resultBox');
|
const resultBox = document.getElementById('resultBox');
|
||||||
resultBox.style.display = 'block'; resultBox.className = result.startsWith('Error:') ? 'error' : 'success';
|
resultBox.style.display = 'block'; resultBox.className = result.startsWith('Error:') ? 'error' : 'success';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스입니다.
|
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스입니다.
|
||||||
@@ -79,8 +80,11 @@ public class ToolScaffolder {
|
|||||||
String group = tools.getFirst().group().toLowerCase(Locale.ROOT);
|
String group = tools.getFirst().group().toLowerCase(Locale.ROOT);
|
||||||
validateToolMethods(tools, group);
|
validateToolMethods(tools, group);
|
||||||
|
|
||||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String sourceDir = System.getProperty("AXHUB_SOURCE_DIR");
|
||||||
Path rootDir = sourceDir == null ? Paths.get(".") : Paths.get(sourceDir);
|
if (sourceDir == null) {
|
||||||
|
sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
}
|
||||||
|
Path rootDir = sourceDir != null ? Paths.get(sourceDir) : Paths.get(".");
|
||||||
Path configuredModule = Paths.get(moduleName);
|
Path configuredModule = Paths.get(moduleName);
|
||||||
Path moduleRoot = configuredModule.isAbsolute() ? configuredModule : rootDir.resolve(configuredModule);
|
Path moduleRoot = configuredModule.isAbsolute() ? configuredModule : rootDir.resolve(configuredModule);
|
||||||
Path sourceRoot = moduleRoot.resolve(BASE_PACKAGE_PATH);
|
Path sourceRoot = moduleRoot.resolve(BASE_PACKAGE_PATH);
|
||||||
@@ -199,9 +203,6 @@ public class ToolScaffolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String toolName = toToolName(moduleName, tool.group(), baseName);
|
String toolName = toToolName(moduleName, tool.group(), baseName);
|
||||||
writeUtf8(definitionDir.resolve(toolName + ".yml"), toolDefinitionContentV17(toolName,
|
|
||||||
option(tool.title(), baseName), tool.description(), tool.group(), tool.interfaceId(),
|
|
||||||
tool.inputFields(), isMutationTool(baseName), tool.definitionOptions()));
|
|
||||||
writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields()));
|
writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields()));
|
||||||
log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n");
|
log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n");
|
||||||
}
|
}
|
||||||
@@ -214,12 +215,43 @@ public class ToolScaffolder {
|
|||||||
String baseName = toPascalCase(tool.baseName());
|
String baseName = toPascalCase(tool.baseName());
|
||||||
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
|
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
|
||||||
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n");
|
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n");
|
||||||
|
boolean isMutation = isMutationTool(baseName);
|
||||||
|
ToolDefinitionOptions opts = tool.definitionOptions() == null ? new ToolDefinitionOptions(null, null, null, null, null, null, null, null) : tool.definitionOptions();
|
||||||
|
|
||||||
methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName))
|
methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName))
|
||||||
.append("\", title = \"").append(javaText(option(tool.title(), baseName)))
|
.append("\", title = \"").append(javaText(option(tool.title(), baseName)))
|
||||||
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
|
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
|
||||||
.append(" @GrowToolHint(register = ").append(tool.register()).append(", categoryKey = \"")
|
.append(" @GrowToolHint(\n")
|
||||||
.append(tool.group().toLowerCase(Locale.ROOT)).append("\", mappingId = \"")
|
.append(" requiresApproval = ").append(isMutation).append(",\n")
|
||||||
.append(javaText(tool.interfaceId())).append("\")\n")
|
.append(" categoryKey = \"").append(tool.group().toLowerCase(Locale.ROOT)).append("\",\n");
|
||||||
|
if (tool.interfaceId() != null && !tool.interfaceId().isBlank()) {
|
||||||
|
methods.append(" mappingId = \"").append(javaText(tool.interfaceId())).append("\",\n");
|
||||||
|
}
|
||||||
|
methods.append(" functionDescription = \"").append(javaText(opts.functionDescription() != null && !opts.functionDescription().isBlank() ? opts.functionDescription() : option(tool.title(), baseName) + " 기능을 수행합니다.")).append("\",\n")
|
||||||
|
.append(" whenToUse = \"").append(javaText(opts.whenToUse() != null && !opts.whenToUse().isBlank() ? opts.whenToUse() : "사용자가 이 업무 기능의 실행 또는 조회를 요청할 때 사용합니다.")).append("\",\n")
|
||||||
|
.append(" whenNotToUse = \"").append(javaText(opts.whenNotToUse() != null && !opts.whenNotToUse().isBlank() ? opts.whenNotToUse() : "정보 변경이나 실행 작업에는 사용하지 않습니다.")).append("\",\n")
|
||||||
|
.append(" ioLimits = \"").append(javaText(opts.ioLimits() != null && !opts.ioLimits().isBlank() ? opts.ioLimits() : "정의된 입력 항목만 허용하며 업무 결과만 반환합니다.")).append("\",\n")
|
||||||
|
.append(" displayDescription = \"").append(javaText(opts.displayDescription() != null && !opts.displayDescription().isBlank() ? opts.displayDescription() : option(tool.title(), baseName) + " 정보를 처리합니다.")).append("\",\n");
|
||||||
|
|
||||||
|
List<String> examples = opts.exampleQueries();
|
||||||
|
if (examples == null || examples.isEmpty()) {
|
||||||
|
examples = List.of(option(tool.title(), baseName) + " 정보를 보여줘", option(tool.title(), baseName) + " 확인해줘", "현재 " + option(tool.title(), baseName) + " 알려줘");
|
||||||
|
}
|
||||||
|
methods.append(" exampleQueries = {")
|
||||||
|
.append(examples.stream().map(q -> "\"" + javaText(q) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n")
|
||||||
|
.append(" destructive = ").append(isMutation).append(",\n")
|
||||||
|
.append(" idempotent = ").append(!isMutation).append(",\n");
|
||||||
|
|
||||||
|
List<String> tags = opts.tags();
|
||||||
|
if (tags == null || tags.isEmpty()) {
|
||||||
|
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
|
||||||
|
}
|
||||||
|
methods.append(" tags = {")
|
||||||
|
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n")
|
||||||
|
.append(" ownerOrg = \"").append(javaText(opts.ownerOrg() != null && !opts.ownerOrg().isBlank() ? opts.ownerOrg() : "MCP_TOOL")).append("\"\n")
|
||||||
|
.append(" )\n")
|
||||||
.append(" ").append(baseName).append("Response ").append(tool.methodName()).append("(")
|
.append(" ").append(baseName).append("Response ").append(tool.methodName()).append("(")
|
||||||
.append(baseName).append("Request req);\n\n");
|
.append(baseName).append("Request req);\n\n");
|
||||||
}
|
}
|
||||||
@@ -327,11 +359,47 @@ public class ToolScaffolder {
|
|||||||
|
|
||||||
useCase = addImport(useCase, "import " + bizPackage + ".dto." + requestType + ";") ;
|
useCase = addImport(useCase, "import " + bizPackage + ".dto." + requestType + ";") ;
|
||||||
useCase = addImport(useCase, "import " + bizPackage + ".dto." + responseType + ";") ;
|
useCase = addImport(useCase, "import " + bizPackage + ".dto." + responseType + ";") ;
|
||||||
String declaration = "\n @McpTool(name = \"" + toolName + "\", title = \"" + javaText(option(tool.title(), baseName))
|
boolean isMutation = isMutationTool(baseName);
|
||||||
+ "\", description = \"" + javaText(option(tool.description(), "")) + "\")\n"
|
ToolDefinitionOptions opts = tool.definitionOptions() == null ? new ToolDefinitionOptions(null, null, null, null, null, null, null, null) : tool.definitionOptions();
|
||||||
+ " @GrowToolHint(register = " + tool.register() + ", categoryKey = \"" + tool.group().toLowerCase(Locale.ROOT)
|
|
||||||
+ "\", mappingId = \"" + javaText(option(tool.interfaceId(), tool.httpApiName())) + "\")\n"
|
StringBuilder declBuilder = new StringBuilder("\n @McpTool(name = \"").append(toolName)
|
||||||
+ " " + responseType + " " + methodName + "(" + requestType + " req);\n";
|
.append("\", title = \"").append(javaText(option(tool.title(), baseName)))
|
||||||
|
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
|
||||||
|
.append(" @GrowToolHint(\n")
|
||||||
|
.append(" requiresApproval = ").append(isMutation).append(",\n")
|
||||||
|
.append(" categoryKey = \"").append(tool.group().toLowerCase(Locale.ROOT)).append("\",\n");
|
||||||
|
String mappingId = option(tool.interfaceId(), tool.httpApiName());
|
||||||
|
if (mappingId != null && !mappingId.isBlank()) {
|
||||||
|
declBuilder.append(" mappingId = \"").append(javaText(mappingId)).append("\",\n");
|
||||||
|
}
|
||||||
|
declBuilder.append(" functionDescription = \"").append(javaText(opts.functionDescription() != null && !opts.functionDescription().isBlank() ? opts.functionDescription() : option(tool.title(), baseName) + " 기능을 수행합니다.")).append("\",\n")
|
||||||
|
.append(" whenToUse = \"").append(javaText(opts.whenToUse() != null && !opts.whenToUse().isBlank() ? opts.whenToUse() : "사용자가 이 업무 기능의 실행 또는 조회를 요청할 때 사용합니다.")).append("\",\n")
|
||||||
|
.append(" whenNotToUse = \"").append(javaText(opts.whenNotToUse() != null && !opts.whenNotToUse().isBlank() ? opts.whenNotToUse() : "정보 변경이나 실행 작업에는 사용하지 않습니다.")).append("\",\n")
|
||||||
|
.append(" ioLimits = \"").append(javaText(opts.ioLimits() != null && !opts.ioLimits().isBlank() ? opts.ioLimits() : "정의된 입력 항목만 허용하며 업무 결과만 반환합니다.")).append("\",\n")
|
||||||
|
.append(" displayDescription = \"").append(javaText(opts.displayDescription() != null && !opts.displayDescription().isBlank() ? opts.displayDescription() : option(tool.title(), baseName) + " 정보를 처리합니다.")).append("\",\n");
|
||||||
|
|
||||||
|
List<String> examples = opts.exampleQueries();
|
||||||
|
if (examples == null || examples.isEmpty()) {
|
||||||
|
examples = List.of(option(tool.title(), baseName) + " 정보를 보여줘", option(tool.title(), baseName) + " 확인해줘", "현재 " + option(tool.title(), baseName) + " 알려줘");
|
||||||
|
}
|
||||||
|
declBuilder.append(" exampleQueries = {")
|
||||||
|
.append(examples.stream().map(q -> "\"" + javaText(q) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n")
|
||||||
|
.append(" destructive = ").append(isMutation).append(",\n")
|
||||||
|
.append(" idempotent = ").append(!isMutation).append(",\n");
|
||||||
|
|
||||||
|
List<String> tags = opts.tags();
|
||||||
|
if (tags == null || tags.isEmpty()) {
|
||||||
|
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
|
||||||
|
}
|
||||||
|
declBuilder.append(" tags = {")
|
||||||
|
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n")
|
||||||
|
.append(" ownerOrg = \"").append(javaText(opts.ownerOrg() != null && !opts.ownerOrg().isBlank() ? opts.ownerOrg() : "MCP_TOOL")).append("\"\n")
|
||||||
|
.append(" )\n")
|
||||||
|
.append(" ").append(responseType).append(" ").append(methodName).append("(").append(requestType).append(" req);\n");
|
||||||
|
|
||||||
|
String declaration = declBuilder.toString();
|
||||||
useCase = insertBeforeLastBrace(useCase, declaration);
|
useCase = insertBeforeLastBrace(useCase, declaration);
|
||||||
|
|
||||||
implementation = addImport(implementation, "import " + bizPackage + ".dto." + requestType + ";");
|
implementation = addImport(implementation, "import " + bizPackage + ".dto." + requestType + ";");
|
||||||
@@ -456,6 +524,12 @@ public class ToolScaffolder {
|
|||||||
String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/";
|
String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/";
|
||||||
String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null;
|
String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null;
|
||||||
String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null;
|
String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null;
|
||||||
|
String defaultWorkspace = "c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dap-was-dapmt";
|
||||||
|
String workspace = getOrAsk(args, 10, scanner, "11. 대상 프로젝트 워크스페이스 경로 (default: " + defaultWorkspace + "): ");
|
||||||
|
if (workspace.trim().isEmpty()) {
|
||||||
|
workspace = defaultWorkspace;
|
||||||
|
}
|
||||||
|
System.setProperty("AXHUB_SOURCE_DIR", workspace);
|
||||||
|
|
||||||
String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of());
|
String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of());
|
||||||
System.out.println(result);
|
System.out.println(result);
|
||||||
@@ -513,7 +587,10 @@ public class ToolScaffolder {
|
|||||||
title = title == null || title.isBlank() ? baseName : title.trim();
|
title = title == null || title.isBlank() ? baseName : title.trim();
|
||||||
description = description == null ? "" : description.trim();
|
description = description == null ? "" : description.trim();
|
||||||
httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(baseName) : httpApiName.trim();
|
httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(baseName) : httpApiName.trim();
|
||||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String envSourceDir = System.getProperty("AXHUB_SOURCE_DIR");
|
||||||
|
if (envSourceDir == null) {
|
||||||
|
envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
|
}
|
||||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
|
|
||||||
Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase"));
|
Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase"));
|
||||||
@@ -654,14 +731,45 @@ public class ToolScaffolder {
|
|||||||
|
|
||||||
String toolName = toToolName(moduleName, group, baseName);
|
String toolName = toToolName(moduleName, group, baseName);
|
||||||
|
|
||||||
String toolHintLine;
|
boolean isMutation = isMutationTool(baseName);
|
||||||
|
StringBuilder sb = new StringBuilder(" @GrowToolHint(\n");
|
||||||
if (useSchemaResource) {
|
if (useSchemaResource) {
|
||||||
toolHintLine = (" @GrowToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\",\n" +
|
sb.append(" inputSchemaResource = \"").append(inputSchemaClasspath).append("\",\n");
|
||||||
" inputSchemaResource = \"%s\",\n" +
|
sb.append(" outputSchemaResource = \"").append(outputSchemaClasspath).append("\",\n");
|
||||||
" outputSchemaResource = \"%s\")").formatted(register, group.toLowerCase(Locale.ROOT), interfaceId, inputSchemaClasspath, outputSchemaClasspath);
|
|
||||||
} else {
|
|
||||||
toolHintLine = " @GrowToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\")".formatted(register, group.toLowerCase(Locale.ROOT), interfaceId);
|
|
||||||
}
|
}
|
||||||
|
sb.append(" requiresApproval = ").append(isMutation).append(",\n");
|
||||||
|
sb.append(" categoryKey = \"").append(group.toLowerCase(Locale.ROOT)).append("\",\n");
|
||||||
|
if (interfaceId != null && !interfaceId.isBlank()) {
|
||||||
|
sb.append(" mappingId = \"").append(interfaceId).append("\",\n");
|
||||||
|
}
|
||||||
|
sb.append(" functionDescription = \"").append(javaText(definitionOptions.functionDescription() != null && !definitionOptions.functionDescription().isBlank() ? definitionOptions.functionDescription() : title + " 기능을 수행합니다.")).append("\",\n");
|
||||||
|
sb.append(" whenToUse = \"").append(javaText(definitionOptions.whenToUse() != null && !definitionOptions.whenToUse().isBlank() ? definitionOptions.whenToUse() : "사용자가 이 업무 기능의 실행 또는 조회를 요청할 때 사용합니다.")).append("\",\n");
|
||||||
|
sb.append(" whenNotToUse = \"").append(javaText(definitionOptions.whenNotToUse() != null && !definitionOptions.whenNotToUse().isBlank() ? definitionOptions.whenNotToUse() : "정보 변경이나 실행 작업에는 사용하지 않습니다.")).append("\",\n");
|
||||||
|
sb.append(" ioLimits = \"").append(javaText(definitionOptions.ioLimits() != null && !definitionOptions.ioLimits().isBlank() ? definitionOptions.ioLimits() : "정의된 입력 항목만 허용하며 업무 결과만 반환합니다.")).append("\",\n");
|
||||||
|
sb.append(" displayDescription = \"").append(javaText(definitionOptions.displayDescription() != null && !definitionOptions.displayDescription().isBlank() ? definitionOptions.displayDescription() : title + " 정보를 처리합니다.")).append("\",\n");
|
||||||
|
|
||||||
|
List<String> examples = definitionOptions.exampleQueries();
|
||||||
|
if (examples == null || examples.isEmpty()) {
|
||||||
|
examples = List.of(title + " 정보를 보여줘", title + " 확인해줘", "현재 " + title + " 알려줘");
|
||||||
|
}
|
||||||
|
sb.append(" exampleQueries = {")
|
||||||
|
.append(examples.stream().map(q -> "\"" + javaText(q) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n");
|
||||||
|
|
||||||
|
sb.append(" destructive = ").append(isMutation).append(",\n");
|
||||||
|
sb.append(" idempotent = ").append(!isMutation).append(",\n");
|
||||||
|
|
||||||
|
List<String> tags = definitionOptions.tags();
|
||||||
|
if (tags == null || tags.isEmpty()) {
|
||||||
|
tags = List.of(group.toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
|
||||||
|
}
|
||||||
|
sb.append(" tags = {")
|
||||||
|
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
|
||||||
|
.append("},\n");
|
||||||
|
|
||||||
|
sb.append(" ownerOrg = \"").append(javaText(definitionOptions.ownerOrg() != null && !definitionOptions.ownerOrg().isBlank() ? definitionOptions.ownerOrg() : "MCP_TOOL")).append("\"\n");
|
||||||
|
sb.append(" )");
|
||||||
|
String toolHintLine = sb.toString();
|
||||||
|
|
||||||
String serviceInterfaceContent = """
|
String serviceInterfaceContent = """
|
||||||
package %s.usecase;
|
package %s.usecase;
|
||||||
@@ -1269,11 +1377,8 @@ public class ToolScaffolder {
|
|||||||
writeUtf8(generatedTestPath, useCaseTestContent(bizPackage, baseName));
|
writeUtf8(generatedTestPath, useCaseTestContent(bizPackage, baseName));
|
||||||
log.append("[Unit Test] ").append(generatedTestPath).append("\\n");
|
log.append("[Unit Test] ").append(generatedTestPath).append("\\n");
|
||||||
log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n");
|
log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n");
|
||||||
Files.createDirectories(definitionDir);
|
// Tool Definition YML is no longer generated. We use @GrowToolHint instead.
|
||||||
Path definitionPath = definitionDir.resolve(toolName + ".yml");
|
// log.append("[V17 Tool Definition] ...\n");
|
||||||
writeUtf8(definitionPath, toolDefinitionContentV17(toolName, title, description, group,
|
|
||||||
interfaceId, inputFields, isMutationTool(baseName), definitionOptions));
|
|
||||||
log.append("[V17 Tool Definition] ").append(definitionPath).append("\n");
|
|
||||||
log.append("\n Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n");
|
log.append("\n Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n");
|
||||||
|
|
||||||
return log.toString();
|
return log.toString();
|
||||||
@@ -1894,10 +1999,8 @@ public class ToolScaffolder {
|
|||||||
return supportedType(itemType);
|
return supportedType(itemType);
|
||||||
}
|
}
|
||||||
private static String supportedType(String type) {
|
private static String supportedType(String type) {
|
||||||
return switch (type == null ? "String" : type) {
|
if (type == null || type.isBlank()) return "String";
|
||||||
case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type;
|
return type;
|
||||||
default -> throw new IllegalArgumentException("Unsupported field type: " + type);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user