fix: dap-was-dapmt 바라보도록 수정

This commit is contained in:
juheelee
2026-08-20 14:58:50 +09:00
parent 4986f9801c
commit 52ae31d853
30 changed files with 495 additions and 359 deletions

Binary file not shown.

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
<component name="dataSourceStorageLocal" created-in="IU-252.28238.7">
<data-source name="MCP_Redis" uuid="e04b0a95-55ed-4687-8007-98dd44e0ab05">
<database-info product="Redis Standalone" version="8.10.0" jdbc-version="4.2" driver-name="Redis JDBC Driver" driver-version="1.5" dbms="REDIS" exact-version="8.10.0" exact-driver-version="1.5" />
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="MCP_Redis">
<database-model serializer="dbm" dbms="REDIS" family-id="REDIS" format-version="4.55">
<root id="1">
<ServerVersion>8.10.0</ServerVersion>
</root>
<schema id="2" parent="1" name="0">
<Current>1</Current>
</schema>
<schema id="3" parent="1" name="1"/>
<schema id="4" parent="1" name="10"/>
<schema id="5" parent="1" name="11"/>
<schema id="6" parent="1" name="12"/>
<schema id="7" parent="1" name="13"/>
<schema id="8" parent="1" name="14"/>
<schema id="9" parent="1" name="15"/>
<schema id="10" parent="1" name="2"/>
<schema id="11" parent="1" name="3"/>
<schema id="12" parent="1" name="4"/>
<schema id="13" parent="1" name="5"/>
<schema id="14" parent="1" name="6"/>
<schema id="15" parent="1" name="7"/>
<schema id="16" parent="1" name="8"/>
<schema id="17" parent="1" name="9"/>
</database-model>
</dataSource>

7
.idea/modules.xml generated
View File

@@ -5,8 +5,15 @@
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-core/dap-admin.dap-tool-core.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-core/dap-admin.dap-tool-core.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-oth/dap-admin.dap-tool-oth.test.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-report/dap-admin.dap-tool-report.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-report/dap-admin.dap-tool-report.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dap-tool-sms/dap-admin.dap-tool-sms.test.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-cus/dap-admin.dat-was-cus.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-cus/dap-admin.dat-was-cus.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-pro/dap-admin.dat-was-pro.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-pro/dap-admin.dat-was-pro.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-sal/dap-admin.dat-was-sal.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-sal/dap-admin.dat-was-sal.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/dat-was-sys/dap-admin.dat-was-sys.main.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-sys/dap-admin.dat-was-sys.main.iml" />
</modules>
</component>
</project>

File diff suppressed because one or more lines are too long

View File

