refactor: rename dasmt to datmt across the project
All checks were successful
Deploy Tools / deploy (push) Successful in 1m11s

This commit is contained in:
jade
2026-08-31 13:21:33 +09:00
parent 8b9bd4de16
commit 740ef1c757
11 changed files with 448 additions and 438 deletions

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dat.lib.util;
package io.shinhanlife.dat.lib.util;
import java.io.IOException;
@@ -16,28 +16,28 @@ import java.util.Scanner;
import java.util.stream.Collectors;
/**
* MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스입니다.
* MCP Tool 肄붾뱶瑜??먮룞 ?앹꽦(Scaffolding)?섎뒗 ?좏떥由ы떚 ?대옒?ㅼ엯?덈떎.
*
* [실행 방법]
* 방법 1. IDE(IntelliJ 등)에서 직접 실행
* - ToolScaffolder.javamain 메서드를 실행합니다.
* - 콘솔 질문에 차례대로 값을 입력하면 파일이 생성됩니다.
* [?ㅽ뻾 諛⑸쾿]
* 諛⑸쾿 1. IDE(IntelliJ ???먯꽌 吏곸젒 ?ㅽ뻾
* - ToolScaffolder.java??main 硫붿꽌?쒕? ?ㅽ뻾?⑸땲??
* - 肄섏넄 吏덈Ц??李⑤??€濡?媛믪쓣 ?낅젰?섎㈃ ?뚯씪???앹꽦?⑸땲??
*
* 방법 2. 명령줄에서 실행
* - 컴파일: javac -encoding UTF-8 dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java
* - 실행: java -cp dat-was-lib/src/main/java io.shinhanlife.dat.lib.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]"
* 諛⑸쾿 2. 紐낅졊以꾩뿉???ㅽ뻾
* - 而댄뙆?? javac -encoding UTF-8 dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java
* - ?ㅽ뻾: java -cp dat-was-lib/src/main/java io.shinhanlife.dat.lib.util.ToolScaffolder [?대쫫] [ID] "[?ㅻ챸]" "[洹몃9]" "[?듭떊諛⑹떇]" "[紐⑤뱢紐?"
*/
/**
* @package io.shinhanlife.dat.lib.util
* @className ToolScaffolder
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
* 2026.09.01 0986406 理쒖큹?앹꽦
*
* </pre>
*/
@@ -237,15 +237,15 @@ public class ToolScaffolder {
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");
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() : "?뺤쓽???낅젰 ??ぉ留??덉슜?섎ʼn ?낅Т 寃곌낵留?諛섑솚?⑸땲??")).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) + " 알려줘");
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(", ")))
@@ -255,7 +255,7 @@ public class ToolScaffolder {
List<String> tags = opts.tags();
if (tags == null || tags.isEmpty()) {
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "泥섎━" : "議고쉶");
}
methods.append(" tags = {")
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
@@ -392,15 +392,15 @@ public class ToolScaffolder {
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");
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() : "?뺤쓽???낅젰 ??ぉ留??덉슜?섎ʼn ?낅Т 寃곌낵留?諛섑솚?⑸땲??")).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) + " 알려줘");
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(", ")))
@@ -410,7 +410,7 @@ public class ToolScaffolder {
List<String> tags = opts.tags();
if (tags == null || tags.isEmpty()) {
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
tags = List.of(tool.group().toLowerCase(Locale.ROOT), isMutation ? "泥섎━" : "議고쉶");
}
declBuilder.append(" tags = {")
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
@@ -525,8 +525,8 @@ public class ToolScaffolder {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: ");
String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): ");
String baseName = getOrAsk(args, 0, scanner, "1. ?앹꽦??Tool??湲곕낯 ?대쫫 (?? ExchangeRate) [?곷Ц PascalCase]: ");
String interfaceId = getOrAsk(args, 1, scanner, "2. ?덇굅??API ?명꽣?섏씠??ID (?? EXCH_001): ");
String title = getOrAsk(args, 2, scanner, "3. Tool title: ");
String description = getOrAsk(args, 3, scanner, "4. Tool description for LLM: ");
String group = getOrAsk(args, 4, scanner, "5. Tool category: ");
@@ -543,19 +543,19 @@ public class ToolScaffolder {
String defaultAuthor = System.getProperty("user.name");
String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
String author = getOrAsk(args, 7, scanner, "8. 작성자(Enter 입력 시 '" + defaultAuthor + "'): ");
String author = getOrAsk(args, 7, scanner, "8. ?묒꽦??Enter ?낅젰 ??'" + defaultAuthor + "'): ");
if (author.trim().isEmpty()) author = defaultAuthor;
String createDate = getOrAsk(args, 8, scanner, "9. 작성일(Enter 입력 시 '" + defaultDate + "'): ");
String createDate = getOrAsk(args, 8, scanner, "9. ?묒꽦??Enter ?낅젰 ??'" + defaultDate + "'): ");
if (createDate.trim().isEmpty()) createDate = defaultDate;
String useSchemaResourceStr = getOrAsk(args, 9, scanner, "10. input/output JSON Schema 파일 자동 생성 여부 (y/N): ");
String useSchemaResourceStr = getOrAsk(args, 9, scanner, "10. input/output JSON Schema ?뚯씪 ?먮룞 ?앹꽦 ?щ? (y/N): ");
boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim());
String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/";
String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null;
String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null;
String defaultWorkspace = "c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dat-was-dasmt";
String workspace = getOrAsk(args, 10, scanner, "11. 대상 프로젝트 워크스페이스 경로 (default: " + defaultWorkspace + "): ");
String defaultWorkspace = "c:\\eGovFrameDev-4.3.1-64bit\\workspace-egov\\dat-was-datmt";
String workspace = getOrAsk(args, 10, scanner, "11. ?€???꾨줈?앺듃 ?뚰겕?ㅽ럹?댁뒪 寃쎈줈 (default: " + defaultWorkspace + "): ");
if (workspace.trim().isEmpty()) {
workspace = defaultWorkspace;
}
@@ -631,7 +631,7 @@ public class ToolScaffolder {
Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy"));
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
// Schema Resource 파일 경로(useSchemaResource=true일 때만 생성)
// Schema Resource ?뚯씪 寃쎈줈(useSchemaResource=true???뚮쭔 ?앹꽦)
boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty());
String schemaBaseName = toKebabCase(baseName);
String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json";
@@ -692,25 +692,25 @@ public class ToolScaffolder {
/**
* @package %s.dto
* @className %sRequest
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class %sRequest {
@McpToolParam(description = "수신자 전화번호", required = true)
@McpToolParam(description = "?섏떊???꾪솕踰덊샇", required = true)
-(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"})
private String phoneNumber;
@McpToolParam(description = "전송할 메시지 내용", required = true)
@McpToolParam(description = "?꾩넚??硫붿떆吏€ ?댁슜", required = true)
private String message;
}
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
@@ -720,7 +720,7 @@ public class ToolScaffolder {
.replaceAll("(?m)^\\s*@McpToolParam\\([^\\r\\n]*\\)\\R", "")
.replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "")
.replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;")
.replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;");
.replace("private String message;", "@Schema(example = \"?뚯뒪??硫붿떆吏€?낅땲??\")\n private String message;");
reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true);
writeUtf8(dtoDir.resolve(baseName + "Request.java"), reqContent);
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", inputFields);
@@ -735,14 +735,14 @@ public class ToolScaffolder {
/**
* @package %s.dto
* @className %sResponse
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -775,15 +775,15 @@ public class ToolScaffolder {
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");
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() : "?뺤쓽???낅젰 ??ぉ留??덉슜?섎ʼn ?낅Т 寃곌낵留?諛섑솚?⑸땲??")).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 + " 알려줘");
examples = List.of(title + " ?뺣낫瑜?蹂댁뿬以?, title + " ?뺤씤?댁쨾", "?꾩옱 " + title + " ?뚮젮以?);
}
sb.append(" exampleQueries = {")
.append(examples.stream().map(q -> "\"" + javaText(q) + "\"").collect(Collectors.joining(", ")))
@@ -794,7 +794,7 @@ public class ToolScaffolder {
List<String> tags = definitionOptions.tags();
if (tags == null || tags.isEmpty()) {
tags = List.of(group.toLowerCase(Locale.ROOT), isMutation ? "처리" : "조회");
tags = List.of(group.toLowerCase(Locale.ROOT), isMutation ? "泥섎━" : "議고쉶");
}
sb.append(" tags = {")
.append(tags.stream().map(t -> "\"" + javaText(t) + "\"").collect(Collectors.joining(", ")))
@@ -815,14 +815,14 @@ public class ToolScaffolder {
/**
* @package %s.usecase
* @className %sUseCase
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -866,14 +866,14 @@ public class ToolScaffolder {
/**
* @package %s.usecase.impl
* @className %sUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -887,9 +887,9 @@ public class ToolScaffolder {
@Override
public %sResponse execute(%sRequest req) {
log.info("[MCI Tool] {} 요청 수신.", "%s");
log.info("[MCI Tool] {} ?붿껌 ?섏떊.", "%s");
try {
// MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO)
// MapStruct瑜??댁슜???먮룞 留ㅽ븨 (AI DTO -> MCI DTO)
%s_I mciReq = converter.toLegacyRequest(req);
Transfer<Object> resTransfer = mci.callTo(
@@ -905,7 +905,7 @@ public class ToolScaffolder {
: "MCI call completed without a response body.");
return response;
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
log.error("[MCI Tool] ?곕룞 以??ㅻ쪟 諛쒖깮: {}", e.getMessage(), e);
%sResponse response = new %sResponse();
response.setResultCode("ERROR");
response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error");
@@ -965,14 +965,14 @@ public class ToolScaffolder {
/**
* @package %s.usecase.impl
* @className %sUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1029,26 +1029,26 @@ public class ToolScaffolder {
/**
* @package %s.%s.io
* @className %s_I
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@Data
public class %s_I {
/**
* EAI 시스템이 요구하는 수신자 번호 파라미터명
* EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐?
*/
private String phone;
/**
* EAI 시스템이 요구하는 메시지 내용 파라미터명
* EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐?
*/
private String content;
}
@@ -1065,14 +1065,14 @@ public class ToolScaffolder {
/**
* @package %s.%s.io
* @className %s_O
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1099,14 +1099,14 @@ public class ToolScaffolder {
/**
* @package %s.converter
* @className %sConverter
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1160,14 +1160,14 @@ public class ToolScaffolder {
/**
* @package %s.%s
* @className Mci%sClient
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1225,26 +1225,26 @@ public class ToolScaffolder {
/**
* @package %s.legacy
* @className %sLegacyRequest
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@Data
public class %sLegacyRequest {
/**
* EAI 시스템이 요구하는 수신자 번호 파라미터명
* EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐?
*/
private String phone;
/**
* EAI 시스템이 요구하는 메시지 내용 파라미터명
* EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐?
*/
private String content;
}
@@ -1260,14 +1260,14 @@ public class ToolScaffolder {
/**
* @package %s.legacy
* @className %sLegacyResponse
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1293,14 +1293,14 @@ public class ToolScaffolder {
/**
* @package %s.converter
* @className %sConverter
* @description AX HUB 시스템 처리 클래스
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author %s
* @create %s
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* %s %s 최초생성
* %s %s 理쒖큹?앹꽦
*
* </pre>
*/
@@ -1340,7 +1340,7 @@ public class ToolScaffolder {
log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n");
log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n");
}
// Schema Resource 파일 생성(useSchemaResource=true일 때)
// Schema Resource ?뚯씪 ?앹꽦(useSchemaResource=true????
if (useSchemaResource) {
Files.createDirectories(schemaDir);
String inputSchema = """
@@ -1350,7 +1350,7 @@ public class ToolScaffolder {
"properties": {
"TODO_FIELD": {
"type": "string",
"description": "TODO: 파라미터 설명을 입력하세요."
"description": "TODO: ?뚮씪誘명꽣 ?ㅻ챸???낅젰?섏꽭??"
}
},
"required": []
@@ -1363,12 +1363,12 @@ public class ToolScaffolder {
"properties": {
"status": {
"type": "string",
"description": "처리 결과 상태 (SUCCESS / FAILURE)",
"description": "泥섎━ 寃곌낵 ?곹깭 (SUCCESS / FAILURE)",
"enum": ["SUCCESS", "FAILURE"]
},
"message": {
"type": "string",
"description": "처리 결과 메시지"
"description": "泥섎━ 寃곌낵 硫붿떆吏€"
}
},
"required": ["status"]
@@ -1412,7 +1412,7 @@ public class ToolScaffolder {
log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n");
// Tool Definition YML is no longer generated. We use @GrowToolHint instead.
// log.append("[V17 Tool Definition] ...\n");
log.append("\n Tip: HTTP Tool WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n");
log.append("\n Tip: HTTP Tool?€ WireMock ?ㅽ뻾 ???앹꽦??mapping URL濡??몄텧???뺤씤?섏꽭??\n");
return log.toString();
}
@@ -1421,7 +1421,7 @@ public class ToolScaffolder {
String categoryKey, String interfaceId,
List<FieldDefinition> inputFields, boolean mutation) {
String safeDescription = description == null || description.isBlank()
? title + " 기능을 수행한다." : description;
? title + " 湲곕뒫???섑뻾?쒕떎." : description;
StringBuilder properties = new StringBuilder();
StringBuilder required = new StringBuilder();
Set<String> generatedNames = new LinkedHashSet<>();
@@ -1448,14 +1448,14 @@ public class ToolScaffolder {
category_key: %s
description:
function: %s
when_to_use: 사용자가 이 업무 기능의 실행 또는 조회를 명확히 요청한 경우 사용한다.
when_not_to_use: 입력값이 확인되지 않았거나 다른 업무 기능이 더 적합한 경우에는 사용하지 않는다.
io_limits: 정의된 입력 항목만 허용하며 응답 DTO에 정의된 업무 결과만 반환한다.
when_to_use: ?ъ슜?먭? ???낅Т 湲곕뒫???ㅽ뻾 ?먮뒗 議고쉶瑜?紐낇솗???붿껌??寃쎌슦 ?ъ슜?쒕떎.
when_not_to_use: ?낅젰媛믪씠 ?뺤씤?섏? ?딆븯嫄곕굹 ?ㅻⅨ ?낅Т 湲곕뒫?????곹빀??寃쎌슦?먮뒗 ?ъ슜?섏? ?딅뒗??
io_limits: ?뺤쓽???낅젰 ??ぉ留??덉슜?섎ʼn ?묐떟 DTO???뺤쓽???낅Т 寃곌낵留?諛섑솚?쒕떎.
display_description: %s
example_queries:
- %s 처리해줘
- %s 정보를 확인해줘
- %s 업무 결과를 알려줘
- %s 泥섎━?댁쨾
- %s ?뺣낫瑜??뺤씤?댁쨾
- %s ?낅Т 寃곌낵瑜??뚮젮以?
read_only: %s
destructive: %s
idempotent: %s
@@ -1477,16 +1477,16 @@ public class ToolScaffolder {
List<FieldDefinition> inputFields, boolean mutation,
ToolDefinitionOptions options) {
String function = option(options == null ? null : options.functionDescription(),
option(description, title + " 기능을 수행한다."));
option(description, title + " 湲곕뒫???섑뻾?쒕떎."));
String whenToUse = option(options == null ? null : options.whenToUse(),
"사용자가 해당 업무 기능의 실행 또는 조회를 명확히 요청한 경우 사용한다.");
"?ъ슜?먭? ?대떦 ?낅Т 湲곕뒫???ㅽ뻾 ?먮뒗 議고쉶瑜?紐낇솗???붿껌??寃쎌슦 ?ъ슜?쒕떎.");
String whenNotToUse = option(options == null ? null : options.whenNotToUse(),
"필수 입력값이 확인되지 않았거나 다른 업무 기능이 더 적합한 경우에는 사용하지 않는다.");
"?꾩닔 ?낅젰媛믪씠 ?뺤씤?섏? ?딆븯嫄곕굹 ?ㅻⅨ ?낅Т 湲곕뒫?????곹빀??寃쎌슦?먮뒗 ?ъ슜?섏? ?딅뒗??");
String ioLimits = option(options == null ? null : options.ioLimits(),
"정의된 입력 항목만 허용하며 응답 DTO에 정의된 업무 결과만 반환한다.");
"?뺤쓽???낅젰 ??ぉ留??덉슜?섎ʼn ?묐떟 DTO???뺤쓽???낅Т 寃곌낵留?諛섑솚?쒕떎.");
String displayDescription = option(options == null ? null : options.displayDescription(), title);
List<String> examples = normalizedList(options == null ? null : options.exampleQueries(), List.of(
title + " 처리해줘", title + " 정보를 확인해줘", title + " 업무 결과를 알려줘"));
title + " 泥섎━?댁쨾", title + " ?뺣낫瑜??뺤씤?댁쨾", title + " ?낅Т 寃곌낵瑜??뚮젮以?));
List<String> tags = normalizedList(options == null ? null : options.tags(),
List.of(categoryKey.toLowerCase(Locale.ROOT)));
String ownerOrg = option(options == null ? null : options.ownerOrg(), "MCP_TOOL");
@@ -2008,10 +2008,10 @@ public class ToolScaffolder {
private static String richDescription(FieldDefinition field) {
String desc = field.description() == null ? "" : field.description().trim();
if (field.pattern() != null && !field.pattern().isBlank()) {
desc += " (형식: " + field.pattern() + ")";
desc += " (?뺤떇: " + field.pattern() + ")";
}
if (field.examples() != null && !field.examples().isEmpty()) {
desc += " (예시: " + String.join(", ", field.examples()) + ")";
desc += " (?덉떆: " + String.join(", ", field.examples()) + ")";
}
return desc.trim();
}
@@ -2163,3 +2163,4 @@ public class ToolScaffolder {
return result.toString();
}
}

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
@@ -54,37 +54,37 @@
</nav>
</div>
<div class="flex items-center gap-3">
<button id="dtoDownloadButton" class="primary" type="button">DTO 다운로드</button>
<button id="dtoDownloadButton" class="primary" type="button">DTO ?ㅼ슫濡쒕뱶</button>
<span class="badge" id="manifestStatus" style="border:1px solid #3f3f46; border-radius:99px; padding:4px 8px; color:#a1a1aa; font-size:11px; font-family:ui-monospace,monospace;">Manifest loading</span>
<span class="text-[10px] uppercase tracking-widest px-2 py-1 rounded font-bold" style="background:rgba(59,130,246,0.1); color:#60a5fa; border:1px solid rgba(59,130,246,0.2);">v0.0.1</span>
</div>
</div>
</header>
<main>
<section class="intro"><h1>Schema 기반 Tool 테스트</h1><p>Tool을 선택하고 요청 JSON을 확인한 뒤 실행하세요. 검증한 요청은 브라우저에 저장되며, 저장된 케이스 전체를 한 번에 다시 실행할 수 있습니다.</p></section>
<section class="intro"><h1>Schema 湲곕컲 Tool ?뚯뒪??/h1><p>Tool???좏깮?섍퀬 ?붿껌 JSON???뺤씤?????ㅽ뻾?섏꽭?? 寃€利앺븳 ?붿껌?€ 釉뚮씪?곗????€?λ릺硫? ?€?λ맂 耳€?댁뒪 ?꾩껜瑜???踰덉뿉 ?ㅼ떆 ?ㅽ뻾?????덉뒿?덈떎.</p></section>
<div class="grid">
<aside class="stack">
<section class="card stack"><h2>1. Tool 선택</h2><div><label for="filter">검색</label><input id="filter" placeholder="이름, 설명으로 검색"></div><div><label for="toolSelect">Tool</label><select id="toolSelect" size="12"></select></div><div class="meta" id="toolMeta"></div><div class="buttons"><button id="sampleButton">Schema 샘플 채우기</button><button id="reloadButton">Manifest 새로고침</button></div></section>
<section class="card"><h2>저장된 테스트 케이스</h2><div class="buttons"><button class="primary" id="runAllButton">Run saved cases</button><button class="danger" id="clearCasesButton">전체 삭제</button></div><div id="caseList" class="case-list"></div><div id="runLog" class="run-log"></div></section>
<section class="card stack"><h2>1. Tool ?좏깮</h2><div><label for="filter">寃€??/label><input id="filter" placeholder="?대쫫, ?ㅻ챸?쇰줈 寃€??></div><div><label for="toolSelect">Tool</label><select id="toolSelect" size="12"></select></div><div class="meta" id="toolMeta"></div><div class="buttons"><button id="sampleButton">Schema ?섑뵆 梨꾩슦湲?/button><button id="reloadButton">Manifest ?덈줈怨좎묠</button></div></section>
<section class="card"><h2>?€?λ맂 ?뚯뒪??耳€?댁뒪</h2><div class="buttons"><button class="primary" id="runAllButton">Run saved cases</button><button class="danger" id="clearCasesButton">?꾩껜 ??젣</button></div><div id="caseList" class="case-list"></div><div id="runLog" class="run-log"></div></section>
</aside>
<section class="stack">
<section class="card"><h2>2. 요청 JSON</h2><div class="notice">필수값과 형식은 ToolinputSchema 기준입니다. MCI·외부 연동 Tool은 업무에 맞는 테스트 데이터를 입력한 후 저장하세요.</div><textarea id="arguments" spellcheck="false" aria-label="요청 JSON"></textarea><div class="buttons" style="margin-top:12px"><button id="saveButton">현재 요청 저장</button><button class="primary" id="executeButton">실행</button></div></section>
<section class="card"><h2>3. 실행 결과</h2><div class="meta"><span class="badge" id="httpStatus">대기</span><span class="badge" id="latency">-</span><span class="badge" id="traceId">X-Guid: -</span><span class="badge" id="requestId">X-Request-Id: -</span></div><pre class="result" id="result">Tool을 선택하고 실행하세요.</pre></section>
<section class="card"><h2>2. ?붿껌 JSON</h2><div class="notice">?꾩닔媛믨낵 ?뺤떇?€ Tool??inputSchema 湲곗??낅땲?? MCI쨌?몃? ?곕룞 Tool?€ ?낅Т??留욌뒗 ?뚯뒪???곗씠?곕? ?낅젰?????€?ν븯?몄슂.</div><textarea id="arguments" spellcheck="false" aria-label="?붿껌 JSON"></textarea><div class="buttons" style="margin-top:12px"><button id="saveButton">?꾩옱 ?붿껌 ?€??/button><button class="primary" id="executeButton">?ㅽ뻾</button></div></section>
<section class="card"><h2>3. ?ㅽ뻾 寃곌낵</h2><div class="meta"><span class="badge" id="httpStatus">?€湲?/span><span class="badge" id="latency">-</span><span class="badge" id="traceId">X-Guid: -</span><span class="badge" id="requestId">X-Request-Id: -</span></div><pre class="result" id="result">Tool???좏깮?섍퀬 ?ㅽ뻾?섏꽭??</pre></section>
</section>
</div>
<p class="footer-note">이 화면은 현재 Tool Pod<code>/tool-manifest</code> <code>/mcp/{toolName}</code>만 사용합니다. 저장된 케이스는 이 브라우저의 localStorage에만 보관됩니다.</p>
<p class="footer-note">???붾㈃?€ ?꾩옱 Tool Pod??<code>/tool-manifest</code>?€ <code>/mcp/{toolName}</code>留??ъ슜?⑸땲?? ?€?λ맂 耳€?댁뒪????釉뚮씪?곗???localStorage?먮쭔 蹂닿??⑸땲??</p>
</main>
<div id="dtoDownloadModal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="dtoDownloadTitle">
<div class="modal">
<div class="modal-header">
<h2 id="dtoDownloadTitle">DTO 다운로드</h2>
<button id="dtoModalCloseButton" class="modal-close" type="button" aria-label="닫기">&times;</button>
<h2 id="dtoDownloadTitle">DTO ?ㅼ슫濡쒕뱶</h2>
<button id="dtoModalCloseButton" class="modal-close" type="button" aria-label="?リ린">&times;</button>
</div>
<label for="dtoSelect">DTO 선택</label>
<label for="dtoSelect">DTO ?좏깮</label>
<select id="dtoSelect">
<option value="">DTO 목록 불러오는 중...</option>
<option value="">DTO 紐⑸줉 遺덈윭?ㅻ뒗 以?..</option>
</select>
<div class="modal-actions"><button id="dtoConfirmButton" class="primary" type="button">확인</button></div>
<div class="modal-actions"><button id="dtoConfirmButton" class="primary" type="button">?뺤씤</button></div>
</div>
</div>
<script>
@@ -95,7 +95,7 @@
const select = $('toolSelect'), args = $('arguments'), result = $('result');
async function loadDtoOptions() {
// 서버가 itrf/mci/**/io 패키지에서 찾은 DTO_I/_O 접미사 없이 이름 단위로 묶어 셀렉트박스를 구성한다.
// ?쒕쾭媛€ itrf/mci/**/io ?⑦궎吏€?먯꽌 李얠? DTO瑜?_I/_O ?묐????놁씠 ?대쫫 ?⑥쐞濡?臾띠뼱 ?€?됲듃諛뺤뒪瑜?援ъ꽦?쒕떎.
const dtoSelect = $('dtoSelect');
try {
const response = await fetch('/dto-download/options', { headers: { 'Cache-Control': 'no-cache' } });
@@ -108,17 +108,17 @@
option.textContent = dtoName;
dtoSelect.appendChild(option);
}
if (!dtoNames.length) dtoSelect.innerHTML = '<option value="">DTO 파일이 없습니다.</option>';
if (!dtoNames.length) dtoSelect.innerHTML = '<option value="">DTO ?뚯씪???놁뒿?덈떎.</option>';
} catch (error) {
dtoSelect.innerHTML = '<option value="">DTO 목록을 불러오지 못했습니다.</option>';
dtoSelect.innerHTML = '<option value="">DTO 紐⑸줉??遺덈윭?ㅼ? 紐삵뻽?듬땲??</option>';
}
}
function openDtoModal() { $('dtoDownloadModal').classList.remove('hidden'); loadDtoOptions(); $('dtoSelect').focus(); }
function closeDtoModal() { $('dtoDownloadModal').classList.add('hidden'); $('dtoDownloadButton').focus(); }
async function downloadSelectedDto() {
// 선택한 DTO 이름의 _I/_O 엑셀 생성 API를 순차 호출해 존재하는 파일을 각각 브라우저 다운로드로 저장한다.
// ?좏깮??DTO ?대쫫??_I/_O ?묒? ?앹꽦 API瑜??쒖감 ?몄텧??議댁옱?섎뒗 ?뚯씪??媛곴컖 釉뚮씪?곗? ?ㅼ슫濡쒕뱶濡??€?ν븳??
const baseName = $('dtoSelect').value;
if (!baseName) return alert('다운로드할 DTO를 선택해주세요.');
if (!baseName) return alert('?ㅼ슫濡쒕뱶??DTO瑜??좏깮?댁<?몄슂.');
const confirmButton = $('dtoConfirmButton');
confirmButton.disabled = true;
const errors = [];
@@ -131,13 +131,13 @@
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' }
});
if (response.status === 404) continue; // _I/_O 중 한쪽만 존재하는 DTO는 정상 케이스이므로 건너뛴다.
if (response.status === 404) continue; // _I/_O 以??쒖そ留?議댁옱?섎뒗 DTO???뺤긽 耳€?댁뒪?대?濡?嫄대꼫?대떎.
if (!response.ok) {
// 422GlowTrgmField 메타데이터가 없어 설계서 양식으로 변환할 수 없는 경우다.
// 422??GlowTrgmField 硫뷀??곗씠?곌? ?놁뼱 ?ㅺ퀎???묒떇?쇰줈 蹂€?섑븷 ???녿뒗 寃쎌슦??
const message = (await response.text()).trim();
throw new Error(response.status === 422
? `${dtoName}: dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요`
: message || `${dtoName} 다운로드에 실패했습니다. (HTTP ${response.status})`);
? `${dtoName}: dto???댁슜???묒??뚯씪 ?묒떇??留욎??딆뒿?덈떎 ?뚯씪???뺤씤?댁<?몄슂`
: message || `${dtoName} ?ㅼ슫濡쒕뱶???ㅽ뙣?덉뒿?덈떎. (HTTP ${response.status})`);
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
@@ -149,14 +149,14 @@
link.remove();
URL.revokeObjectURL(url);
successCount += 1;
// 브라우저가 연속 다운로드를 한 번에 차단하지 않도록 다음 요청 전에 짧은 간격을 둔다.
// 釉뚮씪?곗?媛€ ?곗냽 ?ㅼ슫濡쒕뱶瑜???踰덉뿉 李⑤떒?섏? ?딅룄濡??ㅼ쓬 ?붿껌 ?꾩뿉 吏㏃? 媛꾧꺽???붾떎.
await new Promise(resolve => setTimeout(resolve, 300));
} catch (error) {
errors.push(error.message);
}
}
if (successCount === 0) {
alert(errors.length ? errors.join('\n') : `${baseName} 파일을 찾을 수 없습니다.`);
alert(errors.length ? errors.join('\n') : `${baseName} ?뚯씪??李얠쓣 ???놁뒿?덈떎.`);
return;
}
if (errors.length) alert(errors.join('\n'));
@@ -179,7 +179,7 @@
let response = await fetch('/tool-manifest', { headers: { 'Cache-Control': 'no-cache' } });
if (!response.ok && response.status === 404) {
// Gateway 환경 감지 및 Fallback 처리
// Gateway ?섍꼍 媛먯? 諛?Fallback 泥섎━
response = await fetch('/mcp/api/v1/tools/list');
if (!response.ok) throw new Error(`Gateway HTTP ${response.status}`);
@@ -187,7 +187,7 @@
state.tools = rpcData.result?.tools || [];
isGatewayMode = true;
$('manifestStatus').textContent = `${state.tools.length} tools · Gateway Mode`;
$('manifestStatus').textContent = `${state.tools.length} tools Gateway Mode`;
$('manifestStatus').className = 'badge ok';
renderTools();
return;
@@ -196,13 +196,13 @@
if (!response.ok) throw new Error(`Manifest HTTP ${response.status}`);
const manifest = await response.json();
state.tools = (manifest.tools || []).filter(tool => tool._meta?.enabled !== false);
$('manifestStatus').textContent = `${state.tools.length} tools · rev ${manifest.revision || '-'}`;
$('manifestStatus').textContent = `${state.tools.length} tools rev ${manifest.revision || '-'}`;
$('manifestStatus').className = 'badge ok';
renderTools();
} catch (error) {
$('manifestStatus').textContent = `Load error: ${error.message}`;
$('manifestStatus').className = 'badge fail';
select.innerHTML = '<option>Tool 목록을 불러오지 못했습니다</option>';
select.innerHTML = '<option>Tool 紐⑸줉??遺덈윭?ㅼ? 紐삵뻽?듬땲??/option>';
}
}
@@ -212,7 +212,7 @@
select.innerHTML = '';
for (const tool of tools) {
const option = document.createElement('option'); option.value = tool.name;
option.textContent = `${tool.name} ${tool.title || tool.description || ''}`; select.appendChild(option);
option.textContent = `${tool.name} ??${tool.title || tool.description || ''}`; select.appendChild(option);
}
if (tools.length) choose(tools.find(tool => tool.name === state.selected?.name) || tools[0]);
else { state.selected = null; $('toolMeta').innerHTML = ''; args.value = ''; }
@@ -228,7 +228,7 @@
hints.destructiveHint ? '<span class="badge fail">destructive</span>' : ''
].join('');
args.value = JSON.stringify(exampleForSchema(tool.inputSchema || tool.parametersSchema || {}), null, 2);
result.textContent = tool.description || '설명이 없습니다.';
result.textContent = tool.description || '?ㅻ챸???놁뒿?덈떎.';
resetResult();
}
@@ -252,12 +252,12 @@
return 'test-value';
}
function parseArguments() { try { const value = JSON.parse(args.value || '{}'); if (value && typeof value === 'object' && !Array.isArray(value)) return value; throw new Error('요청 JSON은 객체여야 합니다.'); } catch (error) { throw new Error(`요청 JSON 오류: ${error.message}`); } }
function parseArguments() { try { const value = JSON.parse(args.value || '{}'); if (value && typeof value === 'object' && !Array.isArray(value)) return value; throw new Error('?붿껌 JSON?€ 媛앹껜?ъ빞 ?⑸땲??'); } catch (error) { throw new Error(`?붿껌 JSON ?ㅻ쪟: ${error.message}`); } }
function endpointFor(tool) {
const fallback = `/mcp/${encodeURIComponent(tool.name)}`;
try { const endpoint = new URL(tool.endpoint || fallback, window.location.origin); return endpoint.origin === window.location.origin ? `${endpoint.pathname}${endpoint.search}` : fallback; } catch (_) { return fallback; }
}
function resetResult() { $('httpStatus').textContent = '대기'; $('httpStatus').className = 'badge'; $('latency').textContent = '-'; $('traceId').textContent = 'X-Guid: -'; $('requestId').textContent = 'X-Request-Id: -'; }
function resetResult() { $('httpStatus').textContent = '?€湲?; $('httpStatus').className = 'badge'; $('latency').textContent = '-'; $('traceId').textContent = 'X-Guid: -'; $('requestId').textContent = 'X-Request-Id: -'; }
function toolHeaders(guid, request, session) {
return {
'Content-Type': 'application/json',
@@ -266,7 +266,7 @@
'X-Request-Id': request,
'X-Request-Time': new Date().toISOString(),
'X-Vrtl-Praf-No': 'V100001',
'X-App-Code': 'DASMT',
'X-App-Code': 'DATMT',
'X-Project-Code': 'AXHUB',
'X-User-Ip': '127.0.0.1',
'X-Caller-Ip': '127.0.0.1',
@@ -278,9 +278,9 @@
}
async function execute(tool = state.selected, body = null) {
if (!tool) throw new Error('실행할 Tool을 선택하세요.');
if (!tool) throw new Error('?ㅽ뻾??Tool???좏깮?섏꽭??');
const payload = body || parseArguments(); const guid = requestId(), request = requestId(), session = requestId(), started = performance.now();
$('executeButton').disabled = true; $('httpStatus').textContent = '실행 중'; $('httpStatus').className = 'badge';
$('executeButton').disabled = true; $('httpStatus').textContent = '?ㅽ뻾 ?; $('httpStatus').className = 'badge';
try {
let response;
if (isGatewayMode) {
@@ -326,31 +326,31 @@
function saveCase() {
try {
const payload = parseArguments(); const name = window.prompt('테스트 케이스 이름', state.selected.name);
const payload = parseArguments(); const name = window.prompt('?뚯뒪??耳€?댁뒪 ?대쫫', state.selected.name);
if (!name) return;
state.cases.push({ id: requestId(), name, toolName: state.selected.name, arguments: payload, savedAt: new Date().toISOString() }); persistCases();
} catch (error) { alert(error.message); }
}
function renderCases() {
const list = $('caseList'); list.innerHTML = '';
if (!state.cases.length) { list.innerHTML = '<p style="margin:0;color:#a1a1aa;font-size:13px">저장된 케이스가 없습니다.</p>'; return; }
if (!state.cases.length) { list.innerHTML = '<p style="margin:0;color:#a1a1aa;font-size:13px">?€?λ맂 耳€?댁뒪媛€ ?놁뒿?덈떎.</p>'; return; }
for (const item of state.cases) {
const row = document.createElement('div'); row.className = 'case-row';
row.innerHTML = `<main><strong>${escapeHtml(item.name)}</strong><small>${escapeHtml(item.toolName)}</small></main><button data-run="${item.id}">실행</button><button class="danger" data-delete="${item.id}">삭제</button>`;
row.innerHTML = `<main><strong>${escapeHtml(item.name)}</strong><small>${escapeHtml(item.toolName)}</small></main><button data-run="${item.id}">?ㅽ뻾</button><button class="danger" data-delete="${item.id}">??젣</button>`;
list.appendChild(row);
}
}
async function runSavedCases() {
if (!state.cases.length) return alert('저장된 테스트 케이스가 없습니다.');
$('runAllButton').disabled = true; $('runLog').textContent = `${state.cases.length}건 실행 시작\n`;
if (!state.cases.length) return alert('?€?λ맂 ?뚯뒪??耳€?댁뒪媛€ ?놁뒿?덈떎.');
$('runAllButton').disabled = true; $('runLog').textContent = `珥?${state.cases.length}嫄??ㅽ뻾 ?쒖옉\n`;
let success = 0;
for (const item of state.cases) {
const tool = state.tools.find(candidate => candidate.name === item.toolName);
if (!tool) { $('runLog').textContent += `FAIL ${item.name} · Tool 없음\n`; continue; }
try { const execution = await execute(tool, item.arguments); success += execution.ok ? 1 : 0; $('runLog').textContent += `${execution.ok ? 'PASS' : 'FAIL'} ${item.name} · HTTP ${execution.status} · ${execution.elapsed}ms\n`; }
catch (error) { $('runLog').textContent += `FAIL ${item.name} · ${error.message}\n`; }
if (!tool) { $('runLog').textContent += `FAIL ${item.name} Tool ?놁쓬\n`; continue; }
try { const execution = await execute(tool, item.arguments); success += execution.ok ? 1 : 0; $('runLog').textContent += `${execution.ok ? 'PASS' : 'FAIL'} ${item.name} HTTP ${execution.status} ${execution.elapsed}ms\n`; }
catch (error) { $('runLog').textContent += `FAIL ${item.name} ${error.message}\n`; }
}
$('runLog').textContent += `완료: ${success}/${state.cases.length} 성공`; $('runAllButton').disabled = false;
$('runLog').textContent += `?꾨즺: ${success}/${state.cases.length} ?깃났`; $('runAllButton').disabled = false;
}
select.addEventListener('change', () => choose(state.tools.find(tool => tool.name === select.value)));
@@ -358,9 +358,9 @@
$('sampleButton').addEventListener('click', () => { if (state.selected) args.value = JSON.stringify(exampleForSchema(state.selected.inputSchema || state.selected.parametersSchema || {}), null, 2); });
$('reloadButton').addEventListener('click', loadManifest);
$('saveButton').addEventListener('click', saveCase);
$('executeButton').addEventListener('click', async () => { try { await execute(); } catch (error) { $('httpStatus').textContent = '입력 오류'; $('httpStatus').className = 'badge fail'; result.textContent = error.message; } });
$('executeButton').addEventListener('click', async () => { try { await execute(); } catch (error) { $('httpStatus').textContent = '?낅젰 ?ㅻ쪟'; $('httpStatus').className = 'badge fail'; result.textContent = error.message; } });
$('runAllButton').addEventListener('click', runSavedCases);
$('clearCasesButton').addEventListener('click', () => { if (confirm('저장된 테스트 케이스를 모두 삭제할까요?')) { state.cases = []; persistCases(); } });
$('clearCasesButton').addEventListener('click', () => { if (confirm('?€?λ맂 ?뚯뒪??耳€?댁뒪瑜?紐⑤몢 ??젣?좉퉴??')) { state.cases = []; persistCases(); } });
$('dtoDownloadButton').addEventListener('click', openDtoModal);
$('dtoModalCloseButton').addEventListener('click', closeDtoModal);
$('dtoConfirmButton').addEventListener('click', downloadSelectedDto);
@@ -372,3 +372,4 @@
</script>
</body>
</html>