diff --git a/README.md b/README.md
index acc561d2..650cddc6 100644
--- a/README.md
+++ b/README.md
@@ -588,3 +588,19 @@ oth.smp.weather.inquiry
```
When Scaffold receives `dap-tool-oth`, `cmm`, and `ClaimSearch`, it generates `oth.cmm.claim.search`. The `validateMcpToolNames` Gradle task rejects both a duplicate name and any name outside this format before packaging, including its source file and line number.
+
+### Tool Test Console
+
+각 Tool Pod는 공통 테스트 화면을 제공합니다.
+
+```text
+http://localhost:8084/tool-test-console.html
+```
+
+화면은 현재 Pod의 `/tool-manifest`에서 Tool 목록과 `inputSchema`를 읽습니다. Tool을 선택한 뒤 `Schema 샘플 채우기`로 요청 JSON을 만들고 실행할 수 있습니다. 업무에 맞게 보정한 요청은 `현재 요청 저장`으로 브라우저의 `localStorage`에 보관합니다.
+
+`Run saved cases`는 저장된 테스트 케이스를 순차 실행해 성공/실패, HTTP 상태, 소요 시간을 보여줍니다. 따라서 Tool이 수백 개여도 각 Tool마다 테스트 화면을 만들 필요 없이, 유효한 업무 테스트 데이터만 한 번 저장하면 이후에는 몇 번의 클릭으로 회귀 테스트할 수 있습니다.
+
+- Tool 호출은 현재 Pod의 `/mcp/{toolName}`로 수행합니다.
+- 매 실행마다 `trace-id`, `request-id`를 새로 생성하여 응답과 함께 표시합니다.
+- 외부 MCI/EAI Tool은 샘플값 대신 개발계에서 허용된 테스트 데이터를 저장해서 사용해야 합니다.
diff --git a/dap-gateway/src/main/resources/static/admin/scaffold.html b/dap-gateway/src/main/resources/static/admin/scaffold.html
index cea50134..1c05d10e 100644
--- a/dap-gateway/src/main/resources/static/admin/scaffold.html
+++ b/dap-gateway/src/main/resources/static/admin/scaffold.html
@@ -381,6 +381,8 @@
Catalog
Playground
Chat
+ Tester
+ Console
diff --git a/dap-gateway/src/main/resources/static/catalog.html b/dap-gateway/src/main/resources/static/catalog.html
index 11371ef1..b13da770 100644
--- a/dap-gateway/src/main/resources/static/catalog.html
+++ b/dap-gateway/src/main/resources/static/catalog.html
@@ -70,6 +70,8 @@
Catalog
Playground
Chat
+
Tester
+
Console
diff --git a/dap-gateway/src/main/resources/static/chat.html b/dap-gateway/src/main/resources/static/chat.html
index d166de4d..70b21172 100644
--- a/dap-gateway/src/main/resources/static/chat.html
+++ b/dap-gateway/src/main/resources/static/chat.html
@@ -57,6 +57,8 @@
Catalog
Playground
Chat
+
Tester
+
Console
diff --git a/dap-gateway/src/main/resources/static/playground.html b/dap-gateway/src/main/resources/static/playground.html
index 028d8590..4c2a7efe 100644
--- a/dap-gateway/src/main/resources/static/playground.html
+++ b/dap-gateway/src/main/resources/static/playground.html
@@ -112,6 +112,8 @@
Catalog
Playground
Chat
+
Tester
+
Console
diff --git a/dap-gateway/src/main/resources/static/tester.html b/dap-gateway/src/main/resources/static/tester.html
new file mode 100644
index 00000000..3e21e53a
--- /dev/null
+++ b/dap-gateway/src/main/resources/static/tester.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
Tool Auto Tester
+
+
+
+
+
+
+
+
+
+
+
+
Auto-Tester Dashboard
+
Batch execute all registered tools with auto-generated dummy data to verify stability.
+
+
+
+
+ Export CSV
+
+
+
+ Run All Tests
+
+
+
+
+
+
+
Tool List
+
Total: 0 / Completed: 0
+
+
+
+
+
+
+
+
diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/manifest/ToolManifestService.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/manifest/ToolManifestService.java
index aa3d42db..75df12df 100644
--- a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/manifest/ToolManifestService.java
+++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/manifest/ToolManifestService.java
@@ -12,6 +12,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -21,6 +22,7 @@ import org.springframework.stereotype.Service;
public class ToolManifestService {
private static final long DEFAULT_TIMEOUT_MILLIS = 300000L;
+ private static final AtomicLong LAST_ISSUED_REVISION = new AtomicLong();
private final Supplier
> toolSupplier;
private final ObjectMapper objectMapper;
private final McpProperties properties;
@@ -89,10 +91,9 @@ public class ToolManifestService {
private synchronized String revision(String bundleId, List tools) {
String fingerprint = fingerprint(bundleId, tools);
if (!fingerprint.equals(lastFingerprint)) {
- long nextTimestamp = System.currentTimeMillis();
- if (lastRevision != null) {
- nextTimestamp = Math.max(nextTimestamp, Long.parseLong(lastRevision) + 1);
- }
+ long localMinimum = lastRevision == null ? Long.MIN_VALUE : Long.parseLong(lastRevision) + 1;
+ long nextTimestamp = LAST_ISSUED_REVISION.updateAndGet(previous ->
+ Math.max(Math.max(System.currentTimeMillis(), localMinimum), previous + 1));
lastFingerprint = fingerprint;
lastRevision = Long.toString(nextTimestamp);
}
diff --git a/dap-tool-core/src/main/resources/static/tool-test-console.html b/dap-tool-core/src/main/resources/static/tool-test-console.html
new file mode 100644
index 00000000..951eda99
--- /dev/null
+++ b/dap-tool-core/src/main/resources/static/tool-test-console.html
@@ -0,0 +1,260 @@
+
+
+
+
+
+ AX HUB Tool Test Console
+
+
+
+
+
+
+
+ Schema 기반 Tool 테스트 Tool을 선택하고 요청 JSON을 확인한 뒤 실행하세요. 검증한 요청은 브라우저에 저장되며, 저장된 케이스 전체를 한 번에 다시 실행할 수 있습니다.
+
+
+
+ 2. 요청 JSON 필수값과 형식은 Tool의 inputSchema 기준입니다. MCI·외부 연동 Tool은 업무에 맞는 테스트 데이터를 입력한 후 저장하세요.
현재 요청 저장 실행
+ 3. 실행 결과 대기 - trace-id: - request-id: -
Tool을 선택하고 실행하세요.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java
new file mode 100644
index 00000000..6c21272c
--- /dev/null
+++ b/dap-tool-core/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java
@@ -0,0 +1,24 @@
+package io.shinhanlife.dap.mcc.presentation;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Test;
+
+class ToolTestConsoleResourceTest {
+
+ @Test
+ void publishesManifestDrivenToolTestConsole() throws Exception {
+ try (InputStream resource = getClass().getResourceAsStream("/static/tool-test-console.html")) {
+ assertNotNull(resource);
+ String html = new String(resource.readAllBytes(), StandardCharsets.UTF_8);
+
+ assertTrue(html.contains("/tool-manifest"));
+ assertTrue(html.contains("Run saved cases"));
+ assertTrue(html.contains("localStorage"));
+ assertTrue(html.contains("request-id"));
+ }
+ }
+}
\ No newline at end of file