@@ -31,7 +31,7 @@
.subtitle { margin: 0; color: #a1a1aa; font-size: 14px; line-height: 1.65; }
.count-badge { padding: 7px 11px; border: 1px solid #27272a; border-radius: 8px; background: #18181b; color: #a1a1aa; font-family: "Geist Mono", Consolas, monospace; font-size: 12px; white-space: nowrap; }
.panel { overflow: hidden; border: 1px solid #27272a; border-radius: 12px; background: #111113; box-shadow: 0 18px 50px rgba(0,0,0,.22); }
.toolbar { display: grid; grid-template-columns: minmax(260px,1fr) 190px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
.toolbar { display: grid; grid-template-columns: 210px minmax(240px,1fr) 170px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
input, select, button { min-height: 40px; border: 1px solid #3f3f46; border-radius: 7px; background: #18181b; color: #e4e4e7; padding: 8px 12px; font: inherit; font-size: 13px; outline: none; }
input::placeholder { color: #71717a; }
input:focus, select:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
@@ -95,6 +95,10 @@
<section class="panel">
<div class="toolbar">
<select id="sourceType" aria-label="Tool 소스 프로젝트">
<option value="DAP_WAS_DAPMT">dap-was-dapmt 소스</option>
<option value="DAP_ADMIN">dap-admin 소스</option>
</select>
<input id="keyword" type="search" autocomplete="off" placeholder="툴명, 제목 또는 설명 검색">
<select id="category"><option value="">전체 카테고리</option></select>
<button id="selectAll" type="button">전체 선택</button>
@@ -114,7 +118,7 @@
</main>
<script>
const state = { tools: [], selected: new Set() };
const state = { tools: [], selected: new Set(), sourceType: 'DAP_WAS_DAPMT' };
const el = id => document.getElementById(id);
const gateway = location.origin;
@@ -155,7 +159,11 @@
async function load() {
try {
const response = await fetch('/report/api/report-tools');
state.sourceType = el('sourceType').value;
state.selected.clear();
state.tools = [];
el('tools').innerHTML = '<tr><td class="loading-row" colspan="6">선택한 프로젝트의 Tool 소스를 분석하고 있습니다.</td></tr>';
const response = await fetch(`/report/api/report-tools?sourceType=${encodeURIComponent(state.sourceType)}`);
const body = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(body?.error || body?.message || `툴 목록 조회에 실패했습니다. (${response.status})`);
@@ -178,6 +186,10 @@
}
el('keyword').addEventListener('input', render);
el('sourceType').addEventListener('change', () => {
el('category').innerHTML = '<option value="">전체 카테고리</option>';
load();
});
el('category').addEventListener('change', render);
el('selectAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.add(tool.name)); render(); });
el('clearAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.delete(tool.name)); render(); });
@@ -185,7 +197,8 @@
el('download').disabled = true; updateStatus('소스를 분석하고 Excel을 생성하는 중입니다.');
try {
const response = await fetch('/report/api/tool-reports/excel', {
method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({toolNames: [...state.selected]})
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({toolNames: [...state.selected], sourceType: state.sourceType})
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));

View File

@@ -3,6 +3,7 @@ package io.shinhanlife.dat.report.application;
import io.shinhanlife.dat.report.excel.ToolReportExcelWriter;
import io.shinhanlife.dat.report.model.ToolReportModel;
import io.shinhanlife.dat.report.model.ToolSummary;
import io.shinhanlife.dat.report.model.ToolSourceType;
import io.shinhanlife.dat.report.source.ToolDetailAnalyzer;
import io.shinhanlife.dat.report.source.ToolSourceDiscovery;
import java.util.LinkedHashMap;
@@ -26,13 +27,18 @@ public class ToolReportService {
this.excelWriter = excelWriter;
}
public byte[] createExcel(List<String> selectedNames) {
public List<ToolSummary> findTools(String sourceType) {
return discovery.discover(ToolSourceType.from(sourceType));
}
public byte[] createExcel(List<String> selectedNames, String sourceTypeValue) {
if (selectedNames == null || selectedNames.isEmpty()) {
throw new IllegalArgumentException("보고서에 포함할 툴을 하나 이상 선택해야 합니다.");
}
ToolSourceType sourceType = ToolSourceType.from(sourceTypeValue);
Map<String, ToolSummary> sourceTools = new LinkedHashMap<>();
discovery.discover().forEach(tool -> sourceTools.put(tool.name(), tool));
discovery.discover(sourceType).forEach(tool -> sourceTools.put(tool.name(), tool));
List<String> missingSources = selectedNames.stream()
.filter(name -> !sourceTools.containsKey(name)).distinct().toList();
if (!missingSources.isEmpty()) {
@@ -41,7 +47,7 @@ public class ToolReportService {
List<ToolReportModel> reports = selectedNames.stream().distinct()
.map(sourceTools::get)
.map(analyzer::analyze)
.map(tool -> analyzer.analyze(tool, sourceType))
.toList();
return excelWriter.write(reports);
}

View File

@@ -4,5 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/** 보고서 소스 위치와 출력 정책 설정. */
@ConfigurationProperties(prefix = "report")
public record ReportProperties(String sourceRoot, String outputFilenamePrefix) {
public record ReportProperties(String dapWasDapmtSourceRoot,
String dapAdminSourceRoot,
String outputFilenamePrefix) {
}

View File

@@ -3,5 +3,5 @@ package io.shinhanlife.dat.report.model;
import java.util.List;
/** 사용자가 선택한 툴 보고서 생성 요청. */
public record ToolReportRequest(List<String> toolNames) {
public record ToolReportRequest(List<String> toolNames, String sourceType) {
}

View File

@@ -3,6 +3,8 @@ package io.shinhanlife.dat.report.presentation;
import io.shinhanlife.dat.report.application.ToolReportService;
import io.shinhanlife.dat.report.config.ReportProperties;
import io.shinhanlife.dat.report.model.ToolReportRequest;
import io.shinhanlife.dat.report.model.ToolSummary;
import java.util.List;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -12,10 +14,12 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
/** 툴 선택 목록과 Excel 다운로드 API. */
@RestController
@@ -33,7 +37,7 @@ public class ToolReportController {
@PostMapping(value = "/tool-reports/excel",
produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public ResponseEntity<byte[]> excel(@RequestBody ToolReportRequest request) {
byte[] content = service.createExcel(request.toolNames());
byte[] content = service.createExcel(request.toolNames(), request.sourceType());
String prefix = properties.outputFilenamePrefix() == null || properties.outputFilenamePrefix().isBlank()
? "tool-report" : properties.outputFilenamePrefix();
String filename = prefix + "-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")) + ".xlsx";
@@ -47,6 +51,11 @@ public class ToolReportController {
.body(content);
}
@GetMapping("/report-tools")
public List<ToolSummary> tools(@RequestParam(required = false) String sourceType) {
return service.findTools(sourceType);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> badRequest(IllegalArgumentException exception) {
return ResponseEntity.badRequest().body(Map.of("error", exception.getMessage()));

View File

@@ -2,6 +2,7 @@ package io.shinhanlife.dat.report.source;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MemberValuePair;
@@ -9,6 +10,7 @@ import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import java.util.Optional;
import java.util.stream.Collectors;
/** JavaParser AST에서 어노테이션 값을 안전하게 읽는 도우미. */
final class JavaAnnotationReader {
@@ -49,6 +51,18 @@ final class JavaAnnotationReader {
.orElse(fallback);
}
static String strings(AnnotationExpr annotation, String key) {
return value(annotation, key)
.filter(ArrayInitializerExpr.class::isInstance)
.map(ArrayInitializerExpr.class::cast)
.map(array -> array.getValues().stream()
.filter(StringLiteralExpr.class::isInstance)
.map(StringLiteralExpr.class::cast)
.map(StringLiteralExpr::asString)
.collect(Collectors.joining(" | ")))
.orElse("");
}
static Optional<AnnotationExpr> nested(AnnotationExpr annotation, String key) {
return value(annotation, key).filter(AnnotationExpr.class::isInstance).map(AnnotationExpr.class::cast);
}

View File

@@ -1,8 +1,5 @@
package io.shinhanlife.dat.report.source;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.FieldDeclaration;
@@ -10,135 +7,56 @@ import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import io.shinhanlife.dat.report.model.FieldDefinition;
import io.shinhanlife.dat.report.model.ToolReportModel;
import io.shinhanlife.dat.report.model.ToolSourceType;
import io.shinhanlife.dat.report.model.ToolSummary;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
/** 선택된 툴의 DTO, JSON Schema와 원천 전문 필드를 수집한다. */
/** 선택한 프로젝트의 DTO와 전문 Java 어노테이션에서 Report 파라미터를 수집한다. */
@Component
public class ToolDetailAnalyzer {
private final ToolSourceDiscovery discovery;
private final ObjectMapper objectMapper;
private final ObjectMapper yamlMapper;
public ToolDetailAnalyzer(ToolSourceDiscovery discovery) {
this.discovery = discovery;
this.objectMapper = new ObjectMapper();
this.yamlMapper = new ObjectMapper(new YAMLFactory());
}
public ToolReportModel analyze(ToolSummary tool) {
public ToolReportModel analyze(ToolSummary tool, ToolSourceType sourceType) {
List<FieldDefinition> fields = new ArrayList<>();
List<String> diagnostics = new ArrayList<>();
Map<String, Path> javaFiles = indexJavaFiles();
Path sourceRoot = discovery.sourceRoot(sourceType);
Map<String, Path> javaFiles = indexJavaFiles(sourceRoot);
collectSchema(tool, tool.inputSchemaResource(), "INPUT", fields, diagnostics);
collectSchema(tool, tool.outputSchemaResource(), "OUTPUT", fields, diagnostics);
boolean definitionInput = collectDefinitionSchema(tool, "parameters_schema", "INPUT", fields, diagnostics);
boolean definitionOutput = collectDefinitionSchema(tool, "output_schema", "OUTPUT", fields, diagnostics);
if (tool.inputSchemaResource().isBlank() && !definitionInput) {
collectJavaType(tool, javaFiles.get(tool.requestType()), "DTO", "INPUT", fields, diagnostics);
}
if (tool.outputSchemaResource().isBlank() && !definitionOutput) {
collectJavaType(tool, javaFiles.get(tool.responseType()), "DTO", "OUTPUT", fields, diagnostics);
}
collectJavaType(sourceRoot, tool, javaFiles.get(tool.requestType()), "DTO", "INPUT", fields, diagnostics);
collectJavaType(sourceRoot, tool, javaFiles.get(tool.responseType()), "DTO", "OUTPUT", fields, diagnostics);
if (!tool.mappingId().isBlank()) {
collectJavaType(tool, javaFiles.get(tool.mappingId() + "_I"), "TELEGRAM", "INPUT", fields, diagnostics);
collectJavaType(tool, javaFiles.get(tool.mappingId() + "_O"), "TELEGRAM", "OUTPUT", fields, diagnostics);
collectJavaType(sourceRoot, tool, javaFiles.get(tool.mappingId() + "_I"),
"TELEGRAM", "INPUT", fields, diagnostics);
collectJavaType(sourceRoot, tool, javaFiles.get(tool.mappingId() + "_O"),
"TELEGRAM", "OUTPUT", fields, diagnostics);
}
return new ToolReportModel(tool, List.copyOf(fields), List.copyOf(diagnostics));
}
private boolean collectDefinitionSchema(ToolSummary tool, String schemaKey, String direction,
List<FieldDefinition> fields, List<String> diagnostics) {
if (tool.definitionFile() == null || tool.definitionFile().isBlank()) return false;
Path path = discovery.sourceRoot().resolve(tool.definitionFile()).normalize();
try {
JsonNode schema = yamlMapper.readTree(path.toFile()).path(schemaKey);
if (schema.isMissingNode() || schema.isNull() || schema.isEmpty()) return false;
collectSchemaFields(tool, schema, "TOOL_DEFINITION", direction, "", "", relative(path), fields);
return true;
} catch (Exception exception) {
diagnostics.add("Tool definition schema 분석 실패: " + relative(path) + " (" + schemaKey + ")");
return false;
}
}
private Map<String, Path> indexJavaFiles() {
private Map<String, Path> indexJavaFiles(Path sourceRoot) {
Map<String, Path> result = new LinkedHashMap<>();
try (var paths = Files.walk(discovery.sourceRoot())) {
try (var paths = Files.walk(sourceRoot)) {
paths.filter(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java"))
.forEach(path -> result.putIfAbsent(path.getFileName().toString().replace(".java", ""), path));
} catch (IOException exception) {
throw new IllegalStateException("Failed to index Java sources", exception);
throw new IllegalStateException("Failed to index Java sources: " + sourceRoot, exception);
}
return result;
}
private void collectSchema(ToolSummary tool, String resource, String direction,
List<FieldDefinition> fields, List<String> diagnostics) {
if (resource == null || resource.isBlank()) return;
String relative = resource.replaceFirst("^classpath:", "").replaceFirst("^/", "");
List<Path> matches = new ArrayList<>();
try (var paths = Files.walk(discovery.sourceRoot())) {
paths.filter(path -> Files.isRegularFile(path))
.filter(path -> path.toString().replace('\\', '/').endsWith("/src/main/resources/" + relative))
.forEach(matches::add);
} catch (IOException exception) {
diagnostics.add("Schema 검색 실패: " + resource);
return;
}
if (matches.isEmpty()) {
diagnostics.add("Schema를 찾을 수 없음: " + resource);
return;
}
Path path = matches.get(0);
try {
JsonNode root = objectMapper.readTree(path.toFile());
collectSchemaFields(tool, root, "JSON_SCHEMA", direction, "", "", relative(path), fields);
} catch (Exception exception) {
diagnostics.add("Schema 분석 실패: " + relative(path));
}
}
private void collectSchemaFields(ToolSummary tool, JsonNode schema, String sourceKind, String direction,
String ownerPath, String fieldPrefix, String sourceFile,
List<FieldDefinition> fields) {
List<String> required = new ArrayList<>();
schema.path("required").forEach(node -> required.add(node.asText()));
Iterator<Map.Entry<String, JsonNode>> iterator = schema.path("properties").fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
JsonNode definition = entry.getValue();
String fieldName = fieldPrefix.isBlank() ? entry.getKey() : fieldPrefix + "." + entry.getKey();
String type = schemaType(definition);
fields.add(new FieldDefinition(tool.name(), sourceKind, direction, ownerPath, fieldName, type,
required.contains(entry.getKey()), definition.path("description").asText(""),
constraints(definition), sourceFile));
JsonNode nested = definition.path("properties").isObject() ? definition : definition.path("items");
if (nested.path("properties").isObject()) {
collectSchemaFields(tool, nested, sourceKind, direction, fieldName, fieldName, sourceFile, fields);
}
}
}
private String schemaType(JsonNode definition) {
String type = definition.path("type").asText("object");
if ("array".equals(type)) {
return "array<" + definition.path("items").path("type").asText("object") + ">";
}
return type;
}
private void collectJavaType(ToolSummary tool, Path path, String sourceKind, String direction,
private void collectJavaType(Path sourceRoot, ToolSummary tool, Path path, String sourceKind, String direction,
List<FieldDefinition> fields, List<String> diagnostics) {
if (path == null) {
diagnostics.add(sourceKind + " " + direction + " 타입을 찾을 수 없음");
@@ -147,18 +65,18 @@ public class ToolDetailAnalyzer {
try {
CompilationUnit unit = StaticJavaParser.parse(path);
for (TypeDeclaration<?> type : unit.getTypes()) {
collectFields(tool, path, type, sourceKind, direction, fields);
collectFields(sourceRoot, tool, path, type, sourceKind, direction, fields);
type.getMembers().stream().filter(TypeDeclaration.class::isInstance)
.map(TypeDeclaration.class::cast)
.forEach(nested -> collectFields(tool, path, nested, sourceKind, direction, fields));
.forEach(nested -> collectFields(sourceRoot, tool, path, nested, sourceKind, direction, fields));
}
} catch (Exception exception) {
diagnostics.add(sourceKind + " 분석 실패: " + relative(path));
diagnostics.add(sourceKind + " 분석 실패: " + relative(sourceRoot, path));
}
}
private void collectFields(ToolSummary tool, Path path, TypeDeclaration<?> owner, String sourceKind,
String direction, List<FieldDefinition> fields) {
private void collectFields(Path sourceRoot, ToolSummary tool, Path path, TypeDeclaration<?> owner,
String sourceKind, String direction, List<FieldDefinition> fields) {
for (FieldDeclaration declaration : owner.getFields()) {
AnnotationExpr param = JavaAnnotationReader.find(declaration.getAnnotations(), "McpToolParam").orElse(null);
AnnotationExpr schema = JavaAnnotationReader.find(declaration.getAnnotations(), "Schema").orElse(null);
@@ -168,7 +86,7 @@ public class ToolDetailAnalyzer {
variable.getTypeAsString(),
param == null ? null : JavaAnnotationReader.bool(param, "required", false),
param == null ? "" : JavaAnnotationReader.string(param, "description", ""),
annotationConstraints(schema, telegram), relative(path))));
annotationConstraints(schema, telegram), relative(sourceRoot, path))));
}
}
@@ -179,16 +97,7 @@ public class ToolDetailAnalyzer {
return String.join("; ", values);
}
private String constraints(JsonNode node) {
List<String> values = new ArrayList<>();
for (String key : List.of("format", "pattern", "minimum", "maximum", "minLength", "maxLength", "example")) {
if (node.has(key)) values.add(key + "=" + node.get(key).asText());
}
if (node.has("enum")) values.add("enum=" + node.get("enum"));
return String.join("; ", values);
}
private String relative(Path path) {
return discovery.sourceRoot().relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
private String relative(Path sourceRoot, Path path) {
return sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
}
}

View File

@@ -4,11 +4,9 @@ import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.shinhanlife.dat.report.config.ReportProperties;
import io.shinhanlife.dat.report.model.ToolSummary;
import io.shinhanlife.dat.report.model.ToolSourceType;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -17,120 +15,50 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.EnumMap;
import org.springframework.stereotype.Component;
/** 프로젝트의 @McpTool 선언을 읽기 전용으로 탐색한다. */
@Component
public class ToolSourceDiscovery {
private final Path sourceRoot;
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
private final Map<ToolSourceType, Path> sourceRoots = new EnumMap<>(ToolSourceType.class);
public ToolSourceDiscovery(ReportProperties properties) {
String configuredRoot = properties.sourceRoot();
if (configuredRoot == null || configuredRoot.isBlank()) {
throw new IllegalArgumentException("report.source-root must not be blank");
}
this.sourceRoot = resolveProjectRoot(Path.of(configuredRoot).toAbsolutePath().normalize());
sourceRoots.put(ToolSourceType.DAP_WAS_DAPMT,
resolveConfiguredRoot(properties.dapWasDapmtSourceRoot(), "report.dap-was-dapmt-source-root"));
sourceRoots.put(ToolSourceType.DAP_ADMIN,
resolveConfiguredRoot(properties.dapAdminSourceRoot(), "report.dap-admin-source-root"));
}
public Path sourceRoot() {
return sourceRoot;
public Path sourceRoot(ToolSourceType sourceType) {
return sourceRoots.get(sourceType);
}
public List<ToolSummary> discover() {
public List<ToolSummary> discover(ToolSourceType sourceType) {
Path sourceRoot = sourceRoot(sourceType);
if (!Files.isDirectory(sourceRoot)) {
throw new IllegalStateException("Report source root does not exist: " + sourceRoot);
}
List<ToolSummary> tools = new ArrayList<>();
try (var paths = Files.walk(sourceRoot)) {
paths.filter(this::isUseCaseSource).forEach(path -> parse(path, tools));
paths.filter(this::isUseCaseSource).forEach(path -> parse(sourceRoot, path, tools));
} catch (IOException exception) {
throw new IllegalStateException("Failed to scan tool sources: " + sourceRoot, exception);
}
Map<String, ToolSummary> discovered = tools.stream()
return tools.stream()
.sorted(Comparator.comparing(ToolSummary::sourceFile))
.collect(Collectors.toMap(ToolSummary::name, Function.identity(),
(first, duplicate) -> first, LinkedHashMap::new));
discoverDefinitions().forEach((name, definition) ->
discovered.merge(name, definition, this::mergeDefinition));
return discovered.values().stream()
.collect(java.util.stream.Collectors.toMap(ToolSummary::name, java.util.function.Function.identity(),
(first, duplicate) -> first, LinkedHashMap::new)).values().stream()
.sorted(Comparator.comparing(ToolSummary::name))
.toList();
}
private Map<String, ToolSummary> discoverDefinitions() {
Map<String, ToolSummary> definitions = new LinkedHashMap<>();
try (var paths = Files.walk(sourceRoot)) {
paths.filter(this::isToolDefinition).sorted().forEach(path -> {
try {
JsonNode root = yamlMapper.readTree(path.toFile());
String name = text(root, "name");
if (name.isBlank()) return;
JsonNode description = root.path("description");
String function = text(description, "function");
definitions.putIfAbsent(name, new ToolSummary(
name, text(root, "display_name"), function, text(root, "category_key"),
text(root, "legacy_interface_id"), true, false,
bool(root, "read_only"), bool(root, "destructive"), bool(root, "idempotent"), false,
"", "", "", "", "", "", text(root, "version"), function,
text(description, "when_to_use"), text(description, "when_not_to_use"),
text(description, "io_limits"), text(root, "display_description"),
joined(root.path("example_queries")), joined(root.path("tags")),
joined(root.path("required_env_keys")), text(root, "owner_org"), relative(path)));
} catch (IOException exception) {
throw new IllegalStateException("Failed to parse tool definition: " + path, exception);
private Path resolveConfiguredRoot(String configuredRoot, String propertyName) {
if (configuredRoot == null || configuredRoot.isBlank()) {
throw new IllegalArgumentException(propertyName + " must not be blank");
}
});
} catch (IOException exception) {
throw new IllegalStateException("Failed to scan tool definitions: " + sourceRoot, exception);
}
return definitions;
}
private ToolSummary mergeDefinition(ToolSummary source, ToolSummary definition) {
return new ToolSummary(source.name(), prefer(definition.title(), source.title()),
prefer(definition.description(), source.description()),
prefer(definition.categoryKey(), source.categoryKey()),
prefer(definition.mappingId(), source.mappingId()), source.register(), source.requiresApproval(),
definition.readOnlyHint(), definition.destructiveHint(), definition.idempotentHint(),
source.openWorldHint(), source.requestType(), source.responseType(), source.useCaseClass(),
source.sourceFile(), source.inputSchemaResource(), source.outputSchemaResource(), definition.version(),
definition.functionDescription(), definition.whenToUse(), definition.whenNotToUse(),
definition.ioLimits(), definition.displayDescription(), definition.exampleQueries(), definition.tags(),
definition.requiredEnvKeys(), definition.ownerOrg(), definition.definitionFile());
}
private boolean isToolDefinition(Path path) {
String normalized = path.toString().replace('\\', '/');
String fileName = path.getFileName().toString().toLowerCase();
return Files.isRegularFile(path) && (fileName.endsWith(".yml") || fileName.endsWith(".yaml"))
&& normalized.contains("/src/main/resources/tool-definitions/");
}
private String text(JsonNode node, String field) {
return node.path(field).asText("");
}
private boolean bool(JsonNode node, String field) {
return node.path(field).asBoolean(false);
}
private String joined(JsonNode node) {
if (!node.isArray()) return node.asText("");
List<String> values = new ArrayList<>();
node.forEach(value -> values.add(value.asText()));
return String.join(" | ", values);
}
private String prefer(String primary, String fallback) {
return primary == null || primary.isBlank() ? fallback : primary;
}
private String relative(Path path) {
return sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/');
return resolveProjectRoot(Path.of(configuredRoot).toAbsolutePath().normalize());
}
private Path resolveProjectRoot(Path configuredPath) {
@@ -153,20 +81,20 @@ public class ToolSourceDiscovery {
&& !normalized.contains("/dap-tool-report/");
}
private void parse(Path path, List<ToolSummary> tools) {
private void parse(Path sourceRoot, Path path, List<ToolSummary> tools) {
try {
CompilationUnit unit = StaticJavaParser.parse(path);
String owner = unit.getPrimaryTypeName().orElse(path.getFileName().toString().replace(".java", ""));
for (MethodDeclaration method : unit.findAll(MethodDeclaration.class)) {
JavaAnnotationReader.find(method.getAnnotations(), "McpTool")
.ifPresent(annotation -> tools.add(toSummary(path, owner, method, annotation)));
.ifPresent(annotation -> tools.add(toSummary(sourceRoot, path, owner, method, annotation)));
}
} catch (Exception exception) {
throw new IllegalStateException("Failed to parse tool source: " + path, exception);
}
}
private ToolSummary toSummary(Path path, String owner, MethodDeclaration method, AnnotationExpr tool) {
private ToolSummary toSummary(Path sourceRoot, Path path, String owner, MethodDeclaration method, AnnotationExpr tool) {
AnnotationExpr hint = JavaAnnotationReader.find(method.getAnnotations(), "GrowToolHint")
.or(() -> JavaAnnotationReader.find(method.getAnnotations(), "ToolHint")).orElse(null);
AnnotationExpr annotations = JavaAnnotationReader.nested(tool, "annotations").orElse(null);
@@ -183,14 +111,30 @@ public class ToolSourceDiscovery {
hint != null && JavaAnnotationReader.bool(hint, "register", false),
hint != null && JavaAnnotationReader.bool(hint, "requiresApproval", false),
annotations != null && JavaAnnotationReader.bool(annotations, "readOnlyHint", false),
annotations != null && JavaAnnotationReader.bool(annotations, "destructiveHint", false),
annotations != null && JavaAnnotationReader.bool(annotations, "idempotentHint", false),
annotations != null
? JavaAnnotationReader.bool(annotations, "destructiveHint",
hint != null && JavaAnnotationReader.bool(hint, "destructive", false))
: hint != null && JavaAnnotationReader.bool(hint, "destructive", false),
annotations != null
? JavaAnnotationReader.bool(annotations, "idempotentHint",
hint != null && JavaAnnotationReader.bool(hint, "idempotent", false))
: hint != null && JavaAnnotationReader.bool(hint, "idempotent", false),
annotations != null && JavaAnnotationReader.bool(annotations, "openWorldHint", false),
requestType,
method.getTypeAsString(),
owner,
sourceRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/'),
hint == null ? "" : JavaAnnotationReader.string(hint, "inputSchemaResource", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "outputSchemaResource", ""));
hint == null ? "" : JavaAnnotationReader.string(hint, "outputSchemaResource", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "version", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "functionDescription", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "whenToUse", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "whenNotToUse", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "ioLimits", ""),
hint == null ? "" : JavaAnnotationReader.string(hint, "displayDescription", ""),
hint == null ? "" : JavaAnnotationReader.strings(hint, "exampleQueries"),
hint == null ? "" : JavaAnnotationReader.strings(hint, "tags"),
hint == null ? "" : JavaAnnotationReader.strings(hint, "requiredEnvKeys"),
hint == null ? "" : JavaAnnotationReader.string(hint, "ownerOrg", ""), "");
}
}

View File

@@ -7,5 +7,6 @@ spring:
name: dap-tool-report
report:
source-root: ${REPORT_SOURCE_ROOT:.}
dap-was-dapmt-source-root: ${REPORT_DAP_WAS_DAPMT_SOURCE_ROOT:../dap-was-dapmt}
dap-admin-source-root: ${REPORT_DAP_ADMIN_SOURCE_ROOT:.}
output-filename-prefix: tool-report

View File

@@ -3,49 +3,53 @@ package io.shinhanlife.dat.report.source;
import static org.assertj.core.api.Assertions.assertThat;
import io.shinhanlife.dat.report.config.ReportProperties;
import io.shinhanlife.dat.report.model.ToolSourceType;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
class ToolSourceDiscoveryTest {
private final Path adminRoot = projectRoot();
private final Path dapmtRoot = adminRoot.resolve("../dap-was-dapmt").normalize();
@Test
void discoversMcpToolsFromProjectSources() {
Path projectRoot = Path.of("..").toAbsolutePath().normalize();
ToolSourceDiscovery discovery = new ToolSourceDiscovery(
new ReportProperties(projectRoot.toString(), "tool-report"));
void discoversToolsFromBothSelectableProjects() {
ToolSourceDiscovery discovery = discovery();
assertThat(discovery.discover())
assertThat(discovery.discover(ToolSourceType.DAP_ADMIN))
.extracting(tool -> tool.name())
.contains("cmm_customer_tool", "cmm_claim_search", "iam_system_status");
assertThat(discovery.discover().stream()
.filter(tool -> tool.name().equals("cmm_customer_tool"))
.findFirst().orElseThrow())
.satisfies(tool -> {
assertThat(tool.version()).isEqualTo("1.0.0");
assertThat(tool.mappingId()).isEqualTo("ONILD0320");
assertThat(tool.definitionFile()).endsWith("tool-definitions/cmm/cmm_customer_tool.yml");
assertThat(tool.whenToUse()).contains("고객 ID");
});
.contains("cmm_claim_search");
assertThat(discovery.discover(ToolSourceType.DAP_WAS_DAPMT))
.extracting(tool -> tool.name())
.contains("spr_field_inquiry_list");
}
@Test
void collectsJsonSchemaFieldsEmbeddedInToolDefinitionYaml() {
Path projectRoot = Path.of("..").toAbsolutePath().normalize();
ToolSourceDiscovery discovery = new ToolSourceDiscovery(
new ReportProperties(projectRoot.toString(), "tool-report"));
var tool = discovery.discover().stream()
.filter(candidate -> candidate.name().equals("cmm_customer_tool"))
void collectsParametersFromDtoAnnotationsInsteadOfYamlDefinitions() {
ToolSourceDiscovery discovery = discovery();
var tool = discovery.discover(ToolSourceType.DAP_WAS_DAPMT).stream()
.filter(candidate -> candidate.name().equals("spr_field_inquiry_list"))
.findFirst().orElseThrow();
var report = new ToolDetailAnalyzer(discovery).analyze(tool);
var report = new ToolDetailAnalyzer(discovery).analyze(tool, ToolSourceType.DAP_WAS_DAPMT);
assertThat(report.fields())
.anySatisfy(field -> {
assertThat(field.sourceKind()).isEqualTo("TOOL_DEFINITION");
assertThat(report.fields()).anySatisfy(field -> {
assertThat(field.sourceKind()).isEqualTo("DTO");
assertThat(field.direction()).isEqualTo("INPUT");
assertThat(field.fieldName()).isEqualTo("csNo");
assertThat(field.required()).isTrue();
assertThat(field.fieldName()).isEqualTo("inquiryId");
assertThat(field.constraints()).contains("Schema=");
});
assertThat(report.fields()).noneMatch(field -> field.sourceKind().equals("TOOL_DEFINITION"));
}
private ToolSourceDiscovery discovery() {
return new ToolSourceDiscovery(new ReportProperties(
dapmtRoot.toString(), adminRoot.toString(), "tool-report"));
}
private Path projectRoot() {
Path current = Path.of("").toAbsolutePath().normalize();
return java.nio.file.Files.isRegularFile(current.resolve("settings.gradle"))
? current : current.getParent();
}
}

View File

@@ -1,51 +1,45 @@
package io.shinhanlife.dat.mcg.presentation;
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
import org.springframework.web.server.ResponseStatusException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Gateway 내부 MCP 목록 조회와 Report 서비스의 Excel API 프록시를 담당한다. */
/** Tool Report 화면과 소스 분석 서비스를 연결한다. */
@RestController
public class ToolReportProxyController {
private final RestClient restClient;
private final String reportServiceUrl;
private final McpRouterController mcpRouterController;
public ToolReportProxyController(
RestClient.Builder restClientBuilder,
McpRouterController mcpRouterController,
public ToolReportProxyController(RestClient.Builder restClientBuilder,
@Value("${report.service-url}") String reportServiceUrl) {
this.restClient = restClientBuilder.build();
this.mcpRouterController = mcpRouterController;
this.reportServiceUrl = reportServiceUrl.replaceAll("/+$", "");
}
/** Tool 목록은 Report 서비스나 로컬 소스가 아니라 Gateway의 MCP Registry에서만 조회한다. */
@GetMapping(value = "/report/api/report-tools", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ReportToolSummary> tools() {
return activeTools().stream().map(ReportToolSummary::from).toList();
public List<ReportToolSummary> tools(@RequestParam(required = false) String sourceType) {
ReportToolSummary[] tools = restClient.get()
.uri(reportServiceUrl + "/api/report-tools?sourceType={sourceType}",
sourceType == null ? "DAP_WAS_DAPMT" : sourceType)
.retrieve()
.body(ReportToolSummary[].class);
return tools == null ? List.of() : List.of(tools);
}
@PostMapping(value = "/report/api/tool-reports/excel",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public ResponseEntity<byte[]> excel(@RequestBody Map<String, Object> request) {
validateMcpSelection(request);
ResponseEntity<byte[]> response = restClient.post()
.uri(reportServiceUrl + "/api/tool-reports/excel")
.contentType(MediaType.APPLICATION_JSON)
@@ -55,28 +49,6 @@ public class ToolReportProxyController {
return copy(response);
}
private void validateMcpSelection(Map<String, Object> request) {
Object rawNames = request.get("toolNames");
if (!(rawNames instanceof List<?> names) || names.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "toolNames must contain at least one tool");
}
Set<String> registeredNames = activeTools().stream()
.map(ToolMetadata::getName)
.collect(java.util.stream.Collectors.toSet());
Set<String> unknown = new LinkedHashSet<>();
names.stream().map(String::valueOf)
.filter(name -> !registeredNames.contains(name))
.forEach(unknown::add);
if (!unknown.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"MCP에 등록되지 않은 툴이 포함되어 있습니다: " + unknown);
}
}
private List<ToolMetadata> activeTools() {
return mcpRouterController.activeToolsForReport();
}
private ResponseEntity<byte[]> copy(ResponseEntity<byte[]> response) {
ResponseEntity.BodyBuilder builder = ResponseEntity.status(response.getStatusCode());
MediaType contentType = response.getHeaders().getContentType();
@@ -87,38 +59,12 @@ public class ToolReportProxyController {
}
public record ReportToolSummary(
String name,
String title,
String description,
String categoryKey,
String mappingId,
boolean register,
boolean requiresApproval,
boolean readOnlyHint,
boolean destructiveHint,
boolean idempotentHint,
boolean openWorldHint,
String requestType,
String responseType,
String useCaseClass,
String sourceFile,
String inputSchemaResource,
String outputSchemaResource) {
static ReportToolSummary from(ToolMetadata tool) {
String name = tool.getName() == null || tool.getName().isBlank() ? tool.getUid() : tool.getName();
String title = tool.getDisplayName() == null || tool.getDisplayName().isBlank()
? name : tool.getDisplayName();
return new ReportToolSummary(
name, title, value(tool.getDescription()), value(tool.getCategoryKey()),
value(tool.getMciServiceId()), Boolean.TRUE.equals(tool.getIsRegistered()),
Boolean.TRUE.equals(tool.getRequiresApproval()), Boolean.TRUE.equals(tool.getReadOnlyHint()),
Boolean.TRUE.equals(tool.getDestructiveHint()), Boolean.TRUE.equals(tool.getIdempotentHint()),
Boolean.TRUE.equals(tool.getOpenWorldHint()), "", "", "", "", "", "");
}
private static String value(String value) {
return value == null ? "" : value;
}
String name, String title, String description, String categoryKey, String mappingId,
boolean register, boolean requiresApproval, boolean readOnlyHint, boolean destructiveHint,
boolean idempotentHint, boolean openWorldHint, String requestType, String responseType,
String useCaseClass, String sourceFile, String inputSchemaResource, String outputSchemaResource,
String version, String functionDescription, String whenToUse, String whenNotToUse,
String ioLimits, String displayDescription, String exampleQueries, String tags,
String requiredEnvKeys, String ownerOrg, String definitionFile) {
}
}

View File

@@ -31,7 +31,7 @@
.subtitle { margin: 0; color: #a1a1aa; font-size: 14px; line-height: 1.65; }
.count-badge { padding: 7px 11px; border: 1px solid #27272a; border-radius: 8px; background: #18181b; color: #a1a1aa; font-family: "Geist Mono", Consolas, monospace; font-size: 12px; white-space: nowrap; }
.panel { overflow: hidden; border: 1px solid #27272a; border-radius: 12px; background: #111113; box-shadow: 0 18px 50px rgba(0,0,0,.22); }
.toolbar { display: grid; grid-template-columns: minmax(260px,1fr) 190px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
.toolbar { display: grid; grid-template-columns: 210px minmax(240px,1fr) 170px auto auto; gap: 10px; padding: 16px; border-bottom: 1px solid #27272a; background: #111113; }
input, select, button { min-height: 40px; border: 1px solid #3f3f46; border-radius: 7px; background: #18181b; color: #e4e4e7; padding: 8px 12px; font: inherit; font-size: 13px; outline: none; }
input::placeholder { color: #71717a; }
input:focus, select:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
@@ -95,6 +95,10 @@
<section class="panel">
<div class="toolbar">
<select id="sourceType" aria-label="Tool 소스 프로젝트">
<option value="DAP_WAS_DAPMT">dap-was-dapmt 소스</option>
<option value="DAP_ADMIN">dap-admin 소스</option>
</select>
<input id="keyword" type="search" autocomplete="off" placeholder="툴명, 제목 또는 설명 검색">
<select id="category"><option value="">전체 카테고리</option></select>
<button id="selectAll" type="button">전체 선택</button>
@@ -114,7 +118,7 @@
</main>
<script>
const state = { tools: [], selected: new Set() };
const state = { tools: [], selected: new Set(), sourceType: 'DAP_WAS_DAPMT' };
const el = id => document.getElementById(id);
const gateway = location.origin;
@@ -155,7 +159,11 @@
async function load() {
try {
const response = await fetch('/report/api/report-tools');
state.sourceType = el('sourceType').value;
state.selected.clear();
state.tools = [];
el('tools').innerHTML = '<tr><td class="loading-row" colspan="6">선택한 프로젝트의 Tool 소스를 분석하고 있습니다.</td></tr>';
const response = await fetch(`/report/api/report-tools?sourceType=${encodeURIComponent(state.sourceType)}`);
const body = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(body?.error || body?.message || `툴 목록 조회에 실패했습니다. (${response.status})`);
@@ -178,6 +186,10 @@
}
el('keyword').addEventListener('input', render);
el('sourceType').addEventListener('change', () => {
el('category').innerHTML = '<option value="">전체 카테고리</option>';
load();
});
el('category').addEventListener('change', render);
el('selectAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.add(tool.name)); render(); });
el('clearAll').addEventListener('click', () => { filtered().forEach(tool => state.selected.delete(tool.name)); render(); });
@@ -185,7 +197,8 @@
el('download').disabled = true; updateStatus('소스를 분석하고 Excel을 생성하는 중입니다.');
try {
const response = await fetch('/report/api/tool-reports/excel', {
method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({toolNames: [...state.selected]})
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({toolNames: [...state.selected], sourceType: state.sourceType})
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));

View File

@@ -36,11 +36,13 @@ services:
dockerfile: dap-tool-report/Dockerfile
volumes:
- ./:/src:ro
- ../dap-was-dapmt:/tool-src/dap-was-dapmt:ro
environment:
- TZ=Asia/Seoul
- REPORT_SERVER_ADDRESS=0.0.0.0
- REPORT_SERVER_PORT=8092
- REPORT_SOURCE_ROOT=/src
- REPORT_DAP_ADMIN_SOURCE_ROOT=/src
- REPORT_DAP_WAS_DAPMT_SOURCE_ROOT=/tool-src/dap-was-dapmt
- JAVA_TOOL_OPTIONS=-Xms64m -Xmx384m
mci-mock:

View File

@@ -42,3 +42,148 @@
2026-08-14 13:08:17.840 [tomcat-shutdown] [] INFO o.s.b.w.e.tomcat.GracefulShutdown - Graceful shutdown complete
2026-08-14 13:08:17.864 [SpringApplicationShutdownHook] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2026-08-14 13:08:17.866 [SpringApplicationShutdownHook] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
2026-08-20 14:19:27.963 [background-preinit] [] INFO o.h.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.3.Final
2026-08-20 14:19:28.008 [main] [] INFO i.s.dat.mcc.pro.DatWasProApplication - Starting DatWasProApplication using Java 21.0.12 with PID 26276 (D:\dev\AX\ax_hub_mcp_tool\dat-was-pro\build\classes\java\main started by User in D:\dev\AX\ax_hub_mcp_tool)
2026-08-20 14:19:28.008 [main] [] DEBUG i.s.dat.mcc.pro.DatWasProApplication - Running with Spring Boot v3.5.11, Spring v6.2.16
2026-08-20 14:19:28.008 [main] [] INFO i.s.dat.mcc.pro.DatWasProApplication - The following 1 profile is active: "local"
2026-08-20 14:19:29.245 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
2026-08-20 14:19:29.249 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2026-08-20 14:19:29.290 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 16 ms. Found 0 Redis repository interfaces.
2026-08-20 14:19:29.537 [main] [] WARN o.m.s.mapper.ClassPathMapperScanner - No MyBatis mapper was found in '[io.shinhanlife.dat]' package. Please check your configuration.
2026-08-20 14:19:31.164 [main] [] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port 8085 (http)
2026-08-20 14:19:31.181 [main] [] INFO o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8085"]
2026-08-20 14:19:31.183 [main] [] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2026-08-20 14:19:31.183 [main] [] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.52]
2026-08-20 14:19:31.294 [main] [] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2026-08-20 14:19:31.295 [main] [] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 3244 ms
2026-08-20 14:19:31.300 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - =================================================
2026-08-20 14:19:31.300 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - ToolMcpServerConfiguration IS LOADED BY SPRING!!!
2026-08-20 14:19:31.300 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - =================================================
2026-08-20 14:19:31.460 [main] [] DEBUG i.s.d.lib.mcp.McpRequestHeaderFilter - Filter 'mcpRequestHeaderFilter' configured for use
2026-08-20 14:19:31.461 [main] [] DEBUG i.s.d.l.mcp.filter.MdcLoggingFilter - Filter 'mdcLoggingFilter' configured for use
2026-08-20 14:19:31.951 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 초기화 시작. Gateway URL: http://localhost:8081, Pod URL: http://localhost:8085
2026-08-20 14:19:34.264 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2026-08-20 14:19:34.458 [main] [] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.p6spy.engine.wrapper.ConnectionWrapper@275ff5f8
2026-08-20 14:19:34.460 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2026-08-20 14:19:34.467 [main] [] INFO o.s.b.a.h.H2ConsoleAutoConfiguration - H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:testdb'
2026-08-20 14:19:34.735 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 1cc321b2-3802-3695-856e-655d2a4ae438 (isRegistered: true)
2026-08-20 14:19:34.736 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: e54fffbe-8816-3cc3-8372-6787fdeec58a (isRegistered: true)
2026-08-20 14:19:34.736 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d0105992-c339-355c-a1fe-387190040378 (isRegistered: true)
2026-08-20 14:19:34.736 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b2ea321a-ccb8-361c-818d-8b4e434ae5ab (isRegistered: true)
2026-08-20 14:19:34.736 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 8f4454a1-f181-32c7-ae4d-78fc379e779d (isRegistered: true)
2026-08-20 14:19:34.736 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d0ecf6c1-addf-3cfb-b782-5fbe92e0d7c4 (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 7459a3ba-99cd-328c-828e-ecc1db8505d2 (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 2832abab-c447-3f43-8de9-e53cde0f72b9 (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f6da5091-b093-305a-89d3-9257aae6f6f3 (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b4c60519-e956-3d17-ac4d-402ac58e526f (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 32bb3aff-e1c2-3fe1-b12a-a14731b0c216 (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: dda8437c-b38e-3e2f-b644-e358aa92b13b (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: e927805c-68da-3f89-ab78-ef4953d018bd (isRegistered: true)
2026-08-20 14:19:34.737 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 26488ca8-2155-3bf1-8201-c355c7bc79f9 (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 0f504ada-3545-3c8e-ae32-18e7fa4d518b (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f9221bb9-83ee-36fa-9dae-43b631e62486 (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 2df9f86a-080a-3638-953d-f1b0507736b2 (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f8ebe2bd-bc8a-3bae-9c0a-fc562edd5dd3 (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 2884bea3-d3a4-3e1a-bf55-557fd78e87fb (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 36ef9ecf-670e-3cd5-bfd7-4ecb5abd60ec (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: abbd850f-9b78-3179-b261-96f03dc99969 (isRegistered: true)
2026-08-20 14:19:34.738 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d1236725-155b-3d68-93df-7469ad0ec085 (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 356a23e2-be6e-3b5f-b0e1-a2ba6befe0cd (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 5f901eaf-9b09-3fbe-a31d-c58c5841f8d1 (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 5db09024-b97e-3c77-8472-7caf08fdac0a (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 631a66ac-4aaf-39e6-b6bf-53003152e3fb (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 910aee44-8863-3192-955d-1b6cb6c64794 (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d085d13a-90fa-375d-b819-2ac939c89931 (isRegistered: true)
2026-08-20 14:19:34.739 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 82e524aa-80d9-3ec3-ba5b-0a2306b97964 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 0fa7dccc-9a5a-34c7-b0c1-013c0463d81f (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 4e3f1a9b-7293-38ea-b903-eadb26a64650 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 53d3214e-31c6-3ff7-a9ad-126b2cc463da (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d018c9e7-5cea-3787-b80d-e8a3ace07153 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: e96980fc-9756-3c0f-a4b1-fc74b23b50b4 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d2519ff1-2b22-31c6-8187-02f55cd5a2c2 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: e16e016b-4699-3137-af62-25943467fb06 (isRegistered: true)
2026-08-20 14:19:34.740 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 258d3a36-09c3-3b02-91c9-ffa1b62007b5 (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: a146d984-8f27-343a-be30-5aea916b5f7b (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 7a8bad7c-8cb7-37d8-bb5a-2abe52f4582b (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 72fef7af-103f-3bfe-a847-009cc1c1463e (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d835d64d-739b-3c51-bd3c-049a15390e47 (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 655e1319-8626-37d1-b219-473ddc815bd0 (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 4eb38230-ec96-3381-bcd4-cd1291ef4d7f (isRegistered: true)
2026-08-20 14:19:34.741 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f267f753-dabc-345a-ad24-1f9bb19032f9 (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d4e42a57-54ea-3458-a097-78cdf91e3f47 (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: c8659945-3f2c-3790-91c8-204762eba97f (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 79ae6f34-0005-34c5-baa5-2fa0a6e0f757 (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 7832e57b-e495-312d-8135-be74e2ffa07e (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 62a9a8c6-0239-3405-bd86-adf288fb3500 (isRegistered: true)
2026-08-20 14:19:34.742 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: a1a11190-fac2-3000-9e18-a5456fe03cac (isRegistered: true)
2026-08-20 14:19:34.806 [main] [] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8085"]
2026-08-20 14:19:34.814 [main] [] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2026-08-20 14:19:34.880 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.882 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.884 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.886 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.888 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.889 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.891 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.892 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.894 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.895 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.897 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.898 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.900 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.901 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.903 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.904 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.905 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.907 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.908 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.909 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.911 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.913 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.914 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.915 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.917 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.918 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.920 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.921 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.923 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.925 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.926 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.929 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.931 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.933 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.935 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.937 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.939 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.941 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.942 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.944 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.946 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.948 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.951 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.952 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.954 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.955 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.957 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.958 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.959 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.961 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:34.968 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2026-08-20 14:19:34.969 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
2026-08-20 14:19:34.986 [main] [] INFO o.s.b.a.l.ConditionEvaluationReportLogger -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2026-08-20 14:19:35.003 [main] [] ERROR o.s.b.d.LoggingFailureAnalysisReporter -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8085 was already in use.
Action:
Identify and stop the process that's listening on port 8085 or configure this application to listen on another port.

View File

@@ -978,3 +978,150 @@ Action:
Identify and stop the process that's listening on port 8086 or configure this application to listen on another port.
2026-08-20 14:19:23.437 [background-preinit] [] INFO o.h.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.3.Final
2026-08-20 14:19:23.493 [main] [] INFO i.s.dat.mcc.sys.DatWasSysApplication - Starting DatWasSysApplication using Java 21.0.12 with PID 22880 (D:\dev\AX\ax_hub_mcp_tool\dat-was-sys\build\classes\java\main started by User in D:\dev\AX\ax_hub_mcp_tool)
2026-08-20 14:19:23.494 [main] [] DEBUG i.s.dat.mcc.sys.DatWasSysApplication - Running with Spring Boot v3.5.11, Spring v6.2.16
2026-08-20 14:19:23.494 [main] [] INFO i.s.dat.mcc.sys.DatWasSysApplication - The following 1 profile is active: "local"
2026-08-20 14:19:24.512 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
2026-08-20 14:19:24.515 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2026-08-20 14:19:24.548 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 10 ms. Found 0 Redis repository interfaces.
2026-08-20 14:19:24.694 [main] [] WARN o.m.s.mapper.ClassPathMapperScanner - No MyBatis mapper was found in '[io.shinhanlife.dat]' package. Please check your configuration.
2026-08-20 14:19:25.651 [main] [] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port 8086 (http)
2026-08-20 14:19:25.663 [main] [] INFO o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8086"]
2026-08-20 14:19:25.664 [main] [] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
2026-08-20 14:19:25.665 [main] [] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.52]
2026-08-20 14:19:25.764 [main] [] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2026-08-20 14:19:25.765 [main] [] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2220 ms
2026-08-20 14:19:25.769 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - =================================================
2026-08-20 14:19:25.769 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - ToolMcpServerConfiguration IS LOADED BY SPRING!!!
2026-08-20 14:19:25.769 [main] [] WARN i.s.d.l.m.ToolMcpServerConfiguration - =================================================
2026-08-20 14:19:25.957 [main] [] DEBUG i.s.d.lib.mcp.McpRequestHeaderFilter - Filter 'mcpRequestHeaderFilter' configured for use
2026-08-20 14:19:25.958 [main] [] DEBUG i.s.d.l.mcp.filter.MdcLoggingFilter - Filter 'mdcLoggingFilter' configured for use
2026-08-20 14:19:26.788 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 초기화 시작. Gateway URL: http://localhost:8081, Pod URL: http://localhost:8086
2026-08-20 14:19:30.532 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2026-08-20 14:19:30.916 [main] [] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.p6spy.engine.wrapper.ConnectionWrapper@42a698bd
2026-08-20 14:19:30.918 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2026-08-20 14:19:30.930 [main] [] INFO o.s.b.a.h.H2ConsoleAutoConfiguration - H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:testdb'
2026-08-20 14:19:31.335 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: d1acbab4-8d44-3180-a19a-695ed6104049 (isRegistered: true)
2026-08-20 14:19:31.335 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 011a1d16-ecea-326b-ac10-b74cf07c2050 (isRegistered: true)
2026-08-20 14:19:31.335 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 3e060b46-aa8a-3087-944a-bfcece2e0f31 (isRegistered: true)
2026-08-20 14:19:31.335 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 4917602b-9b12-317a-8514-0674425409d0 (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 5ac40409-c1ab-372e-9337-642efdee90ae (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 23ad0832-d1f0-3491-91b6-cb32229dbe11 (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: bca4fe8d-20eb-3031-89dc-ad2bfc58b4fb (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: a9963bf7-7289-375d-ab18-94f2b9367fe0 (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: edc2fecd-853b-3ca5-a682-c4715deeaf43 (isRegistered: true)
2026-08-20 14:19:31.336 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 1cabbd01-06ec-349a-9ec0-55ed949702ea (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: fdaf4b89-889f-3342-b1a0-a1dec729ada9 (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 4461a652-3f98-33f1-8eff-dcf6011e93ea (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 406fb5fd-6e3e-3ded-8bae-f2e5921aad91 (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 60728b23-5014-37b3-b19f-edb140555dbd (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 5b5a90c1-9bd2-3a51-9cda-0a7bc66fddfa (isRegistered: true)
2026-08-20 14:19:31.337 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 2dac4981-9e18-3ec9-a34c-76bd4b47f574 (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b9daeeb3-47df-330d-9489-a0b3a2e37911 (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f2c794fb-0601-34b9-bb74-c3a1900beb89 (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b1921355-1f33-3d8f-8548-39f57783371c (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 7391e562-a322-3d3a-a132-57c2dd816993 (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 04d36c22-7627-3077-9432-2fc253c94f96 (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b07c666e-bc71-36cf-8bf6-2c62335a66ea (isRegistered: true)
2026-08-20 14:19:31.338 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: f445fd9f-5966-3a77-9e5b-a9655a66e817 (isRegistered: true)
2026-08-20 14:19:31.339 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 8fd31065-6601-3417-b5fc-6787dd0de259 (isRegistered: true)
2026-08-20 14:19:31.339 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 15604c40-7f87-3a60-acfc-854a472101e1 (isRegistered: true)
2026-08-20 14:19:31.339 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: c4a9db51-de9d-393b-bf6d-4536251ce716 (isRegistered: true)
2026-08-20 14:19:31.339 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 8be0c7e2-c582-30aa-a72f-750a07008c1c (isRegistered: true)
2026-08-20 14:19:31.339 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: a71e9526-e9ea-3da3-8097-e4c8c6a7eb33 (isRegistered: true)
2026-08-20 14:19:31.340 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 07240d7f-6d4c-3c63-aeee-ee273f052906 (isRegistered: true)
2026-08-20 14:19:31.340 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 7c987af2-39fc-3364-8719-73f659e57e7d (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 48a88da9-b632-3786-bdaa-e74f951183d3 (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 351b2bca-444c-30aa-bc96-b05f59d0ce05 (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b7d5c1d0-2e63-3507-89ca-479f8d75503e (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 97b1ec01-b8e9-3bf2-9204-a415334c9ebe (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: bc74fc01-f2da-3d37-bc3b-24c4d095070a (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 4217ee01-7b52-396c-ac72-49a07d2621bc (isRegistered: true)
2026-08-20 14:19:31.341 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: c44a0750-be28-31f7-8253-455de3b9bc5c (isRegistered: true)
2026-08-20 14:19:31.342 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 742ce39b-8672-314d-b18c-26656389dcca (isRegistered: true)
2026-08-20 14:19:31.342 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 06c719e1-17f1-3297-a17b-5d07396b9171 (isRegistered: true)
2026-08-20 14:19:31.342 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 04d2cd6c-bbb6-3c3c-9f13-0d27d1175f39 (isRegistered: true)
2026-08-20 14:19:31.342 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 15b169f4-a94e-3739-8ea9-cccf30ace2bd (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 5650005c-e272-34b6-aa80-6f40973c1248 (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: c272660d-f9b3-3bc9-8a70-7f83bad7e1d0 (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 6432579a-5e78-3d56-b2b8-335044ed5f9f (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: cb219e80-4416-3006-8c8b-bdd36471bee5 (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 6ea96703-22a9-338a-817f-af1c2296f01c (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 1387ea69-6024-3b3c-a988-3330642c159a (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: b0f3ca6f-c57d-352f-80c0-9cf712c409d4 (isRegistered: true)
2026-08-20 14:19:31.343 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 39b834e0-5faa-3d5b-b3a9-6d27d74b8f18 (isRegistered: true)
2026-08-20 14:19:31.344 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 515de5ad-fd1d-3470-9248-ef91fb902811 (isRegistered: true)
2026-08-20 14:19:31.344 [main] [] INFO i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 도구 메타데이터 생성: 6faebe14-526a-30d0-869d-6f4e892f0c76 (isRegistered: true)
2026-08-20 14:19:31.431 [main] [] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8086"]
2026-08-20 14:19:31.439 [main] [] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2026-08-20 14:19:31.513 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.515 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.518 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.520 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.523 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.525 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.528 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.530 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.532 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.534 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.536 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.539 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.542 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.544 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.547 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.550 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.552 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.554 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.555 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.558 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.560 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.562 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.564 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.567 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.569 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.571 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.573 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.576 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.578 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.579 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.581 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.583 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.585 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.586 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.588 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.594 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.601 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.604 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.609 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.613 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.618 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.626 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.631 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.643 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.648 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.652 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.657 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.660 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.664 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.668 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.672 [main] [] WARN i.s.d.l.m.ToolRegistryHeartbeatSender - [HeartbeatSender] 툴 삭제 실패: I/O error on POST request for "http://localhost:8081/mcp/api/v1/registry/deregister": null
2026-08-20 14:19:31.682 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2026-08-20 14:19:31.684 [main] [] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
2026-08-20 14:19:31.710 [main] [] INFO o.s.b.a.l.ConditionEvaluationReportLogger -
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2026-08-20 14:19:31.742 [main] [] ERROR o.s.b.d.LoggingFailureAnalysisReporter -
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8086 was already in use.
Action:
Identify and stop the process that's listening on port 8086 or configure this application to listen on another port.