Compare commits
2 Commits
main
...
05ea5f9aaa
| Author | SHA1 | Date | |
|---|---|---|---|
| 05ea5f9aaa | |||
| 50315ad245 |
@@ -33,11 +33,7 @@
|
||||
* 처리계 UI ↔ 처리계 AP 구간: `HTTPS` / `SSV`를 사용하며, FW에서 x-api를 통해 SSV↔DTO 변환을 수행한다.
|
||||
|
||||
* 신한라이프 표준 로그 기준 (Logback 설정):
|
||||
* **로그 생성 경로:** `/swlog/어플리케이션명(모듈명)/코드명/` (예: `/swlog/dat-gateway/A01/`)
|
||||
* **로그 생성 경로:** `/swlog/어플리케이션명(모듈명)/코드명/` (예: `/swlog/dap-gateway/A01/`)
|
||||
* **로그 네이밍 규칙:** `${HOSTNAME}_코드명_yyyyMMdd.log` (예: `${HOSTNAME}_A01_20260722.log`)
|
||||
* **기본 로그 구분 코드:** 시스템 운영기록 가동기록의 경우 `A01`을 기본으로 사용한다.
|
||||
* **호스트명 동적 할당:** Logback 설정 시 `<property name="HOSTNAME" value="${HOSTNAME}" />` 를 선언하여 사용한다.
|
||||
|
||||
* 프로젝트 작업 범위 및 툴 연동 규칙:
|
||||
* 현재 `axhub-backend-main` 워크스페이스 내에서는 **오직 `dat-gateway` 모듈만 수정**한다.
|
||||
* 툴(Tool) 연동 개발 작업은 반드시 별도의 워크스페이스인 **`C:\eGovFrameDev-4.3.1-64bit\workspace-egov\dat-was-datmt`** 폴더에서 수행한다.
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
apt-get update
|
||||
apt-get install -y nodejs git openjdk-21-jdk rsync
|
||||
elif command -v apk &> /dev/null; then
|
||||
apk add --no-cache nodejs git openjdk21 rsync docker-cli docker-cli-compose
|
||||
apk add --no-cache nodejs git openjdk21 rsync
|
||||
fi
|
||||
|
||||
- name: Checkout Code
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
run: |
|
||||
echo "Copying latest code to /app (Host Volume) and removing stale files..."
|
||||
if command -v rsync &> /dev/null; then
|
||||
rsync -a --delete --exclude='.git' --exclude='bin' --exclude='act_runner_data' . /app/
|
||||
rsync -a --delete --exclude='.git' . /app/
|
||||
else
|
||||
rm -rf /app/dap-* /app/src /app/build.gradle /app/settings.gradle
|
||||
cp -a . /app/
|
||||
@@ -59,10 +59,9 @@ jobs:
|
||||
# 4. 마운트된 /app 디렉토리로 이동하여 호스트의 도커 컴포즈 제어!
|
||||
cd /app
|
||||
docker system prune -f
|
||||
ACTIVE_PROFILE=dev docker compose up -d --build --pull always --remove-orphans gateway tool-report mci-mock dozzle was-sal was-cus was-pro was-sys
|
||||
ACTIVE_PROFILE=dev docker compose up -d --build --remove-orphans gateway redis mci-mock dozzle was-sms was-oth
|
||||
|
||||
# 5. 배포 후 대롱대롱 매달려 있는 가비지 이미지 및 빌드 캐시 자동 소거 청소!
|
||||
docker image prune -a -f
|
||||
docker builder prune -a -f
|
||||
# 5. 배포 후 대롱대롱 매달려 있는 가비지 이미지 자동 소거 청소!
|
||||
docker image prune -f
|
||||
|
||||
echo "CI/CD Deploy Success!"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -122,6 +122,3 @@ ehthumbs.db
|
||||
# ===== ?섍꼍?ㅼ젙 (誘쇨컧?뺣낫 遺꾨━ ?? =====
|
||||
# application-local.yml
|
||||
# application-secret.yml
|
||||
|
||||
# ===== Generated Sources =====
|
||||
**/src/main/generated/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
#Wed Jul 29 17:05:55 KST 2026
|
||||
gradle.version=8.14.3
|
||||
Binary file not shown.
Binary file not shown.
10
.idea/.gitignore
generated
vendored
10
.idea/.gitignore
generated
vendored
@@ -1,10 +0,0 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# 쿼리 파일을 포함한 무시된 디폴트 폴더
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
dap-admin
|
||||
153
.idea/compiler.xml
generated
153
.idea/compiler.xml
generated
@@ -1,153 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<option name="mapstruct.defaultComponentModel" value="spring" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.mapstruct/mapstruct-processor/1.6.3/52e345c907fbf173b376586c7f8b131d53fa5867/mapstruct-processor-1.6.3.jar" />
|
||||
</processorPath>
|
||||
<module name="dap-admin.dat-was-pro.main" />
|
||||
<module name="dap-admin.dat-was-cus.main" />
|
||||
<module name="dap-admin.dat-was-sys.main" />
|
||||
<module name="dap-admin.dat-was-lib.main" />
|
||||
<module name="dap-admin.dap-tool-report.main" />
|
||||
<module name="dap-admin.dat-gateway.main" />
|
||||
<module name="dap-admin.dat-was-sal.main" />
|
||||
</profile>
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<option name="mapstruct.defaultComponentModel" value="spring" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
|
||||
</processorPath>
|
||||
<module name="dap-admin.dat-was-cus.test" />
|
||||
<module name="dap-admin.dat-was-sal.test" />
|
||||
<module name="dap-admin.dap-tool-report.test" />
|
||||
<module name="dap-admin.dat-was-sys.test" />
|
||||
<module name="dap-admin.dat-gateway.test" />
|
||||
<module name="dap-admin.dat-was-pro.test" />
|
||||
<module name="dap-admin.dat-was-lib.test" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="axhub-admin.axhub-common" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-common.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-common.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-email.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-hr.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-other.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-payment.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="axhub-admin.axhub-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin" options="-parameters" />
|
||||
<module name="dap-admin.dap-common" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-common.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-common.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-email.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-hr.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-other.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-payment.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dap-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-cus.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-lib.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-pro.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sal.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.dat-was-sys.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dap-admin.main" options="-parameters" />
|
||||
<module name="dap-admin.test" options="-parameters" />
|
||||
<module name="dat-admin.dat-gateway" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-gateway.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-gateway.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-tool-report.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-cus.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-lib.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-pro.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sal.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="dat-admin.dat-was-sys.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-core.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-oth.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms.main" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
<module name="io.shinhanlife.dat-admin.dap-tool-sms.test" options="-parameters -Amapstruct.defaultComponentModel=spring" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
16
.idea/dataSources.local.xml
generated
16
.idea/dataSources.local.xml
generated
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
|
||||
<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" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<auth-provider>no-auth</auth-provider>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/dataSources.xml
generated
17
.idea/dataSources.xml
generated
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="MCP_Redis" uuid="e04b0a95-55ed-4687-8007-98dd44e0ab05">
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://localhost:6379/0</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
25
.idea/dataSources/data_sources_history.xml
generated
25
.idea/dataSources/data_sources_history.xml
generated
@@ -1,25 +0,0 @@
|
||||
<DataSourcesHistory>
|
||||
<DataSourceFromHistory isRemovedFromProject="false">
|
||||
<data-source source="LOCAL" 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" />
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://localhost:6379/0</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<auth-provider>no-auth</auth-provider>
|
||||
<schema-mapping>
|
||||
<introspection-scope>
|
||||
<node kind="schema" qname="@" />
|
||||
</introspection-scope>
|
||||
</schema-mapping>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</DataSourceFromHistory>
|
||||
</DataSourcesHistory>
|
||||
6
.idea/encodings.xml
generated
6
.idea/encodings.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
23
.idea/gradle.xml
generated
23
.idea/gradle.xml
generated
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/dat-gateway" />
|
||||
<option value="$PROJECT_DIR$/dap-tool-report" />
|
||||
<option value="$PROJECT_DIR$/dat-was-cus" />
|
||||
<option value="$PROJECT_DIR$/dat-was-lib" />
|
||||
<option value="$PROJECT_DIR$/dat-was-pro" />
|
||||
<option value="$PROJECT_DIR$/dat-was-sal" />
|
||||
<option value="$PROJECT_DIR$/dat-was-sys" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
20
.idea/jarRepositories.xml
generated
20
.idea/jarRepositories.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/misc.xml
generated
17
.idea/misc.xml
generated
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/modules.xml
generated
17
.idea/modules.xml
generated
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<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-gateway/dap-admin.dat-gateway.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-gateway/dap-admin.dat-gateway.test.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-lib/dap-admin.dat-was-lib.test.iml" filepath="$PROJECT_DIR$/.idea/modules/dat-was-lib/dap-admin.dat-was-lib.test.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/test">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-core/build/generated/sources/annotationProcessor/java/test" isTestSource="true" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-oth/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-oth/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../dap-tool-sms/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../dap-tool-sms/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
868
.idea/workspace.xml
generated
868
.idea/workspace.xml
generated
@@ -1,868 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="f2db61ef-4338-4148-81d8-f16ac2f70318" name="ë³€ê²? comment="log 경로 ?˜ì •" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="EmbeddingIndexingInfo">
|
||||
<option name="cachedIndexableFilesCount" value="477" />
|
||||
</component>
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="ExternalProjectsManager">
|
||||
<system id="GRADLE">
|
||||
<state>
|
||||
<task path="$PROJECT_DIR$">
|
||||
<activation />
|
||||
</task>
|
||||
<projects_view>
|
||||
<tree_state>
|
||||
<expand />
|
||||
<select />
|
||||
</tree_state>
|
||||
</projects_view>
|
||||
</state>
|
||||
</system>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="feature/khs_0708" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory">{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "jadekimhs-creator"
|
||||
}
|
||||
}</component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "https://github.com/jadekimhs-creator/axhub-backend-main.git",
|
||||
"accountId": "d14f3549-59c6-4914-b2fc-96612b565c83"
|
||||
}
|
||||
}</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/axhub-tool-core/src/main/java/io/shinhanlife/axhub/biz/mcp/adapter/test/MockEimsHttpServer.java" root0="SKIP_INSPECTION" />
|
||||
</component>
|
||||
<component name="HttpClientOnboardingState">{
|
||||
"isOnboardingCommentShown": true
|
||||
}</component>
|
||||
<component name="KubernetesApiPersistence">{}</component>
|
||||
<component name="KubernetesApiProvider">{
|
||||
"isMigrated": true
|
||||
}</component>
|
||||
<component name="MavenImportPreferences">
|
||||
<option name="generalSettings">
|
||||
<MavenGeneralSettings>
|
||||
<option name="mavenHomeTypeForPersistence" value="WRAPPER" />
|
||||
</MavenGeneralSettings>
|
||||
</option>
|
||||
</component>
|
||||
<component name="McpProjectServerCommands">
|
||||
<commands />
|
||||
<urls />
|
||||
</component>
|
||||
<component name="NextEditCompletionFeaturesState">
|
||||
<decayedCancelled>
|
||||
<entry key="MS100" value="1.0" />
|
||||
<entry key="MS500" value="1.0" />
|
||||
<entry key="S2" value="1.0" />
|
||||
<entry key="S5" value="1.0" />
|
||||
<entry key="S10" value="1.0" />
|
||||
<entry key="S30" value="1.0" />
|
||||
<entry key="S60" value="1.0" />
|
||||
<entry key="M2" value="1.0" />
|
||||
<entry key="M5" value="1.0000004063583507" />
|
||||
<entry key="M10" value="1.0014248804304775" />
|
||||
<entry key="M15" value="1.0216507258806833" />
|
||||
<entry key="M30" value="1.3290052443235425" />
|
||||
<entry key="H1" value="2.282572068736398" />
|
||||
<entry key="H2" value="3.532355821946708" />
|
||||
<entry key="H4" value="4.558341371394377" />
|
||||
<entry key="D1" value="9.094716752814406" />
|
||||
<entry key="W1" value="70.60325442196253" />
|
||||
</decayedCancelled>
|
||||
<decayedSelected>
|
||||
<entry key="MS100" value="0.0" />
|
||||
<entry key="MS500" value="0.0" />
|
||||
<entry key="S2" value="0.0" />
|
||||
<entry key="S5" value="0.0" />
|
||||
<entry key="S10" value="0.0" />
|
||||
<entry key="S30" value="0.0" />
|
||||
<entry key="S60" value="0.0" />
|
||||
<entry key="M2" value="0.0" />
|
||||
<entry key="M5" value="4.0E-323" />
|
||||
<entry key="M10" value="1.1150340305311683E-161" />
|
||||
<entry key="M15" value="7.191589989208289E-108" />
|
||||
<entry key="M30" value="4.642712610093428E-54" />
|
||||
<entry key="H1" value="3.731613880427818E-27" />
|
||||
<entry key="H2" value="1.0580265938924739E-13" />
|
||||
<entry key="H4" value="5.633917287885551E-7" />
|
||||
<entry key="D1" value="0.2594845425222435" />
|
||||
<entry key="W1" value="3.6444939982225684" />
|
||||
</decayedSelected>
|
||||
<decayedShown>
|
||||
<entry key="MS100" value="0.0018350053695586157" />
|
||||
<entry key="MS500" value="0.2836138712339885" />
|
||||
<entry key="S2" value="0.729763044896567" />
|
||||
<entry key="S5" value="0.881602365474992" />
|
||||
<entry key="S10" value="0.9389368272013789" />
|
||||
<entry key="S30" value="0.979216654035547" />
|
||||
<entry key="S60" value="0.9895537651060436" />
|
||||
<entry key="M2" value="0.9947631703606863" />
|
||||
<entry key="M5" value="0.9979023743437856" />
|
||||
<entry key="M10" value="1.00037529616311" />
|
||||
<entry key="M15" value="1.0209507142357308" />
|
||||
<entry key="M30" value="1.328653905977673" />
|
||||
<entry key="H1" value="2.2823944051974228" />
|
||||
<entry key="H2" value="3.532265686879452" />
|
||||
<entry key="H4" value="4.558296332164408" />
|
||||
<entry key="D1" value="9.354155020042933" />
|
||||
<entry key="W1" value="74.24764635087368" />
|
||||
</decayedShown>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 0,
|
||||
"fromUser": false
|
||||
}</component>
|
||||
<component name="ProjectId" id="3Fz4p4oVzuFdzoWCnvW5rfH4F4v" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"Docker.docker-compose.yml.gateway: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml.tool-sms: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock, tool-hr]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Docker.docker-compose.yml[tool-sms, tool-email, gateway, tool-other]: Compose ë°°í<C2B0>¬.executor": "Run",
|
||||
"Gradle.SampleMciMapperTest.testAiToMciMapping.executor": "Debug",
|
||||
"Gradle.ShinhanMciSenderTest.testJsonUnwrapped.executor": "Run",
|
||||
"Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage].executor": "Run",
|
||||
"Gradle.axhub-backend-main [:axhub-gateway:build].executor": "Run",
|
||||
"Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;].executor": "Run",
|
||||
"Gradle.axhub-backend-main [clean build -x test].executor": "Run",
|
||||
"Gradle.axhub-backend-main 빌드.executor": "Run",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"Notification.DisplayName-DoNotAsk-DatabaseConfigFileWatcher.found": "???…ë<E280A6>°?´íЏ???°ì<C2B0>´?°ë² ?´ìФ 구성??발견?˜ì—ˆ?µë‹ˆ??quot;,
|
||||
"Notification.DoNotAsk-DatabaseConfigFileWatcher.found": "true",
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"Spring Boot.AxHubAdminApplication.executor": "Debug",
|
||||
"Spring Boot.AxHubGatewayApplication (1).executor": "Run",
|
||||
"Spring Boot.AxHubGatewayApplication.executor": "Run",
|
||||
"Spring Boot.AxHubToolEmailApplication.executor": "Debug",
|
||||
"Spring Boot.AxHubToolOtherApplication.executor": "Run",
|
||||
"Spring Boot.AxHubToolSmsApplication.executor": "Debug",
|
||||
"Spring Boot.HrToolApplication.executor": "Debug",
|
||||
"Spring Boot.PaymentToolApplication.executor": "Debug",
|
||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||
"git-widget-placeholder": "feature/mcp-refactoring",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "모듈",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.0",
|
||||
"run.configurations.included.in.services": "true",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"to.speed.mode.migration.done": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"redis"
|
||||
]
|
||||
}
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyClassDialog.RECENTS_KEY">
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.service" />
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.sms.service" />
|
||||
<recent name="io.shinhanlife.axhub.biz.mcp.tool.mapper" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunDashboard">
|
||||
<option name="configurationTypes">
|
||||
<set>
|
||||
<option value="KtorApplicationConfigurationType" />
|
||||
<option value="MicronautRunConfigurationType" />
|
||||
<option value="QuarkusRunConfigurationType" />
|
||||
<option value="SpringBootApplicationConfigurationType" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
<component name="RunManager" selected="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬">
|
||||
<configuration name="SampleMciMapperTest.testAiToMciMapping" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-tool-core:test" />
|
||||
<option value="--tests" />
|
||||
<option value=""io.shinhanlife.axhub.biz.mcp.sample.mapper.SampleMciMapperTest.testAiToMciMapping"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="ShinhanMciSenderTest.testJsonUnwrapped" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-tool-core:test" />
|
||||
<option value="--tests" />
|
||||
<option value=""io.shinhanlife.axhub.biz.mcp.adapter.sender.ShinhanMciSenderTest.testJsonUnwrapped"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="axhub-backend-main [:axhub-gateway:bootBuildImage]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":axhub-gateway:bootBuildImage" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="-PmainClass=io.shinhanlife.AxHubToolApplication" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="bootRun" />
|
||||
<option value="--args=&quot;--server.port=8082&quot;" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="AxHubGatewayApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dat-gateway.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcg.AxHubGatewayApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolEmailApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-email.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.email.AxHubToolEmailApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolOtherApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-other.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.other.AxHubToolOtherApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="AxHubToolSmsApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-sms.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.sms.AxHubToolSmsApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="PaymentToolApplication (1)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<module name="dap-admin.dap-tool-payment.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="io.shinhanlife.dat.mcc.payment.PaymentToolApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="docker-deploy" factoryName="docker-compose.yml" temporary="true">
|
||||
<deployment type="docker-compose.yml">
|
||||
<settings />
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" type="docker-deploy" factoryName="docker-compose.yml" temporary="true" server-name="Docker">
|
||||
<deployment type="docker-compose.yml">
|
||||
<settings>
|
||||
<option name="commandLineOptions" value="--build" />
|
||||
<option name="services">
|
||||
<list>
|
||||
<option value="gateway" />
|
||||
<option value="redis" />
|
||||
<option value="tool-email" />
|
||||
<option value="tool-other" />
|
||||
<option value="tool-payment" />
|
||||
<option value="tool-sms" />
|
||||
<option value="mci-mock" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2">
|
||||
<option name="Gradle.BeforeRunTask" enabled="false" tasks="clean build -x test" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" />
|
||||
<item itemvalue="Gradle.ShinhanMciSenderTest.testJsonUnwrapped" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage]" />
|
||||
<item itemvalue="Gradle.SampleMciMapperTest.testAiToMciMapping" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" />
|
||||
<item itemvalue="Spring Boot.AxHubGatewayApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolEmailApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolOtherApplication (1)" />
|
||||
<item itemvalue="Spring Boot.AxHubToolSmsApplication (1)" />
|
||||
<item itemvalue="Spring Boot.PaymentToolApplication (1)" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Docker.docker-compose.yml[gateway, redis, tool-email, tool-other, tool-payment, tool-sms, mci-mock]: Compose ë°°í<C2B0>¬" />
|
||||
<item itemvalue="Gradle.ShinhanMciSenderTest.testJsonUnwrapped" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [:axhub-gateway:bootBuildImage]" />
|
||||
<item itemvalue="Gradle.SampleMciMapperTest.testAiToMciMapping" />
|
||||
<item itemvalue="Gradle.axhub-backend-main [bootRun --args=&quot;--server.port=8082&quot;]" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.25134.95" />
|
||||
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.25134.95" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="StructureViewState">
|
||||
<option name="selectedTab" value="?¼ë¦¬" />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="?”í<E2809D>´???‘ì—…">
|
||||
<changelist id="f2db61ef-4338-4148-81d8-f16ac2f70318" name="변� comment="" />
|
||||
<created>1783064745410</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1783064745410</updated>
|
||||
<workItem from="1783064746565" duration="3558000" />
|
||||
<workItem from="1783295622537" duration="82000" />
|
||||
<workItem from="1783296277574" duration="21824000" />
|
||||
<workItem from="1783381712525" duration="17941000" />
|
||||
<workItem from="1783468449372" duration="71000" />
|
||||
<workItem from="1783468566397" duration="22445000" />
|
||||
<workItem from="1783554872446" duration="40241000" />
|
||||
<workItem from="1783673983621" duration="5000" />
|
||||
<workItem from="1783900184231" duration="22468000" />
|
||||
<workItem from="1783988452900" duration="11918000" />
|
||||
<workItem from="1784003561991" duration="2555000" />
|
||||
<workItem from="1784007005258" duration="1025000" />
|
||||
<workItem from="1784008697772" duration="917000" />
|
||||
<workItem from="1784009797198" duration="27131000" />
|
||||
<workItem from="1784159561976" duration="18127000" />
|
||||
<workItem from="1784187067292" duration="1473000" />
|
||||
<workItem from="1784189910429" duration="11107000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="index.html ?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783316153458</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783316153458</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783326414753</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783326414753</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="?˜í”Œ ?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783476059190</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783476059191</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783563177105</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783563177105</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="hr ?„로?<3F>트 ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783910068516</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783910068516</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="hr ?„로?<3F>트 ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783910259418</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783910259418</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="?´ëª¨ì§€ py ?Œì<C592>¼ ?? œ">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783927746213</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783927746213</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931448779</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931448779</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931684286</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931684286</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="?ŒìФ ?˜ì • ?„료">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783931990807</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783931990807</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00011" summary="보통 금융ê¶?? 한?¼ì<C2BC>´?????€??ë³´í—˜?????´ë? ?ˆê±°???œìФ??>
|
||||
<option name="closed" value="true" />
|
||||
<created>1783992292399</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783992292399</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783994134409</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783994134409</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783995434263</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783995434263</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00014" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784001064429</created>
|
||||
<option name="number" value="00014" />
|
||||
<option name="presentableId" value="LOCAL-00014" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784001064429</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00015" summary="?ŒìФ ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784094367009</created>
|
||||
<option name="number" value="00015" />
|
||||
<option name="presentableId" value="LOCAL-00015" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784094367009</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00016" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784098815515</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784098815515</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00017" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784105860688</created>
|
||||
<option name="number" value="00017" />
|
||||
<option name="presentableId" value="LOCAL-00017" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784105860688</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00018" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784161174384</created>
|
||||
<option name="number" value="00018" />
|
||||
<option name="presentableId" value="LOCAL-00018" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784161174384</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00019" summary="?˜ì • ë²„ì „">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784191131041</created>
|
||||
<option name="number" value="00019" />
|
||||
<option name="presentableId" value="LOCAL-00019" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784191131041</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00020" summary="?˜ì • ë²„ì „111">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784191828823</created>
|
||||
<option name="number" value="00020" />
|
||||
<option name="presentableId" value="LOCAL-00020" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784191828823</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00021" summary="?˜ì • ë²„ì „111">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784192641209</created>
|
||||
<option name="number" value="00021" />
|
||||
<option name="presentableId" value="LOCAL-00021" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784192641209</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00022" summary="log 경로 ?˜ì •">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784505706146</created>
|
||||
<option name="number" value="00022" />
|
||||
<option name="presentableId" value="LOCAL-00022" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784505706146</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="23" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="OPEN_GENERIC_TABS">
|
||||
<map>
|
||||
<entry key="2c2de5f9-e5f5-4565-9d3d-1bc8a056e08f" value="TOOL_WINDOW" />
|
||||
<entry key="39879709-a44e-4eae-b130-7a588c5a5cd8" value="TOOL_WINDOW" />
|
||||
<entry key="4b40888d-9986-4854-8984-53a06ca83373" value="TOOL_WINDOW" />
|
||||
<entry key="7bdac3ab-6a4e-4e39-a92b-4a2c5ea226bd" value="TOOL_WINDOW" />
|
||||
<entry key="26d7f06e-58b7-469d-946b-8ab4827b57da" value="TOOL_WINDOW" />
|
||||
<entry key="efe781d4-6494-48db-a04e-65359ea237cd" value="TOOL_WINDOW" />
|
||||
<entry key="860d5318-a419-41ba-a3c6-f009a6f61676" value="TOOL_WINDOW" />
|
||||
<entry key="57842608-e2e0-4925-a066-a4c097b4b0ad" value="TOOL_WINDOW" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="26d7f06e-58b7-469d-946b-8ab4827b57da">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="2c2de5f9-e5f5-4565-9d3d-1bc8a056e08f">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="39879709-a44e-4eae-b130-7a588c5a5cd8">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="4b40888d-9986-4854-8984-53a06ca83373">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="57842608-e2e0-4925-a066-a4c097b4b0ad">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="7bdac3ab-6a4e-4e39-a92b-4a2c5ea226bd">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="860d5318-a419-41ba-a3c6-f009a6f61676">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="efe781d4-6494-48db-a04e-65359ea237cd">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="HEAD" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="roots">
|
||||
<value>
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="index.html ?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="?˜í”Œ ?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="hr ?„로?<3F>트 ?? œ" />
|
||||
<MESSAGE value="?´ëª¨ì§€ py ?Œì<C592>¼ ?? œ" />
|
||||
<MESSAGE value="?ŒìФ ?˜ì • ?„료" />
|
||||
<MESSAGE value="보통 금융ê¶?? 한?¼ì<C2BC>´?????€??ë³´í—˜?????´ë? ?ˆê±°???œìФ?? />
|
||||
<MESSAGE value="?ŒìФ ?˜ì •" />
|
||||
<MESSAGE value="?˜ì • ë²„ì „" />
|
||||
<MESSAGE value="?˜ì • ë²„ì „111" />
|
||||
<MESSAGE value="log 경로 ?˜ì •" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="log 경로 ?˜ì •" />
|
||||
</component>
|
||||
</project>
|
||||
103
AddJavadoc.java
Normal file
103
AddJavadoc.java
Normal file
@@ -0,0 +1,103 @@
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
public class AddJavadoc {
|
||||
|
||||
static final String ROOT_DIR = ".";
|
||||
|
||||
static final String TEMPLATE =
|
||||
"/**\n" +
|
||||
" * @package %s\n" +
|
||||
" * @className %s\n" +
|
||||
" * @description AX HUB ?쒖뒪??泥섎━ ?대옒??n" +
|
||||
" * @author 源?뺤떇\n" +
|
||||
" * @create 2026.09.01\n" +
|
||||
" * <pre>\n" +
|
||||
" * ---------- 媛쒖젙?대젰 ----------\n" +
|
||||
" * ?섏젙?? ?섏젙?? ?섏젙?댁슜\n" +
|
||||
" * ---------- -------- ---------------------------\n" +
|
||||
" * 2026.09.01 源?뺤떇 理쒖큹?앹꽦\n" +
|
||||
" * \n" +
|
||||
" * </pre>\n" +
|
||||
" */";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Files.walkFileTree(Paths.get(ROOT_DIR), new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
|
||||
String name = dir.getFileName().toString();
|
||||
if (name.equals(".git") || name.equals("build") || name.equals(".gradle") || name.equals("scratch") || name.equals("out") || name.equals("bin")) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (file.toString().endsWith(".java") && !file.getFileName().toString().equals("AddJavadoc.java")) {
|
||||
processJavaFile(file);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
System.out.println("Done.");
|
||||
}
|
||||
|
||||
private static void processJavaFile(Path file) throws IOException {
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
String content = String.join("\n", lines);
|
||||
|
||||
if (content.contains("---------- 媛쒖젙?대젰 ----------") || content.contains("@className")) {
|
||||
System.out.println("Skipping (already has javadoc): " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = "unknown";
|
||||
String className = "unknown";
|
||||
|
||||
Matcher pkgMatcher = Pattern.compile("(?m)^\\s*package\\s+([\\w\\.]+)\\s*;").matcher(content);
|
||||
if (pkgMatcher.find()) {
|
||||
packageName = pkgMatcher.group(1);
|
||||
}
|
||||
|
||||
int classDeclIdx = -1;
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
Matcher classMatcher = Pattern.compile("^\\s*(?:public\\s+|protected\\s+|private\\s+|abstract\\s+|final\\s+|static\\s+)*(class|interface|enum|record)\\s+(\\w+)").matcher(line);
|
||||
if (classMatcher.find()) {
|
||||
classDeclIdx = i;
|
||||
className = classMatcher.group(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (classDeclIdx == -1) {
|
||||
System.out.println("Skipping (no class declaration found): " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
int insertIdx = classDeclIdx;
|
||||
for (int i = classDeclIdx - 1; i >= 0; i--) {
|
||||
String line = lines.get(i).trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("@")) {
|
||||
insertIdx = i;
|
||||
} else if (line.startsWith("//") || line.startsWith("/*") || line.startsWith("*")) {
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String javadoc = String.format(TEMPLATE, packageName, className);
|
||||
lines.add(insertIdx, javadoc);
|
||||
|
||||
Files.write(file, String.join("\n", lines).getBytes("UTF-8"));
|
||||
System.out.println("Updated: " + file);
|
||||
}
|
||||
}
|
||||
39
Dockerfile
39
Dockerfile
@@ -1,50 +1,31 @@
|
||||
# 1. 빌드 환경 (JDK 21)
|
||||
# -----------------------------------------------------------------------------
|
||||
# 외부 인터넷이 차단된 내부망에서는 Docker Hub 대신 사내 Container Registry의
|
||||
# Java 21 이미지를 사용합니다. 인프라 담당자에게 이미지의 전체 경로와 태그를
|
||||
# 받은 후 아래 FROM 행만 교체합니다.
|
||||
#
|
||||
# 예: FROM registry.shinhanlife.co.kr/base/openjdk:21 AS builder
|
||||
#
|
||||
# 이 단계는 Gradle 빌드와 Java 컴파일을 수행하므로 반드시 JDK 21 이미지여야 합니다.
|
||||
# 사내 이미지가 실제로 JDK 21인지 다음 명령으로 확인합니다.
|
||||
# docker run --rm <사내-JDK-이미지> java -version
|
||||
# 1. ??<3F><><EFBFBD>???<3F>꼍 (JDK 21)
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Gradle Wrapper와 소스 파일을 복사합니다.
|
||||
# Gradle ??<3F>띁?? ???<3F><> 蹂듭<E8B982>?
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY build.gradle settings.gradle ./
|
||||
COPY src src
|
||||
|
||||
# Wrapper 실행 권한을 부여하고 테스트를 제외한 빌드를 수행합니다.
|
||||
# 沅뚰<EFBFBD>??<3F>??<3F>???<3F><><EFBFBD>?(???<3F><>????<3F>쇅)
|
||||
RUN chmod +x gradlew
|
||||
RUN ./gradlew clean build -x test
|
||||
|
||||
# 2. 실행 환경 (JRE 21)
|
||||
# -----------------------------------------------------------------------------
|
||||
# 실행 단계는 JRE 21 이미지가 가장 가볍지만, 사내에서 JDK 21 이미지만 제공하는
|
||||
# 경우에는 동일한 JDK 21 이미지를 사용해도 정상 동작합니다.
|
||||
#
|
||||
# 예: FROM registry.shinhanlife.co.kr/base/openjre:21
|
||||
# 예: FROM registry.shinhanlife.co.kr/base/openjdk:21
|
||||
#
|
||||
# 사내 제공 이미지의 기반 OS를 확인합니다.
|
||||
# - Alpine 기반: 아래 apk 명령을 그대로 사용합니다.
|
||||
# - Ubuntu/Debian 기반: apk 대신 apt-get update && apt-get install -y tzdata를 사용합니다.
|
||||
# 2. ??<3F>뻾 ??<3F>꼍 (JRE 21)
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# 서울 시간대를 설정합니다.
|
||||
# ???꾩<>???<3F>젙 (??<3F>뎅 ??<3F>컙)
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
|
||||
# 빌드 단계에서 생성한 애플리케이션 JAR를 복사합니다.
|
||||
# ??<3F><><EFBFBD>??JAR ???<3F><> 蹂듭<E8B982>?
|
||||
COPY --from=builder /app/build/libs/*.jar app.jar
|
||||
|
||||
# 애플리케이션 포트를 노출합니다.
|
||||
# 湲곕???????몄텧
|
||||
EXPOSE 8081
|
||||
|
||||
# 애플리케이션을 실행합니다.
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
# ?<3F>⑦???<3F><>???<3F>뻾 ??JAR ??<3F>뻾
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
|
||||
57
McpBridge.java
Normal file
57
McpBridge.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @package io.shinhanlife
|
||||
* @className McpBridge
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
|
||||
public class McpBridge {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
// Spring AI MCP Server??怨듭떇 ?⑥씪 ?붾뱶?ъ씤??
|
||||
.uri(URI.create("http://localhost:8081/mcp"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(line))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.body());
|
||||
System.out.flush();
|
||||
} catch (Exception e) {
|
||||
// MCP ?쒖? ?먮윭 ?щ㎎?쇰줈 諛섑솚 (?꾩쓽濡?id 異붿텧 ?쒖쇅, 理쒖냼?쒖쓽 ?먮윭 ?묐떟)
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}}");
|
||||
System.out.flush();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
842
README.md
842
README.md
@@ -1,362 +1,606 @@
|
||||
# AX HUB MCP Tool Platform
|
||||
# AX HUB MCP Tool
|
||||
|
||||
## Current Tool Pod modules
|
||||
신한라이프 업무 시스템과 AI Agent를 연결하는 MCP(Model Context Protocol) Gateway 및 Tool 서버 프로젝트입니다.
|
||||
|
||||
| Pod role | Gradle module | Container service | Internal endpoint | Local Docker endpoint |
|
||||
|---|---|---|---|---|
|
||||
| Customer/common integration | `dat-was-cus` | `was-cus` | `http://was-cus:8084/mcp` | `http://localhost:8284/mcp` |
|
||||
| Sales/notification integration | `dat-was-sal` | `was-sal` | `http://was-sal:8082/mcp` | `http://localhost:8282/mcp` |
|
||||
| Process integration (new) | `dat-was-pro` | `was-pro` | `http://was-pro:8085/mcp` | `http://localhost:8085/mcp` |
|
||||
| System integration (new) | `dat-was-sys` | `was-sys` | `http://was-sys:8086/mcp` | `http://localhost:8086/mcp` |
|
||||
Agent는 Gateway에서 Tool 목록과 입력 명세를 받고, Gateway는 권한과 정책을 확인한 뒤 Tool 서버로 요청을 전달합니다. 업무 Tool은 `DTO → UseCase → Converter → MCI/EAI Client` 구조로 레거시 시스템을 호출합니다.
|
||||
|
||||
- The former `dat-was-oth` module is now `dat-was-cus`; the former `dat-was-sms` module is now `dat-was-sal`.
|
||||
- This is a deployment Pod rename only. Existing tool category and function names such as `oth_*` and `sms_*` remain valid so already registered MCP clients are not broken.
|
||||
- `dat-was-pro` and `dat-was-sys` are empty, independently deployable Tool Pods ready for new business tools.
|
||||
|
||||
### Run the Tool Pods locally
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dat-was-cus:bootRun
|
||||
.\gradlew.bat :dat-was-sal:bootRun
|
||||
.\gradlew.bat :dat-was-pro:bootRun
|
||||
.\gradlew.bat :dat-was-sys:bootRun
|
||||
```
|
||||
|
||||
AX HUB에서 AI Agent가 업무 Tool을 검색하고 호출할 수 있도록 Gateway와 독립 Tool Pod를 제공하는 멀티 모듈 Spring Boot 프로젝트입니다.
|
||||
|
||||
## 1. 현재 구성
|
||||
## 전체 흐름
|
||||
|
||||
```text
|
||||
MCP Client / AI Agent
|
||||
|
|
||||
v
|
||||
Gateway (dat-gateway, 8081)
|
||||
- MCP 연결, Tool Registry, 인증·인가, 라우팅, 공통 화면
|
||||
|
|
||||
+-------------------------+
|
||||
| |
|
||||
v v
|
||||
Sales Tool Pod (dat-was-sal, 8082) Customer Tool Pod (dat-was-cus, 8084)
|
||||
- 영업/알림 업무 Tool - 고객/공통 업무 Tool
|
||||
- Tool Manifest - Tool Manifest
|
||||
- Pod Test Console - Pod Test Console
|
||||
|
|
||||
+--> Process Tool Pod (dat-was-pro, 8085)
|
||||
+--> System Tool Pod (dat-was-sys, 8086)
|
||||
|
|
||||
v
|
||||
MCI / EAI / EIMS 등 레거시 연계 대상
|
||||
AI Agent / MCP Client
|
||||
│
|
||||
▼
|
||||
MCP Gateway (dap-gateway)
|
||||
├─ Tool 목록·스키마 제공
|
||||
├─ Tool 권한·승인·가드레일 확인
|
||||
├─ Redis Registry 및 실행 추적
|
||||
└─ 대상 Tool 서버로 라우팅
|
||||
│
|
||||
▼
|
||||
Tool Server (dap-tool-sms / dap-tool-oth)
|
||||
└─ BusinessToolController
|
||||
│
|
||||
▼
|
||||
UseCase → Converter → MCI/EAI Client → 레거시 시스템
|
||||
```
|
||||
|
||||
Gateway는 외부 MCP 진입점과 Tool 등록·라우팅을 담당하고, 실제 업무 로직과 레거시 호출은 각 Tool Pod가 담당합니다. Tool Pod는 업무/조직 단위로 독립 배포할 수 있습니다.
|
||||
## 현재 구조와 목표 구조
|
||||
|
||||
## 2. 모듈
|
||||
현재는 Gateway와 두 개의 Tool 애플리케이션으로 구성됩니다.
|
||||
|
||||
| 모듈 | 역할 | 기본 포트 |
|
||||
```text
|
||||
현재: Gateway + SMS Tool Pod + OTH Tool Pod
|
||||
목표: Gateway + 고객 Pod + 영업 Pod + 지급/납입 Pod + 알림 Pod + 인사 Pod + 공통 Pod
|
||||
```
|
||||
|
||||
`dap-tool-oth`에는 여러 업무 카테고리가 함께 있습니다. AA 협의 후에는 부서·업무 소유권 단위로 Tool 서버, 이미지, Pod, 배포 파이프라인을 분리합니다. 이 목표 구조는 향후 전환 방향이며 현재 구현 완료 상태가 아닙니다.
|
||||
|
||||
## Gradle 멀티모듈
|
||||
|
||||
| 모듈 | 역할 | 실행 포트 |
|
||||
|---|---|---:|
|
||||
| `dat-gateway` | MCP Gateway, Registry, 라우팅, Chat·Catalog·Tester·Scaffold 화면 | 8081 |
|
||||
| `dat-was-lib` | Tool 공통 라이브러리: 어노테이션, Schema, Manifest, MCI/EAI 어댑터, 공통 Web·보안 기능 | - |
|
||||
| `dat-was-sal` | 영업/알림 업무 Tool Pod | 8082 |
|
||||
| `dat-was-cus` | 고객/공통 업무 Tool Pod | 8084 |
|
||||
| `dat-was-pro` | 프로세스 업무 Tool Pod | 8085 |
|
||||
| `dat-was-sys` | 시스템 업무 Tool Pod | 8086 |
|
||||
| `dap-gateway` | MCP 진입점, Tool Registry, 라우팅, 권한·가드레일, Chat API | 8081 |
|
||||
| `dap-tool-core` | 공통 어노테이션, Controller, JSON Schema, MCI/EAI 지원, 보안·로깅 공통 기능 | 라이브러리 |
|
||||
| `dap-tool-sms` | SMS/알림 Tool 서버 | 8082 |
|
||||
| `dap-tool-oth` | 공통·업무·샘플·MCI Tool 서버 | 8084 |
|
||||
|
||||
> 기존 `dap-tool-core`, `dap-tool-sms`, `dap-tool-oth` 명칭은 현재 각각 `dat-was-lib`, `dat-was-sal`, `dat-was-cus`로 변경되었습니다.
|
||||
기술 기준은 Java 21, Spring Boot 4, Gradle, Spring AI MCP Server, Redis, MapStruct, MyBatis, Resilience4j입니다.
|
||||
|
||||
## 3. 개발 환경
|
||||
## 환경 (Environment)
|
||||
|
||||
| 항목 | 기준 |
|
||||
| 항목 | 버전 / 기준 |
|
||||
|---|---|
|
||||
| Java | 21 |
|
||||
| Spring Boot | 3.5.11 |
|
||||
| Gradle | 8.14.3 (Wrapper) |
|
||||
| Spring AI BOM | 1.1.8 |
|
||||
| 기본 프로필 | `local` |
|
||||
| Spring Boot | 4.0.5 |
|
||||
| Gradle Wrapper | 8.14.3 |
|
||||
| Spring AI BOM | 2.0.0 |
|
||||
| Spring AI MCP Server | `spring-ai-starter-mcp-server-webmvc` |
|
||||
| Redis Client | Lettuce 6.6.0.RELEASE |
|
||||
| Resilience | Resilience4j 2.2.0 |
|
||||
| MyBatis Spring Boot Starter | 3.0.3 |
|
||||
| MapStruct | 1.5.5.Final |
|
||||
| Lombok | 1.18.32 |
|
||||
| JSON Schema Validator | networknt 1.4.0 |
|
||||
| OpenAPI UI | springdoc 2.5.0 |
|
||||
| 컨테이너 실행 | Docker Compose |
|
||||
|
||||
로컬 환경에서는 민감한 값은 환경 변수로만 주입합니다.
|
||||
프로젝트는 JDK 21을 기준으로 컴파일됩니다. IntelliJ에서는 Project SDK, Gradle JVM, Run Configuration JRE를 모두 JDK 21로 맞춰야 합니다.
|
||||
## 5분 빠른 시작
|
||||
|
||||
Docker와 JDK 21이 준비된 로컬 개발 환경 기준입니다.
|
||||
|
||||
```powershell
|
||||
# 1. Redis와 MCI Mock만 먼저 실행
|
||||
$env:ACTIVE_PROFILE = 'local'
|
||||
docker compose up -d redis mci-mock
|
||||
|
||||
# 2. Gateway 실행 (새 PowerShell)
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
$env:OPENROUTER_API_KEY = '<개발용 비밀 저장소의 키>'
|
||||
.\gradlew.bat :dap-gateway:bootRun
|
||||
|
||||
# 3. OTH Tool 실행 (또 다른 PowerShell)
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
$env:AXHUB_GATEWAY_URL = 'http://localhost:8081'
|
||||
$env:AXHUB_TOOL_URL = 'http://localhost:8084'
|
||||
.\gradlew.bat :dap-tool-oth:bootRun
|
||||
```
|
||||
|
||||
Tool 서버가 기동된 뒤 아래 URL로 등록된 Tool 목록을 확인합니다.
|
||||
|
||||
```text
|
||||
http://localhost:8081/mcp/api/v1/tools/list
|
||||
```
|
||||
|
||||
SMS Tool도 함께 확인하려면 별도 PowerShell에서 아래 명령을 실행합니다.
|
||||
|
||||
```powershell
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
$env:OPENROUTER_API_KEY = '<발급받은-키>'
|
||||
$env:AXHUB_GATEWAY_URL = 'http://localhost:8081'
|
||||
$env:AXHUB_TOOL_URL = 'http://localhost:8082'
|
||||
.\gradlew.bat :dap-tool-sms:bootRun
|
||||
```
|
||||
|
||||
## 4. 실행
|
||||
|
||||
### 4.1 Gradle 로컬 실행
|
||||
|
||||
각 애플리케이션은 별도 터미널에서 실행합니다.
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dat-gateway:bootRun
|
||||
.\gradlew.bat :dat-was-sal:bootRun
|
||||
.\gradlew.bat :dat-was-cus:bootRun
|
||||
.\gradlew.bat :dat-was-pro:bootRun
|
||||
.\gradlew.bat :dat-was-sys:bootRun
|
||||
```
|
||||
|
||||
### 4.2 Docker Compose 실행
|
||||
전체 컨테이너 환경이 필요하면 개별 실행 대신 다음 한 줄을 사용합니다.
|
||||
|
||||
```powershell
|
||||
$env:ACTIVE_PROFILE = 'local'
|
||||
docker compose up -d --build
|
||||
```
|
||||
## Tool 등록과 실행
|
||||
|
||||
| 서비스 | 호스트 URL | 컨테이너 포트 |
|
||||
|---|---|---:|
|
||||
| Gateway | http://localhost:8281 | 8081 |
|
||||
| Sales Tool Pod | http://localhost:8282 | 8082 |
|
||||
| Customer Tool Pod | http://localhost:8284 | 8084 |
|
||||
| Process Tool Pod | http://localhost:8285 | 8085 |
|
||||
| System Tool Pod | http://localhost:8286 | 8086 |
|
||||
| MCI Mock | http://localhost:8089 | 8080 |
|
||||
| Dozzle 로그 화면 | http://localhost:8288 | 8080 |
|
||||
### 등록과 목록 제공
|
||||
|
||||
## 5. 화면과 운영 도구
|
||||
1. Tool 서버 기동 시 `ToolRegistryHeartbeatSender`가 `@McpTool`, `@McpFunction`을 스캔합니다.
|
||||
2. Tool 이름, 설명, 입력 JSON Schema, `categoryKey`, 연동 방식, 실행 URL을 메타데이터로 생성합니다.
|
||||
3. Gateway Redis Registry에 등록·Heartbeat 정보를 전송합니다.
|
||||
4. Agent와 관리 화면은 Gateway에서 Tool 목록과 명세를 조회합니다.
|
||||
|
||||
### Gateway 화면
|
||||
### 실행
|
||||
|
||||
| 화면 | 로컬 URL | 용도 |
|
||||
1. Agent가 Gateway에 Tool 이름과 입력값을 보냅니다.
|
||||
2. Gateway가 Tool 존재 여부, 허용 Tool, 쓰기 승인, 가드레일을 확인합니다.
|
||||
3. Gateway가 Tool 서버의 `/mcp/{toolName}`으로 요청을 전달합니다.
|
||||
4. `BusinessToolController`가 Tool 메서드를 찾아 DTO로 변환하고 JSON Schema를 검증합니다.
|
||||
5. UseCase가 업무 흐름을 수행합니다.
|
||||
6. Converter가 업무 DTO를 인터페이스 ID 기반 MCI 요청 DTO로 변환합니다.
|
||||
7. MCI/EAI Client가 레거시를 호출하고 결과를 Tool 응답으로 반환합니다.
|
||||
|
||||
## 주요 URL
|
||||
|
||||
로컬에서 Gateway를 직접 실행할 때의 기준입니다. Docker Compose를 사용하면 Gateway 호스트 포트는 `8281`입니다.
|
||||
|
||||
| 용도 | 메서드 | URL |
|
||||
|---|---|---|
|
||||
| 메인 | http://localhost:8081/index.html | 플랫폼 진입 화면 |
|
||||
| Catalog | http://localhost:8081/catalog.html | 등록 Tool 탐색 |
|
||||
| Playground | http://localhost:8081/playground.html | 단건 Tool 호출 확인 |
|
||||
| Chat | http://localhost:8081/chat.html | Agent 기반 대화형 호출 |
|
||||
| Auto Tester | http://localhost:8081/tester.html | Gateway 기준 다중 Tool 스모크/회귀 테스트 |
|
||||
| Scaffold | http://localhost:8081/admin/scaffold.html | Pod·Tool 소스 생성 지원 |
|
||||
| Tool 목록 | `GET` | `http://localhost:8081/mcp/api/v1/tools/list` |
|
||||
| Tool 실행 | `POST` | `http://localhost:8081/mcp/api/v1/tools/call` |
|
||||
| Tool Markdown 문서 | `GET` | `http://localhost:8081/mcp/api/v1/tools/docs/markdown` |
|
||||
| 카테고리별 SSE MCP 연결 | `GET` | `http://localhost:8081/mcp/sse/{categoryKey}` |
|
||||
| 카테고리별 MCP 호출 채널 | `POST` | `http://localhost:8081/mcp/custom/{categoryKey}` |
|
||||
| Chat 스트리밍 API | `POST` | `http://localhost:8081/api/chat` |
|
||||
| Scaffold API | `POST` | `http://localhost:8081/api/v1/scaffold/pod` 또는 `/tool` |
|
||||
|
||||
Docker Compose로 실행한 경우 Gateway 화면은 포트 `8281`을 사용합니다.
|
||||
`/mcp/sse/{categoryKey}`는 SSE 연결을 여는 전송 경로이고, `/mcp/custom/{categoryKey}`는 같은 카테고리의 MCP 요청을 처리하는 호출 경로입니다. 두 URL은 역할이 다릅니다.
|
||||
|
||||
### Pod Module New: 독립 프로젝트 생성
|
||||
## Tool 개발 표준
|
||||
|
||||
Scaffold 화면의 `Pod Module New` 탭은 기존 Pod Module 생성 흐름과 별도로 동작합니다. 새 모듈명과 포트, workspace 경로를 입력하면 해당 workspace 바로 아래에 독립 Tool Pod 프로젝트를 생성합니다.
|
||||
| 구성 요소 | 책임 |
|
||||
|---|---|
|
||||
| `XxxRequest`, `XxxResponse` | Agent/Tool 관점의 입력·응답 DTO |
|
||||
| `XxxUseCase` | Tool 계약과 MCP 메타데이터 선언 |
|
||||
| `XxxUseCaseImpl` | 업무 흐름 조합과 Client 호출 |
|
||||
| `XxxConverter` | 업무 DTO와 레거시 인터페이스 DTO 사이 변환 |
|
||||
| `MciXxxClient` | Glow/MCI 또는 EAI 통신 호출 |
|
||||
| `INTERFACE_ID_I`, `INTERFACE_ID_O` | 인터페이스 ID 기준 MCI 요청·응답 DTO |
|
||||
|
||||
기본 workspace는 `C:\eGovFrameDev-4.3.1-64bit\workspace`이며, 예를 들어 모듈명으로 `dat-was-payment`를 입력하면 `C:\eGovFrameDev-4.3.1-64bit\workspace\dat-was-payment` 프로젝트가 만들어집니다. 모듈명은 `dat-was-`로 시작해야 하며, 이미 같은 폴더가 있으면 덮어쓰지 않고 생성 요청을 거절합니다.
|
||||
### Tool 선언
|
||||
|
||||
생성된 프로젝트는 같은 workspace의 `dat-lib-datmt`를 Gradle composite build로 참조합니다. 생성 직후 프로젝트 폴더에서 다음 명령으로 컴파일할 수 있습니다.
|
||||
Tool 그룹에는 `@McpTool`, Agent가 호출하는 함수에는 `@McpFunction`을 사용합니다.
|
||||
|
||||
```powershell
|
||||
cd C:\eGovFrameDev-4.3.1-64bit\workspace\dat-was-payment
|
||||
.\gradlew.bat compileJava
|
||||
```
|
||||
```java
|
||||
@McpTool(routingType = "MCI", categoryKey = "claim")
|
||||
public interface ClaimInquiryUseCase {
|
||||
|
||||
이 기능은 기존 AX HUB 멀티 모듈 Pod나 기존 `Pod Module` 탭의 구성·소스를 수정하지 않습니다.
|
||||
|
||||
### Tool Pod Test Console
|
||||
|
||||
공통 정적 화면인 `tool-test-console.html`은 Tool Pod의 `/tool-manifest`를 읽어, 해당 Pod에 등록된 Tool과 `inputSchema`를 기준으로 요청 JSON을 만들어 직접 실행합니다.
|
||||
|
||||
| 대상 Pod | 로컬 URL | Docker Compose URL |
|
||||
|---|---|---|
|
||||
| Customer | http://localhost:8084/tool-test-console.html | http://localhost:8284/tool-test-console.html |
|
||||
| Sales | http://localhost:8082/tool-test-console.html | http://localhost:8282/tool-test-console.html |
|
||||
| Process | http://localhost:8085/tool-test-console.html | http://localhost:8285/tool-test-console.html |
|
||||
| System | http://localhost:8086/tool-test-console.html | http://localhost:8286/tool-test-console.html |
|
||||
|
||||
사용 방법은 다음과 같습니다.
|
||||
|
||||
1. 대상 Tool Pod의 Console에 접속합니다.
|
||||
2. Tool을 선택하고 `Schema 샘플 채우기`로 요청 JSON을 생성합니다.
|
||||
3. 업무에 맞는 값으로 보정한 뒤 실행합니다.
|
||||
4. 재사용할 요청은 `현재 요청 저장`으로 브라우저 `localStorage`에 저장합니다.
|
||||
|
||||
### Gateway Auto Tester
|
||||
|
||||
`tester.html`은 Gateway에 등록된 Tool을 대상으로 다음 기능을 제공합니다.
|
||||
|
||||
- Tool 목록 자동 조회 및 Tool별 실행
|
||||
- JSON Schema 기반 더미 요청 생성, 사용자 요청 JSON 저장
|
||||
- 필터된 Tool 일괄 실행 및 실패 Tool 재실행
|
||||
- 결과 차트, 실시간 실행 로그, CSV 내보내기, 호출 예제 복사
|
||||
- Tool별 자동 Ping(3초 간격) 및 Stress Test(현재 50건 동시 요청)
|
||||
|
||||
> Auto Tester의 더미 요청 성공은 **통신·등록·기본 스키마 확인용 스모크 테스트**입니다. 실제 MCI/EAI 업무 규칙, 권한, 데이터 정합성은 보장하지 않습니다. Stress Test와 Auto Ping은 실제 Tool·레거시 호출을 유발할 수 있으므로 개발/테스트 환경 및 승인된 테스트 데이터에서만 사용합니다.
|
||||
|
||||
UI에서 사용하는 Tailwind CSS와 Chart.js는 `dat-gateway/src/main/resources/static/lib`에 포함되어 있어, 화면 라이브러리 로딩을 위해 외부 CDN 연결이 필요하지 않습니다.
|
||||
|
||||
## 6. 주요 HTTP API
|
||||
|
||||
### Gateway API
|
||||
|
||||
| 기능 | Method | 경로 |
|
||||
|---|---|---|
|
||||
| 등록 Tool 목록 | GET | `/mcp/api/v1/tools/list` |
|
||||
| Gateway를 통한 Tool 호출 | POST | `/mcp/api/v1/tools/call` |
|
||||
| Tool 문서 Markdown | GET | `/mcp/api/v1/tools/docs/markdown` |
|
||||
| Tool 등록 | POST | `/mcp/api/v1/registry/register` |
|
||||
| Tool 해제 | POST | `/mcp/api/v1/registry/deregister` |
|
||||
| Heartbeat | POST | `/mcp/api/v1/registry/heartbeat` |
|
||||
|
||||
호출 본문은 JSON-RPC 형식을 사용합니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "cmm_claim_search",
|
||||
"arguments": {
|
||||
"claimNo": "CLM2026070100120"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
@McpFunction(
|
||||
name = "claim_inquiry",
|
||||
displayName = "보험금 청구 조회",
|
||||
description = "청구 번호로 보험금 청구 상태를 조회합니다.",
|
||||
mappingId = "CLM00000001"
|
||||
)
|
||||
ClaimInquiryResponse inquire(ClaimInquiryRequest request);
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Pod API
|
||||
`categoryKey`는 Tool의 업무 그룹입니다. Gateway의 목록 필터링, Agent 권한, 동적 MCP 서버 구분에 사용하므로 합의된 업무 키를 사용합니다.
|
||||
|
||||
| 기능 | Method | 경로 |
|
||||
|---|---|---|
|
||||
| 현재 Pod의 Tool Manifest | GET | `/tool-manifest` |
|
||||
| 현재 Pod의 로컬 Tool 목록 | GET | `/mcp/api/v1/tools/local` |
|
||||
| 현재 Pod에 직접 Tool 호출 | POST | `/mcp/{toolName}` |
|
||||
- `MCI`: 사내 MCI 인터페이스 호출
|
||||
- `EAI`: EAI 연동
|
||||
- `DIRECT`: 외부 HTTP 또는 내부 직접 연동
|
||||
|
||||
Agent나 외부 클라이언트의 표준 MCP 진입은 Gateway를 사용합니다. Pod 직접 호출은 개발·단위 테스트·Pod Console 용도로 사용합니다.
|
||||
### 변환 원칙
|
||||
|
||||
## 7. Tool 개발 규칙
|
||||
UseCase 구현체는 Tool 요청을 레거시 요청과 섞어 쓰지 않습니다. Converter에서 변환한 뒤 Client에 전달합니다.
|
||||
|
||||
### 이름
|
||||
```java
|
||||
@Override
|
||||
public ClaimInquiryResponse inquire(ClaimInquiryRequest request) {
|
||||
CLM00000001_I mciRequest = converter.toMciRequest(request);
|
||||
CLM00000001_O mciResponse = mciClmClient.callClm00000001(mciRequest);
|
||||
return converter.toResponse(mciResponse);
|
||||
}
|
||||
```
|
||||
|
||||
Tool 함수명은 아래 4단계 규칙을 사용합니다.
|
||||
MCI 입출력 객체는 업무 이름이 아니라 인터페이스 ID를 기준으로 둡니다.
|
||||
|
||||
```text
|
||||
pod_domain_service_action
|
||||
예: cmm_claim_search
|
||||
CLCNNB00001_I : CLCNNB00001 요청 DTO
|
||||
CLCNNB00001_O : CLCNNB00001 응답 DTO
|
||||
```
|
||||
|
||||
- `pod`: Tool Pod 식별자 (`oth`, `sms` 등)
|
||||
- `domain`: 업무 도메인 또는 `categoryKey` (`cmm`, `smp` 등)
|
||||
- `service`: 업무 서비스명
|
||||
- `action`: 동작 (`search`, `issue`, `send` 등)
|
||||
예를 들어 `Onnba3011Request`와 `CLCNNB00001_I`는 같은 업무 데이터를 담을 수 있지만 같은 객체가 아닙니다. 둘 사이의 변환 책임은 `Onnba3011Converter`에 둡니다.
|
||||
|
||||
### 기본 구현 계층
|
||||
### 새 Tool 추가 체크리스트
|
||||
|
||||
```text
|
||||
Request DTO
|
||||
-> UseCase Interface
|
||||
-> UseCaseImpl
|
||||
-> Converter (AI DTO <-> MCI/EAI Interface IO)
|
||||
-> Client / Adapter
|
||||
-> Legacy System
|
||||
```
|
||||
새 업무 Tool을 추가할 때는 아래 순서로 확인합니다.
|
||||
|
||||
- Request DTO 필드에는 `@Schema(description = "...")`와 Bean Validation 어노테이션을 선언합니다.
|
||||
- 복잡하거나 조건부 규칙이 필요한 입력은 `tool-schemas/{categoryKey}/...-input-schema.json` 리소스를 사용합니다.
|
||||
- 응답 Schema가 필요한 경우 Response DTO에 `@McpOutputSchema`를 선언하고, 복잡한 경우 Output Schema 리소스로 명시합니다.
|
||||
- `null`의 업무 의미, 조건부 필드, 배열 정렬 기준, `hasMore` 여부, 민감 정보 제외 원칙을 Schema 설명에 명확히 씁니다.
|
||||
- [ ] 소속 모듈과 `categoryKey`를 업무 소유 조직 기준으로 결정한다.
|
||||
- [ ] Agent 입력·응답 DTO인 `XxxRequest`, `XxxResponse`를 만든다.
|
||||
- [ ] `XxxUseCase`에 `@McpTool`을 선언하고, 호출 메서드에 `@McpFunction`의 이름·설명·연동 ID를 선언한다.
|
||||
- [ ] `XxxUseCaseImpl`에서 업무 흐름만 조합한다.
|
||||
- [ ] `XxxConverter`에 업무 DTO ↔ 인터페이스 ID DTO 변환을 둔다.
|
||||
- [ ] `MciXxxClient`와 `INTERFACE_ID_I`, `INTERFACE_ID_O`를 인터페이스 ID 기준으로 만든다.
|
||||
- [ ] DTO에 Bean Validation을 선언하고, 중첩 DTO가 있으면 입력 Schema와 검증 대상에 포함되는지 확인한다.
|
||||
- [ ] 조회·변경 작업 특성에 따라 `readOnlyHint`, `requiresApproval`, `idempotentHint`를 설정한다.
|
||||
- [ ] 단위 테스트를 작성하고 `:dap-tool-core:test` 또는 대상 모듈 테스트를 실행한다.
|
||||
- [ ] Tool 서버 기동 후 `/mcp/api/v1/tools/list`에서 이름, 설명, category, JSON Schema가 맞는지 확인한다.
|
||||
- [ ] 요청·응답 로그에 개인정보나 인증값이 남지 않는지 확인한다.
|
||||
### 등록·노출 제어
|
||||
|
||||
### Schema 우선순위
|
||||
|
||||
1. `inputSchemaResource` 또는 `outputSchemaResource`가 지정된 경우: 해당 JSON Schema를 사용합니다.
|
||||
2. 리소스가 없으면: DTO의 `@Schema` 정보와 공통 Generator로 생성합니다. 응답 Schema는 `@McpOutputSchema`가 선언된 DTO만 자동 생성합니다.
|
||||
|
||||
## 8. Manifest와 Registry
|
||||
|
||||
각 Tool Pod는 기동 시 `@McpTool`, `@McpFunction` 정보를 읽어 `/tool-manifest`를 제공합니다. Manifest에는 Tool 이름, 설명, endpoint, revision, Input/Output Schema가 포함됩니다.
|
||||
|
||||
Gateway는 Registry 등록 및 Heartbeat 정보를 이용해 Tool을 라우팅합니다. Tool Pod가 추가되어도 동일한 Manifest/Registry 규약을 준수하면 Gateway에서 탐색·호출할 수 있습니다.
|
||||
|
||||
## 9. MCI/EAI 연계
|
||||
|
||||
공통 MCI/EAI 연계 기능과 Glow 기본 설정은 `dat-was-lib`에서 제공합니다. Tool Pod별 애플리케이션 프로필은 각 Pod의 `application*.yml`에서 관리합니다.
|
||||
|
||||
```text
|
||||
dat-was-lib/src/main/resources/glow/application-glow*.yml
|
||||
dat-was-sal/src/main/resources/application*.yml
|
||||
dat-was-cus/src/main/resources/application*.yml
|
||||
dat-was-pro/src/main/resources/application*.yml
|
||||
dat-was-sys/src/main/resources/application*.yml
|
||||
```
|
||||
|
||||
MCI 호출 주소는 일반적으로 `host + uri`로 구성합니다. 예를 들어 `host=https://dev-ichmci.shinhanlife.co.kr`, `uri=/ntl_mci/clc_rcv`이면 호출 대상은 다음과 같습니다.
|
||||
|
||||
```text
|
||||
https://dev-ichmci.shinhanlife.co.kr/ntl_mci/clc_rcv
|
||||
```
|
||||
|
||||
`receive-uri`는 Tool이 호출할 주소가 아니라, MCI가 비동기/콜백 방식으로 응답을 전달하도록 별도 계약된 경우 수신에 사용하는 경로입니다.
|
||||
## 10. 품질 검증
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dat-was-lib:test
|
||||
.\gradlew.bat :dat-was-cus:compileJava
|
||||
.\gradlew.bat :dat-was-sal:compileJava
|
||||
.\gradlew.bat :dat-was-pro:compileJava
|
||||
.\gradlew.bat :dat-was-sys:compileJava
|
||||
.\gradlew.bat validateMcpToolNames
|
||||
```
|
||||
|
||||
배포 전에는 다음을 확인합니다.
|
||||
|
||||
- Tool 이름의 전역 중복 여부와 `pod_domain_service_action` 규칙(아래 확인 사항 반영 후)
|
||||
- Request/Response Schema 및 실제 예제 JSON
|
||||
- Tool Pod 단위 테스트와 Gateway 경유 호출
|
||||
- MCI/EAI 오류 코드의 사용자용 응답 매핑
|
||||
- 민감정보가 요청·응답·로그에 포함되지 않는지
|
||||
- 승인된 개발/테스트 계정과 데이터만 사용했는지
|
||||
|
||||
## 11. 보안과 운영 원칙
|
||||
|
||||
- API Key, Runner 등록 토큰, 계정·비밀번호 등 비밀값은 Git에 넣지 않고 환경 변수 또는 Secret Manager로 주입합니다.
|
||||
- `TESTER-DEV` 같은 테스트 권한은 개발 환경에서만 허용하고, 운영에서는 Tool 단위 최소 권한으로 제한합니다.
|
||||
- Tool 요청·응답 로그는 마스킹/제외 정책을 거친 값만 남깁니다.
|
||||
- Trace ID는 요청 흐름 전체를 추적하고, Request ID는 HTTP 호출 단위로 새로 부여합니다.
|
||||
- Tool Pod는 업무 소유권과 장애 격리 단위에 맞춰 독립 이미지·배포·운영 책임으로 분리합니다.
|
||||
|
||||
## 12. 참고 소스 위치
|
||||
|
||||
| 기능 | 위치 |
|
||||
| 속성 | 의미 |
|
||||
|---|---|
|
||||
| Tool 공통 Controller | `dat-was-lib/src/main/java/io/shinhanlife/dat/mcc/presentation` |
|
||||
| Tool 어노테이션 | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/annotation` |
|
||||
| JSON Schema/검증 | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/schema`, `.../validation` |
|
||||
| Manifest | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/manifest` |
|
||||
| Tool Scaffold | `dat-was-lib/src/main/java/io/shinhanlife/dat/lib/util/ToolScaffolder.java` |
|
||||
| Gateway 라우터 | `dat-gateway/src/main/java/io/shinhanlife/dat/mcg/presentation/McpRouterController.java` |
|
||||
| Customer Tool | `dat-was-cus/src/main/java` |
|
||||
| Sales Tool | `dat-was-sal/src/main/java` |
|
||||
| Process Tool | `dat-was-pro/src/main/java` |
|
||||
| System Tool | `dat-was-sys/src/main/java` |
|
||||
## 13. 모듈명 전환 검증
|
||||
| `register` | Redis Registry와 Gateway 카탈로그에 등록할지 여부 |
|
||||
| `visible` | Agent/클라이언트 목록에 표시할지 여부 |
|
||||
| `requiresApproval` | 쓰기·고위험 작업의 승인 요구 여부 |
|
||||
| `readOnlyHint`, `destructiveHint`, `idempotentHint` | Agent 호출 특성 힌트 |
|
||||
|
||||
Tool 관련 공통 기능은 `dat-was-*` 모듈명만 기준으로 동작합니다.
|
||||
`register = false`인 Tool은 자동 카탈로그 등록 대상이 아닙니다. 별도 실행 목적이 있는 경우에만 사용하고, 필요한 fallback 경로를 운영 설정으로 확인합니다.
|
||||
|
||||
- `validateMcpToolNames`는 `dat-was-*` Tool Pod를 탐색하여 이름 규칙과 전역 중복을 검사합니다.
|
||||
- Tool Scaffold는 Pod 이름을 Tool 함수명에 포함하지 않습니다. 함수명은 `도메인_비즈니스_행위` 형식입니다. 예: `cmm_notification_send`
|
||||
- Pod Scaffold와 Gateway Scaffold 화면/API의 모듈 목록도 `dat-was-*` 명칭으로 통일되어 있습니다.
|
||||
- Tool Source Update 기능은 `dat-was-*` 아래의 `*UseCase.java`를 검색합니다.
|
||||
## 빌드·테스트·로컬 실행
|
||||
|
||||
## 14. BC-DAB-STD-003 Tool Schema V17 적용
|
||||
### 사전 조건
|
||||
|
||||
각 Tool의 표준 명세는 Tool Pod별 다음 경로에서 관리합니다.
|
||||
- JDK 21
|
||||
- Gradle Wrapper 사용 권장
|
||||
- 로컬 Redis 또는 Docker Compose 환경
|
||||
- 필요 시 MCI Mock 또는 사내 MCI/EAI 접근 환경
|
||||
|
||||
```text
|
||||
dat-was-{pod}/src/main/resources/tool-definitions/{categoryKey}/{toolName}.yml
|
||||
```
|
||||
|
||||
Tool 이름은 Pod 정보를 포함하지 않는 `도메인_서비스_행위` 형태의 영문 소문자 snake_case를 사용하며,
|
||||
정규식 `^[a-z][a-z0-9_]{2,63}$`을 만족해야 합니다. 예: `cmm_claim_search`.
|
||||
|
||||
필수 항목은 `name`, `display_name`, `version`, `category_key`, 설명 4개 요소(function, when_to_use,
|
||||
when_not_to_use, io_limits), `display_description`, 예시 질의 3~10건, 동작 힌트 3개(read_only,
|
||||
destructive, idempotent), `parameters_schema`입니다. 입력 Schema는 루트 `type: object`, 각 property의
|
||||
`description`, `additionalProperties: false`를 갖춰야 합니다. 선택 운영 항목은 `tags`,
|
||||
`legacy_interface_id`, `required_env_keys`, `owner_org`입니다.
|
||||
|
||||
기동 시 `tool-definitions/**/*.yml`을 한 번 읽어 이름 기준으로 캐시하고, `@McpTool` 실행 정보와 결합한
|
||||
동일한 `ToolMetadata`를 `/tool-manifest`, Tool Pod MCP, Gateway MCP에 사용합니다.
|
||||
|
||||
입력 Schema 우선순위는 `inputSchemaResource` → V17 `parameters_schema` → DTO 자동 생성이고, 출력은
|
||||
`outputSchemaResource` → 명시 Output Schema → `@McpOutputSchema` 기반 생성입니다. Output Schema를
|
||||
명시한 Tool만 최종 응답 검증을 수행합니다. 현재 Scaffold는 Tool Definition V17 YAML과 응답 Mock JSON을
|
||||
자동 생성하지 않고, 생성한 Java Tool의 `@GrowToolHint`를 기준으로 메타데이터를 제공합니다.
|
||||
|
||||
Scaffold의 Pod Module 생성은 `local`, `dev`, `test`, `prod` 프로필 파일과
|
||||
`src/main/resources/tool-service-manifest.yml`을 만듭니다. 이 파일은 라우팅용 설정이며 `/tool-manifest`
|
||||
API 응답을 대체하지 않습니다. AI 초안은 한국어 YAML로 생성하며, 선택한 Target Module 목록에서 자기
|
||||
자신을 제외해 `confusable-servers`에 반영합니다. Tool Function의 기본 Protocol은 MCI(Legacy)입니다.
|
||||
### 전체 빌드와 대표 검증
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat validateMcpToolNames validateToolSchemaV17
|
||||
.\gradlew.bat clean build
|
||||
.\gradlew.bat :dap-tool-core:test
|
||||
.\gradlew.bat :dap-tool-core:compileJava
|
||||
```
|
||||
|
||||
`bootJar`는 두 검증에 의존하므로 이름 중복, 필수 항목 누락, Java Tool과 YAML 명세 불일치가 있으면
|
||||
Docker 이미지 생성 전에 빌드가 실패합니다.
|
||||
### 애플리케이션 실행
|
||||
|
||||
각 애플리케이션은 별도 PowerShell에서 실행합니다.
|
||||
|
||||
```powershell
|
||||
# Gateway
|
||||
.\gradlew.bat :dap-gateway:bootRun
|
||||
|
||||
# SMS Tool
|
||||
.\gradlew.bat :dap-tool-sms:bootRun
|
||||
|
||||
# 기타 업무 Tool
|
||||
.\gradlew.bat :dap-tool-oth:bootRun
|
||||
```
|
||||
|
||||
기본 프로필은 `local`입니다. 개발 서버 설정이 필요하면 실행 환경에 프로필을 지정합니다.
|
||||
|
||||
```powershell
|
||||
$env:SPRING_PROFILES_ACTIVE = 'dev'
|
||||
.\gradlew.bat :dap-tool-oth:bootRun
|
||||
```
|
||||
|
||||
## Docker Compose 실행
|
||||
|
||||
Docker Compose는 Redis, Gateway, MCI Mock, SMS Tool, OTH Tool을 함께 기동합니다.
|
||||
|
||||
```powershell
|
||||
$env:ACTIVE_PROFILE = 'local'
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
| 서비스 | 컨테이너 포트 | 호스트 포트 |
|
||||
|---|---:|---:|
|
||||
| Redis | 6379 | 6379 |
|
||||
| Gateway | 8081 | 8281 |
|
||||
| MCI Mock | 8080 | 8089 |
|
||||
| SMS Tool | 8082 | 8282 |
|
||||
| OTH Tool | 8084 | 8284 |
|
||||
|
||||
컨테이너 내부와 PC 브라우저의 접속 주소는 다릅니다.
|
||||
|
||||
```text
|
||||
컨테이너 내부: http://gateway:8081, http://tool-oth:8084
|
||||
PC 브라우저: http://localhost:8281, http://localhost:8284
|
||||
```
|
||||
|
||||
## 환경 설정
|
||||
|
||||
### 프로필
|
||||
|
||||
기본 프로필은 `local`입니다. Tool 서버와 Gateway 모두 `local`, `dev` 프로필 파일을 사용합니다.
|
||||
|
||||
| 프로필 | 목적 | 주요 설정 파일 |
|
||||
|---|---|---|
|
||||
| `local` | PC 개발·MCI Mock·H2 메모리 DB 기반 실행 | `application-local.yml` |
|
||||
| `dev` | 개발 서버 배포 실행 | `application-dev.yml` |
|
||||
|
||||
프로필은 환경 변수로 지정합니다.
|
||||
|
||||
```powershell
|
||||
# 로컬 개발
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
|
||||
# 개발 서버 설정으로 실행
|
||||
$env:SPRING_PROFILES_ACTIVE = 'dev'
|
||||
```
|
||||
|
||||
### 환경별 연결 주소 비교
|
||||
|
||||
| 항목 | `local` 프로세스 실행 | Docker Compose | `dev` 프로필 |
|
||||
|---|---|---|---|
|
||||
| Gateway 접근 주소 | `http://localhost:8081` | 컨테이너 내부 `http://gateway:8081` | 배포 환경 Gateway URL |
|
||||
| SMS Tool 주소 | `http://localhost:8082` | `http://tool-sms:8082` | `PORT` 기본 8082 |
|
||||
| OTH Tool 주소 | `http://localhost:8084` | `http://tool-oth:8084` | `PORT` 기본 8084 |
|
||||
| Redis 주소 | 로컬 Redis 또는 `localhost:6379` | `redis:6379` | 운영/개발 Redis 설정 |
|
||||
| MCI/EAI 대상 | MCI Mock 또는 로컬 설정 | `mci-mock:8080` | 개발망 연동 설정 |
|
||||
| 브라우저 Gateway 접속 | `http://localhost:8081` | `http://localhost:8281` | 운영·개발 도메인 |
|
||||
|
||||
`local`에서 프로세스를 직접 실행하면 Tool의 `AXHUB_GATEWAY_URL`은 `localhost`를 사용합니다. Docker에서는 각 컨테이너가 서로 다른 네트워크 공간에 있으므로 반드시 Compose 서비스 이름을 사용합니다.
|
||||
### Gateway 환경 변수
|
||||
|
||||
| 변수 | 적용 대상 | 설명 | 로컬 기본값/예시 |
|
||||
|---|---|---|---|
|
||||
| `SPRING_PROFILES_ACTIVE` | Gateway, 모든 Tool | 활성 Spring 프로필 | `local` |
|
||||
| `OPENROUTER_API_KEY` | Gateway | Chat/LLM 호출 API 키 | 운영·개발 환경의 비밀 저장소에서 주입 |
|
||||
| `SPRING_DATA_REDIS_HOST` | Gateway, 모든 Tool | Redis 호스트 | Docker: `redis` |
|
||||
| `SPRING_DATA_REDIS_PORT` | Gateway, 모든 Tool | Redis 포트 | `6379` |
|
||||
| `MCP_GATEWAY_FALLBACK_DEFAULT_URL` | Gateway | Registry에 없는 Tool의 기본 fallback URL | Docker: `http://tool-oth:8084` |
|
||||
| `MCP_GATEWAY_FALLBACK_ROUTES_SMS` | Gateway | SMS 계열 fallback URL | Docker: `http://tool-sms:8082` |
|
||||
| `ACTIVE_PROFILE` | Docker Compose | Compose가 `SPRING_PROFILES_ACTIVE`에 전달할 프로필 | `local` |
|
||||
|
||||
`OPENROUTER_API_KEY` 같은 인증값은 `application.yml`, README, Git 커밋에 직접 넣지 않습니다. 개발·운영 환경의 Secret, CI/CD 변수 또는 안전한 환경 변수로 주입합니다.
|
||||
|
||||
### Tool 서버 환경 변수
|
||||
|
||||
| 변수 | 적용 대상 | 설명 | 로컬 기본값/예시 |
|
||||
|---|---|---|---|
|
||||
| `PORT` | `dap-tool-sms`, `dap-tool-oth` | 개발 프로필에서 Tool 서버 포트 변경 | SMS `8082`, OTH `8084` |
|
||||
| `AXHUB_GATEWAY_URL` | 모든 Tool | Tool 등록·Heartbeat 대상 Gateway 주소 | 로컬 `http://localhost:8081`, Docker `http://gateway:8081` |
|
||||
| `AXHUB_TOOL_URL` | 모든 Tool | Gateway가 해당 Tool Pod를 호출할 주소 | 로컬 `http://localhost:{server.port}` |
|
||||
| `GLOW_COMMUNICATION_MCI_HOMT` | Docker Tool 컨테이너 | MCI 대상 호스트 | 로컬 Compose는 `mci-mock` |
|
||||
| `GLOW_COMMUNICATION_MCI_PORT` | Docker Tool 컨테이너 | MCI 대상 포트 | 로컬 Compose는 `8080` |
|
||||
| `GLOW_COMMUNICATION_EAI_HOMT` | Docker Tool 컨테이너 | EAI 대상 호스트 | 로컬 Compose는 `mci-mock` |
|
||||
| `GLOW_COMMUNICATION_EAI_PORT` | Docker Tool 컨테이너 | EAI 대상 포트 | 로컬 Compose는 `8080` |
|
||||
|
||||
`AXHUB_TOOL_URL`은 반드시 Gateway가 실제로 접근 가능한 주소여야 합니다. PC에서 각각 실행할 때는 `localhost`를 사용하고, Docker 컨테이너 안에서는 `tool-sms`, `tool-oth` 같은 Compose 서비스 이름을 사용합니다.
|
||||
|
||||
### 로컬 실행용 권장 설정
|
||||
|
||||
아래는 키 값 없이 로컬 프로세스를 실행하는 예시입니다. Redis를 Docker로 먼저 기동하거나 전체 Docker Compose를 사용합니다.
|
||||
|
||||
```powershell
|
||||
# 선택 1: Redis만 기동
|
||||
$env:ACTIVE_PROFILE = 'local'
|
||||
docker compose up -d redis mci-mock
|
||||
|
||||
# 선택 2: 각 프로세스를 로컬에서 기동
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
$env:OPENROUTER_API_KEY = '<개인 또는 개발용 비밀 저장소의 키>'
|
||||
.\gradlew.bat :dap-gateway:bootRun
|
||||
```
|
||||
|
||||
다른 PowerShell에서 Tool을 실행합니다.
|
||||
|
||||
```powershell
|
||||
$env:SPRING_PROFILES_ACTIVE = 'local'
|
||||
$env:AXHUB_GATEWAY_URL = 'http://localhost:8081'
|
||||
$env:AXHUB_TOOL_URL = 'http://localhost:8084'
|
||||
.\gradlew.bat :dap-tool-oth:bootRun
|
||||
```
|
||||
|
||||
### 권한 도메인 설정
|
||||
|
||||
`mcp.security.tenant-domains`는 테넌트 또는 호출 주체가 접근할 수 있는 `categoryKey`를 정의합니다. 운영 환경에서는 `ALL`을 무분별하게 사용하지 않고, Agent·조직별 허용 도메인을 최소 권한으로 설정합니다.
|
||||
|
||||
```yaml
|
||||
mcp:
|
||||
security:
|
||||
tenant-domains:
|
||||
claims-agent: claim, customer
|
||||
notification-agent: notification
|
||||
```
|
||||
|
||||
## 공통 기능과 운영 기준
|
||||
|
||||
### Redis
|
||||
|
||||
Redis는 Tool Registry의 등록 상태와 Heartbeat, Gateway 실행 추적 정보에 사용됩니다. 현재 Redis는 PII 원문 보관소가 아닙니다.
|
||||
|
||||
### 권한과 승인
|
||||
|
||||
Gateway는 Agent가 전달한 허용 Tool 목록, 서버 정책, 신뢰된 Claim 여부, 쓰기 승인 여부를 확인합니다. Tool마다 권한 로직을 중복 구현하지 말고 Gateway 공통 정책과 `@McpFunction` 메타데이터를 사용합니다.
|
||||
|
||||
### Trace ID와 Request ID
|
||||
|
||||
```text
|
||||
trace-id : 사용자 요청 전체에서 유지되는 상관관계 ID
|
||||
request-id : Gateway → Tool, Tool → MCI 등 HTTP 호출마다 새로 생성되는 ID
|
||||
```
|
||||
|
||||
Gateway와 Tool에는 관련 헤더 및 MDC 기반 로그 처리가 있습니다. 신규 HTTP Client도 공통 전파 정책을 따르며, Tool별로 임의의 헤더 이름을 추가하지 않습니다.
|
||||
|
||||
### 로그와 개인정보
|
||||
|
||||
Gateway에는 민감 키와 일부 형식을 마스킹하는 공통 기능이 있습니다. 마스킹은 원문을 Agent에서 분리하는 PII 토큰화와는 다릅니다.
|
||||
|
||||
- 요청·응답 전문을 로그에 남길 때는 반드시 마스킹합니다.
|
||||
- 운영 로그에 주민번호, 계좌번호, 전화번호, 이메일, 인증값을 남기지 않습니다.
|
||||
- Tool 서버의 신규 로그도 같은 마스킹 정책을 적용합니다.
|
||||
- 민감정보 복원 필요 여부는 Tool 개발자가 임의로 결정하지 않고 보안·AA 정책을 따릅니다.
|
||||
|
||||
## AA 협의 기반 향후 전환 과제
|
||||
|
||||
아래 항목은 현재 구현 완료 기능이 아니라 회의에서 합의한 목표 구조입니다.
|
||||
|
||||
### 부서별 Tool Pod와 저장소 경계
|
||||
|
||||
현재 `dap-tool-oth`에 함께 있는 업무 Tool을 부서·업무 소유권 단위로 분리합니다. 각 Pod는 독립 이미지, 독립 배포, 독립 장애 범위를 갖도록 구성합니다. 실제 분리는 AA가 확정한 Tool 소유 부서와 운영 책임 매핑을 기준으로 수행합니다.
|
||||
|
||||
### Agent별 Tool 노출 수 제한
|
||||
|
||||
Agent에게 모든 Tool을 한 번에 제공하지 않고, 업무 도메인과 권한에 따라 약 10~20개 수준의 Tool 그룹을 제공합니다. `categoryKey`는 이를 위한 기초 메타데이터이며, 향후 Agent-Tool Group 정책으로 확장합니다.
|
||||
|
||||
### PII 토큰화
|
||||
|
||||
```text
|
||||
원문 개인정보
|
||||
→ PII Gateway가 Redis에 짧은 TTL로 보관
|
||||
→ Agent에는 PII 토큰 또는 안전한 식별자만 전달
|
||||
→ 인가된 Tool이 MCI 호출 직전에 필요한 항목만 복원
|
||||
```
|
||||
|
||||
이 전환 전까지는 현행 마스킹 기능을 PII 분리 구현으로 오해하지 않아야 합니다.
|
||||
|
||||
### MCP SDK와 Glow Framework
|
||||
|
||||
Gateway에는 Spring AI MCP Server 의존성이 포함되어 있습니다. MCP SDK/Glow 표준 적용 시에는 업무 Tool을 재작성하지 않고, 기존 `@McpTool`·`@McpFunction`과 UseCase를 표준 MCP Tool 명세·호출 콜백으로 연결하는 Adapter 계층을 공통 Core에 추가합니다.
|
||||
|
||||
```text
|
||||
MCP SDK 표준 tools/list, tools/call
|
||||
↓
|
||||
공통 Adapter
|
||||
↓
|
||||
기존 UseCase → Converter → MCI Client
|
||||
```
|
||||
|
||||
따라서 업무 DTO, Converter, MCI Client의 책임은 유지됩니다.
|
||||
|
||||
## 참고 소스
|
||||
|
||||
| 주제 | 대표 위치 |
|
||||
|---|---|
|
||||
| Gateway Tool API | `dap-gateway/.../presentation/McpRouterController.java` |
|
||||
| 동적 MCP SSE/호출 경로 | `dap-gateway/.../sync/DynamicMcpController.java` |
|
||||
| Tool 실행 Controller | `dap-tool-core/.../presentation/BusinessToolController.java` |
|
||||
| Tool 자동 등록 | `dap-tool-core/.../usecase/ToolRegistryHeartbeatSender.java` |
|
||||
| Tool 어노테이션 | `dap-tool-core/.../annotation/McpTool.java`, `McpFunction.java` |
|
||||
| Tool 예시 | `dap-tool-oth/.../biz/oth`, `biz/sol`, `biz/smp` |
|
||||
| SMS Tool 예시 | `dap-tool-sms/.../biz/sms` |
|
||||
| Docker 환경 | `docker-compose.yml` |
|
||||
|
||||
---
|
||||
|
||||
문서에 없는 업무·보안·배포 기준은 임의로 추가하지 말고 AA 및 플랫폼 운영 기준과 먼저 합의합니다.
|
||||
## Input/Output Schema 작성 가이드
|
||||
|
||||
Tool Schema는 Agent가 Tool을 정확히 호출하고, 반환값의 의미를 일관되게 해석하도록 하는 계약입니다. 인증 정보·사번·주민번호 등 민감정보(PII)는 Input/Output Schema와 Tool 응답에 포함하지 않습니다.
|
||||
|
||||
### Input Schema
|
||||
|
||||
Input Schema는 Agent가 Tool에 전달하는 파라미터의 이름, 타입, 필수 여부, 허용값, 형식 등을 정의합니다.
|
||||
|
||||
적용 우선순위는 다음과 같습니다.
|
||||
|
||||
1. `inputSchemaResource` — 복잡한 규칙을 담은 JSON Schema 리소스
|
||||
2. `inputSchema` — 어노테이션에 직접 선언한 JSON Schema
|
||||
3. 요청 DTO 필드의 `@McpValidation` — 자동 JSON Schema 생성
|
||||
|
||||
단순한 요청 DTO는 `@McpValidation`만으로 관리합니다.
|
||||
|
||||
```java
|
||||
public class ClaimSearchRequest {
|
||||
|
||||
@McpValidation(required = true, pattern = "^CLM[0-9]{13}$")
|
||||
private String claimNo;
|
||||
|
||||
@McpValidation(minimum = 1, maximum = 100)
|
||||
private Integer size;
|
||||
}
|
||||
```
|
||||
|
||||
### Output Schema
|
||||
|
||||
Output Schema는 Tool이 반환하는 결과의 타입과 의미를 정의합니다. `BusinessToolController`는 Tool 실행 후 반환값을 Output Schema 기준으로 검증합니다.
|
||||
|
||||
적용 우선순위는 다음과 같습니다.
|
||||
|
||||
1. `outputSchemaResource` — 조건부 필드·중첩 배열 등 복잡한 규칙을 담은 JSON Schema 리소스
|
||||
2. `outputSchema` — 어노테이션에 직접 선언한 JSON Schema
|
||||
3. 반환 DTO의 `@McpOutputSchema`와 필드 `@McpValidation` — 자동 JSON Schema 생성
|
||||
4. 위 설정이 모두 없으면 Output Schema 검증을 수행하지 않음
|
||||
|
||||
따라서 단순한 응답은 별도 `outputSchemaResource` 없이 반환 DTO에 `@McpOutputSchema`를 선언하면 됩니다. `null`이 정상 값일 수 있는 필드는 `nullable = true`를 반드시 지정합니다.
|
||||
|
||||
```java
|
||||
@McpOutputSchema
|
||||
public class ClaimSearchResponse {
|
||||
|
||||
@McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"})
|
||||
private String resultCode;
|
||||
|
||||
@McpValidation(nullable = true, minimum = 0)
|
||||
private Long approvedAmount;
|
||||
}
|
||||
```
|
||||
|
||||
### 복잡한 Schema는 Tool 모듈별 리소스로 관리
|
||||
|
||||
조건부 응답, 중첩 DTO, 배열 정렬 기준처럼 어노테이션만으로 표현하기 어려운 규칙은 Tool Core가 아니라 각 Tool 모듈의 리소스에 JSON Schema로 둡니다.
|
||||
|
||||
```text
|
||||
src/main/resources/
|
||||
└─ tool-schemas/
|
||||
└─ {categoryKey}/
|
||||
├─ claim-search-resource-input-schema.json
|
||||
└─ claim-search-resource-output-schema.json
|
||||
```
|
||||
|
||||
예를 들어 `categoryKey`가 `cmm`이면 아래와 같이 선언합니다.
|
||||
|
||||
```java
|
||||
@McpFunction(
|
||||
name = "oth.cmm.claim.search",
|
||||
inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json",
|
||||
outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json"
|
||||
)
|
||||
public ClaimSearchResponse search(ClaimSearchRequest request) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`inputSchemaResource`와 `outputSchemaResource`는 복잡한 경우에만 선언합니다. 단순한 Tool까지 JSON 파일을 별도 생성할 필요는 없습니다.
|
||||
|
||||
### Output 설계 규칙
|
||||
|
||||
- 코드와 표시용 라벨을 함께 반환합니다. 예: `status` + `statusLabel`
|
||||
- `null`이 정상인 값은 의미를 설명에 명시하고 DTO에는 `nullable = true`를 설정합니다.
|
||||
- 조건부 필드는 어떤 조건에서 값이 존재하는지 JSON Schema에 명시합니다.
|
||||
- 배열은 정렬 기준을 설명에 명시합니다. 예: `접수일 내림차순`
|
||||
- 목록 응답에는 추가 조회 여부를 나타내는 `hasMore`를 포함합니다.
|
||||
- 민감정보는 마스킹보다 **응답에서 제외**하는 것을 우선합니다.
|
||||
|
||||
### 실행 로그 및 확인
|
||||
|
||||
Tool 실행이 끝나면 아래 로그는 Schema 정의가 아니라 **검증을 통과한 실제 최종 응답값**을 출력합니다.
|
||||
|
||||
```text
|
||||
[Tool -> MCP Gateway] Output Schema Result: { ... }
|
||||
```
|
||||
|
||||
따라서 로그에도 실제 응답이 남으므로, 응답 DTO와 Output Schema에 민감정보가 포함되지 않도록 설계해야 합니다.
|
||||
|
||||
스키마 리소스와 DTO 기반 자동 Schema는 아래 테스트로 함께 검증할 수 있습니다.
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :dap-tool-oth:test --tests "io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequestSchemaTest"
|
||||
```
|
||||
|
||||
### Tool Naming Convention
|
||||
|
||||
All Tool names use the four-level lowercase format `pod.domain.service.action`. Do not use underscores or CamelCase; use a hyphen (`-`) only when a single level has multiple words.
|
||||
|
||||
- `pod`: deployment Tool Pod/module (`dap-tool-oth` → `oth`, `dap-tool-sms` → `sms`)
|
||||
- `domain`: business-domain package (`cmm`, `smp`, `sol`, etc.)
|
||||
- `service`: business service or resource
|
||||
- `action`: the requested operation (`search`, `list`, `detail`, `issue`, `inquiry`, etc.)
|
||||
|
||||
```text
|
||||
oth.cmm.bond.issue
|
||||
oth.cmm.claim.search
|
||||
oth.sol.request.list
|
||||
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은 샘플값 대신 개발계에서 허용된 테스트 데이터를 저장해서 사용해야 합니다.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
||||
public class TestClientTransport {
|
||||
public static void main(String[] args) {
|
||||
HttpClientStreamableHttpTransport t = HttpClientStreamableHttpTransport.builder("http://localhost:8086/mcp").build();
|
||||
System.out.println("Wait, I can't print private fields easily, but let's just see if it runs.");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class TestMcpCall {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String json = "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"iam_team_contact\",\"arguments\":{\"target\":\"전체\"}},\"id\":1}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8086/mcp"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer dapms") // just in case
|
||||
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("STATUS: " + response.statusCode());
|
||||
System.out.println("BODY: " + response.body());
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import io.modelcontextprotocol.client.McpSyncClient;
|
||||
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class TestMcpClient {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String endpoint = "http://localhost:8086/mcp";
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
|
||||
requestBuilder.header("Authorization", "Bearer dapms");
|
||||
|
||||
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(endpoint)
|
||||
.requestBuilder(requestBuilder)
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
try (McpSyncClient client = McpClient.sync(transport)
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.build()) {
|
||||
System.out.println("Initializing client...");
|
||||
client.initialize();
|
||||
System.out.println("Client initialized.");
|
||||
|
||||
System.out.println("Calling tool...");
|
||||
McpSchema.CallToolResult result = client.callTool(McpSchema.CallToolRequest.builder()
|
||||
.name("iam_team_contact")
|
||||
.arguments(Map.of("target", "전체"))
|
||||
.build());
|
||||
System.out.println("Result: " + result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,11 +0,0 @@
|
||||
import java.lang.reflect.Method;
|
||||
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
||||
|
||||
public class TestReflection {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Class<?> clazz = HttpServletStreamableServerTransportProvider.Builder.class;
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
System.out.println(m.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class TestSseEndpoint {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8086/mcp"))
|
||||
.header("Accept", "text/event-stream")
|
||||
.header("X-Tool-Server-API-Key", "test")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("STATUS: " + response.statusCode());
|
||||
System.out.println("BODY: " + response.body());
|
||||
}
|
||||
}
|
||||
173
build.gradle
173
build.gradle
@@ -1,181 +1,68 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.5.11'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
id 'org.springframework.boot' version '4.0.5' apply false
|
||||
id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// 전체 프로젝트 공통 Maven 좌표
|
||||
// ============================================================================
|
||||
|
||||
allprojects {
|
||||
|
||||
// 모든 모듈이 공유하는 Maven Group ID입니다.
|
||||
group = 'io.shinhanlife'
|
||||
|
||||
// 모든 모듈이 공유하는 프로젝트 버전입니다.
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
}
|
||||
|
||||
|
||||
subprojects {
|
||||
|
||||
// 모든 하위 모듈에서 Java Plugin을 사용합니다.
|
||||
apply plugin: 'java'
|
||||
|
||||
// Spring Boot BOM 기반 의존성 버전 관리를 위해 적용합니다.
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
sourceCompatibility = '21'
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// 외부 개발 환경
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
// 현재 인터넷이 가능한 환경에서는 Maven Central을 사용합니다.
|
||||
//
|
||||
// 신한라이프 폐쇄망 반입 시 아래 mavenCentral()은 제거하고
|
||||
// 사내 Nexus Repository URL로 교체해야 합니다.
|
||||
//
|
||||
// 예:
|
||||
//
|
||||
// maven {
|
||||
// url = uri('https://내부-Nexus/repository/maven-public/')
|
||||
// }
|
||||
//
|
||||
// Nexus 인증이 필요한 경우에는 credentials 설정도 추가해야 합니다.
|
||||
//
|
||||
// credentials {
|
||||
// username = findProperty('nexusUsername') ?: System.getenv('NEXUS_USERNAME')
|
||||
// password = findProperty('nexusPassword') ?: System.getenv('NEXUS_PASSWORD')
|
||||
// }
|
||||
//
|
||||
// 계정/비밀번호를 build.gradle에 직접 작성하면 안 됩니다.
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
maven { url 'https://repo.spring.io/snapshot' }
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Dependency Version Management
|
||||
// ========================================================================
|
||||
|
||||
dependencyManagement {
|
||||
|
||||
imports {
|
||||
|
||||
// Spring Boot 3.5.11이 검증한
|
||||
// Spring Framework / Jackson / Tomcat / Logback /
|
||||
// JUnit 등 주요 라이브러리 버전을 공통 관리합니다.
|
||||
// 하위 모듈에서는 Spring Boot가 관리하는 라이브러리라면
|
||||
// 가급적 개별 버전을 직접 지정하지 않고 사용합니다.
|
||||
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
|
||||
|
||||
// Gateway에서 사용하는 Spring AI 의존성 버전 관리
|
||||
mavenBom 'org.springframework.ai:spring-ai-bom:1.1.8'
|
||||
|
||||
// MCP SDK 버전 관리
|
||||
mavenBom 'io.modelcontextprotocol.sdk:mcp-bom:2.0.0'
|
||||
|
||||
mavenBom "org.springframework.ai:spring-ai-bom:2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Common Dependencies
|
||||
// ========================================================================
|
||||
|
||||
dependencies {
|
||||
|
||||
// ====================================================================
|
||||
// Lombok
|
||||
// ====================================================================
|
||||
|
||||
// Lombok은 컴파일 시 Getter / Setter / Builder 등의
|
||||
// 반복 코드를 생성합니다.
|
||||
//
|
||||
// compileOnly이므로 Runtime JAR에는 포함되지 않습니다.
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
|
||||
// Lombok Annotation Processor입니다.
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
// 테스트 코드에서도 Lombok Annotation을 사용할 수 있도록 합니다.
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
||||
// 테스트 코드용 Lombok Annotation Processor입니다.
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// MapStruct
|
||||
// ====================================================================
|
||||
|
||||
// Tool DTO와
|
||||
// MCI 요청/응답 DTO 사이의 Converter 구현체를
|
||||
// 컴파일 시 자동 생성하기 위해 사용합니다.
|
||||
implementation 'org.mapstruct:mapstruct:1.6.3'
|
||||
|
||||
// MapStruct Converter 구현 클래스를 생성하는
|
||||
// Annotation Processor입니다.
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// Test
|
||||
// ====================================================================
|
||||
|
||||
// Spring Boot Test / JUnit 5 / Mockito / AssertJ 등
|
||||
// 공통 테스트 환경을 제공합니다.
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
|
||||
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
// Gradle / IDE 환경에서 JUnit Platform 기반 테스트 실행을 지원합니다.
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Java Compile Options
|
||||
// ========================================================================
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
|
||||
// Reflection 및 MCP Schema 생성 시
|
||||
// Java 메서드의 실제 파라미터명을 사용할 수 있도록
|
||||
// 컴파일 결과에 파라미터 이름을 보존합니다.
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-parameters'
|
||||
|
||||
// MapStruct가 생성하는 Converter 구현 클래스를
|
||||
// Spring Bean(@Component)으로 생성합니다.
|
||||
//
|
||||
// 이를 통해 생성자 주입 등의 방식으로 사용할 수 있습니다.
|
||||
options.compilerArgs << '-Amapstruct.defaultComponentModel=spring'
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Test Configuration
|
||||
// ========================================================================
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
|
||||
// 모든 하위 모듈에서 JUnit 5 Platform을 사용합니다.
|
||||
tasks.withType(Test) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
task exportAllLibs(type: Copy) {
|
||||
into 'all-exported-libs'
|
||||
subprojects {
|
||||
from(it.configurations.findByName('runtimeClasspath'))
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
def toolCoreProject = project(':dap-was-lib')
|
||||
|
||||
tasks.register('validateMcpToolNames', JavaExec) {
|
||||
group = 'verification'
|
||||
description = 'Checks duplicate @McpFunction names across all Tool modules before packaging.'
|
||||
dependsOn toolCoreProject.tasks.named('classes')
|
||||
classpath = toolCoreProject.sourceSets.main.runtimeClasspath
|
||||
mainClass.set('io.shinhanlife.dap.lib.validation.McpToolNameValidationRunner')
|
||||
args rootProject.projectDir.absolutePath
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.matching { it.name == 'bootJar' }.configureEach {
|
||||
dependsOn rootProject.tasks.named('validateMcpToolNames')
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
Manifest-Version: 1.0
|
||||
|
||||
39
dap-gateway/build.gradle
Normal file
39
dap-gateway/build.gradle
Normal file
@@ -0,0 +1,39 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':dap-was-lib')
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
// Spring AI MCP Server
|
||||
implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc'
|
||||
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
|
||||
implementation 'org.springframework.ai:spring-ai-openai:2.0.0'
|
||||
// MyBatis & DB
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
implementation 'p6spy:p6spy:3.9.1'
|
||||
|
||||
// MapStruct
|
||||
implementation "org.mapstruct:mapstruct:1.5.5.Final"
|
||||
annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
|
||||
annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final"
|
||||
|
||||
// implementation 'com.networknt:json-schema-validator:1.4.0' // Spring AI 내장 버전과 충돌 방지를 위해 주석 처리
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.shinhanlife.dat.mcg;
|
||||
package io.shinhanlife.dap.mcg;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg
|
||||
* @className DatGatewayApplication
|
||||
* @package io.shinhanlife.dap.mcg
|
||||
* @className DapGatewayApplication
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
@@ -21,12 +21,12 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dat.mcg", "io.shinhanlife.dat.lib.mcp", "io.shinhanlife.dat.lib.config"})
|
||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dat.mcg", "io.shinhanlife.dat.lib.mcp", "io.shinhanlife.dat.lib.config"})
|
||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcg", "io.shinhanlife.dap.lib.mcp", "io.shinhanlife.dap.lib.config"})
|
||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcg", "io.shinhanlife.dap.lib.mcp", "io.shinhanlife.dap.lib.config"})
|
||||
@EnableCaching
|
||||
@EnableScheduling
|
||||
public class DatGatewayApplication {
|
||||
public class DapGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
new org.springframework.boot.builder.SpringApplicationBuilder(DatGatewayApplication.class).headless(false).run(args);
|
||||
SpringApplication.run(DapGatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dat.mcg.aop;
|
||||
package io.shinhanlife.dap.mcg.aop;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.aop
|
||||
* @package io.shinhanlife.dap.mcg.aop
|
||||
* @className GatewayLoggingAspect
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -27,7 +27,7 @@ import org.springframework.util.StopWatch;
|
||||
public class GatewayLoggingAspect {
|
||||
|
||||
// gateway의 controller 패키지 하위의 모든 클래스/메서드 실행 시 작동
|
||||
@Around("execution(* io.shinhanlife.dat.mcg.presentation..*(..))")
|
||||
@Around("execution(* io.shinhanlife.dap.mcg.presentation..*(..))")
|
||||
public Object logGatewayExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
String targetMethod = joinPoint.getSignature().toShortString();
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.audit;
|
||||
package io.shinhanlife.dap.mcg.audit;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.audit
|
||||
* @package io.shinhanlife.dap.mcg.audit
|
||||
* @className AuditLogService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,8 +15,8 @@ package io.shinhanlife.dat.mcg.audit;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dat.mcg.security.McpRequestContext;
|
||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.mcg.security.McpRequestContext;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.config;
|
||||
package io.shinhanlife.dap.mcg.config;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.config
|
||||
* @package io.shinhanlife.dap.mcg.config
|
||||
* @className AgentResponseBudgetProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dat.mcg.config;
|
||||
package io.shinhanlife.dap.mcg.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -8,7 +8,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.config
|
||||
* @package io.shinhanlife.dap.mcg.config
|
||||
* @className GatewayFallbackProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.config;
|
||||
package io.shinhanlife.dap.mcg.config;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.config
|
||||
* @package io.shinhanlife.dap.mcg.config
|
||||
* @className McpGatewayProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -37,8 +37,8 @@ public class McpGatewayProperties {
|
||||
private Boolean agentClaimsRequired;
|
||||
private Boolean trustedClaimsRequired;
|
||||
private Boolean writeApprovalRequired;
|
||||
private Boolean traceEnabled;
|
||||
private Long traceRetentionSize;
|
||||
private Boolean redisTraceEnabled;
|
||||
private Long redisTraceTtlSeconds;
|
||||
private Long toolTimeoutMillis;
|
||||
private Integer retryMaxAttempts;
|
||||
private Long retryInitialBackoffMillis;
|
||||
@@ -70,8 +70,8 @@ public class McpGatewayProperties {
|
||||
public void setAgentClaimsRequired(Boolean agentClaimsRequired) { this.agentClaimsRequired = agentClaimsRequired; }
|
||||
public void setTrustedClaimsRequired(Boolean trustedClaimsRequired) { this.trustedClaimsRequired = trustedClaimsRequired; }
|
||||
public void setWriteApprovalRequired(Boolean writeApprovalRequired) { this.writeApprovalRequired = writeApprovalRequired; }
|
||||
public void setTraceEnabled(Boolean traceEnabled) { this.traceEnabled = traceEnabled; }
|
||||
public void setTraceRetentionSize(Long traceRetentionSize) { this.traceRetentionSize = traceRetentionSize; }
|
||||
public void setRedisTraceEnabled(Boolean redisTraceEnabled) { this.redisTraceEnabled = redisTraceEnabled; }
|
||||
public void setRedisTraceTtlSeconds(Long redisTraceTtlSeconds) { this.redisTraceTtlSeconds = redisTraceTtlSeconds; }
|
||||
public void setToolTimeoutMillis(Long toolTimeoutMillis) { this.toolTimeoutMillis = toolTimeoutMillis; }
|
||||
public void setRetryMaxAttempts(Integer retryMaxAttempts) { this.retryMaxAttempts = retryMaxAttempts; }
|
||||
public void setRetryInitialBackoffMillis(Long retryInitialBackoffMillis) { this.retryInitialBackoffMillis = retryInitialBackoffMillis; }
|
||||
@@ -103,8 +103,8 @@ public class McpGatewayProperties {
|
||||
public boolean agentClaimsRequired() { return agentClaimsRequired == null || agentClaimsRequired; }
|
||||
public boolean trustedClaimsRequired() { return trustedClaimsRequired == null || trustedClaimsRequired; }
|
||||
public boolean writeApprovalRequired() { return writeApprovalRequired == null || writeApprovalRequired; }
|
||||
public boolean traceEnabled() { return traceEnabled == null || traceEnabled; }
|
||||
public long traceRetentionSize() { return traceRetentionSize == null || traceRetentionSize < 1 ? 1000 : traceRetentionSize; }
|
||||
public boolean redisTraceEnabled() { return redisTraceEnabled == null || redisTraceEnabled; }
|
||||
public long redisTraceTtlSeconds() { return redisTraceTtlSeconds == null || redisTraceTtlSeconds < 1 ? 3_600 : redisTraceTtlSeconds; }
|
||||
public long toolTimeoutMillis() { return toolTimeoutMillis == null || toolTimeoutMillis < 1 ? 5_000 : toolTimeoutMillis; }
|
||||
public int retryMaxAttempts() { return retryMaxAttempts == null || retryMaxAttempts < 1 ? 3 : retryMaxAttempts; }
|
||||
public long retryInitialBackoffMillis() { return retryInitialBackoffMillis == null || retryInitialBackoffMillis < 1 ? 1_000 : retryInitialBackoffMillis; }
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.shinhanlife.dap.mcg.config;
|
||||
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.config
|
||||
* @className RedisConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, ToolMetadata> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, ToolMetadata> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 1. Key는 무조건 String
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// 2. Value는 Generic 직렬화기 사용 (생성자 인자 없음)
|
||||
Jackson2JsonRedisSerializer<ToolMetadata> serializer = new Jackson2JsonRedisSerializer<>(ToolMetadata.class);
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.guardrail;
|
||||
package io.shinhanlife.dap.mcg.guardrail;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.guardrail
|
||||
* @package io.shinhanlife.dap.mcg.guardrail
|
||||
* @className GuardrailService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,9 +15,9 @@ package io.shinhanlife.dat.mcg.guardrail;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dat.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.guardrail;
|
||||
package io.shinhanlife.dap.mcg.guardrail;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.guardrail
|
||||
* @package io.shinhanlife.dap.mcg.guardrail
|
||||
* @className ToolResponseGuardrailService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,9 +15,9 @@ package io.shinhanlife.dat.mcg.guardrail;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dat.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.dat.mcg.messaging;
|
||||
package io.shinhanlife.dap.mcg.messaging;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -10,7 +10,7 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.messaging
|
||||
* @package io.shinhanlife.dap.mcg.messaging
|
||||
* @className KafkaProducerService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.presentation;
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.presentation
|
||||
* @package io.shinhanlife.dap.mcg.presentation
|
||||
* @className ChatController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,10 +15,10 @@ package io.shinhanlife.dat.mcg.presentation;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.lib.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.registry.InMemoryRegistryService;
|
||||
import io.shinhanlife.dat.mcg.service.ExecuteService;
|
||||
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
@@ -41,19 +41,10 @@ public class ChatController {
|
||||
|
||||
private final ExecuteService executeService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final InMemoryRegistryService registryService;
|
||||
private final RedisRegistryService registryService;
|
||||
private final ChatClient.Builder chatClientBuilder;
|
||||
private final McpRouterController mcpRouterController;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Value("${shinhan.ai.base-url}")
|
||||
private String liteLlmBaseUrl;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Value("${shinhan.ai.gemma-key}")
|
||||
private String gemmaKey;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Value("${shinhan.ai.qwen-key}")
|
||||
private String qwenKey;
|
||||
|
||||
@PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter chatStream(@RequestBody Map<String, String> request,
|
||||
@RequestHeader(value = "X-Agent-Id", required = false) String agentId,
|
||||
@@ -70,18 +61,12 @@ public class ChatController {
|
||||
if (toolsResponse != null && toolsResponse.getResult() instanceof Map) {
|
||||
Map<String, Object> resultMap = (Map<String, Object>) toolsResponse.getResult();
|
||||
if (resultMap.containsKey("tools")) {
|
||||
List<Map<String, Object>> allTools = (List<Map<String, Object>>) resultMap.get("tools");
|
||||
List<ToolMetadata> allTools = (List<ToolMetadata>) resultMap.get("tools");
|
||||
Set<String> addedToolNames = new HashSet<>();
|
||||
for (Map<String, Object> metaMap : allTools) {
|
||||
String name = (String) metaMap.get("name");
|
||||
if (name != null) {
|
||||
String cleanName = name.replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||
for (ToolMetadata meta : allTools) {
|
||||
if (Boolean.TRUE.equals(meta.getVisible()) && meta.getName() != null) {
|
||||
String cleanName = meta.getName().replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||
if (!addedToolNames.contains(cleanName)) {
|
||||
ToolMetadata meta = ToolMetadata.builder()
|
||||
.name(name)
|
||||
.description((String) metaMap.get("description"))
|
||||
.parametersSchema((Map<String, Object>) metaMap.get("inputSchema"))
|
||||
.build();
|
||||
callbacks.add(new DynamicMcpToolCallback(meta, executeService, objectMapper, effectiveTenantId));
|
||||
addedToolNames.add(cleanName);
|
||||
}
|
||||
@@ -90,59 +75,27 @@ public class ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
String selectedModel = request.getOrDefault("model", "Qwen3-Coder").trim();
|
||||
String selectedModel = request.getOrDefault("model", "gemini-flash-latest").trim();
|
||||
if (selectedModel.isEmpty()) {
|
||||
selectedModel = "Qwen3-Coder";
|
||||
selectedModel = "gemini-flash-latest";
|
||||
}
|
||||
|
||||
// LiteLLM 내부망 모델 매핑
|
||||
// 1. gemini-flash-latest 선택 시 OpenRouter의 Google Gemma 4 모델로 라우팅
|
||||
if (selectedModel.equals("gemini-flash-latest")) {
|
||||
selectedModel = "Gemma-4-31B";
|
||||
log.info("[Real AI Chat] 화면의 Gemini 플래시 선택을 사내 Gemma-4-31B 모델로 라우팅 전환");
|
||||
selectedModel = "google/gemma-4-31b-it:free";
|
||||
log.info("[Real AI Chat] Gemini 직접 API → OpenRouter Google Gemma 4 31B 로 라우팅 전환");
|
||||
}
|
||||
|
||||
ChatClient activeChatClient;
|
||||
org.springframework.ai.chat.prompt.ChatOptions chatOptions;
|
||||
|
||||
// 2. 모델 분기 처리: 사내 모델(Qwen3/Gemma-4) vs 기존 외부 모델(OpenRouter 등)
|
||||
if ("Qwen3-Coder".equalsIgnoreCase(selectedModel) || "Gemma-4-31B".equalsIgnoreCase(selectedModel)) {
|
||||
// 사내 LiteLLM 환경 동적 ChatModel 생성
|
||||
// Use liteLlmBaseUrl from application.yml
|
||||
String apiKey = "Gemma-4-31B".equalsIgnoreCase(selectedModel) ? gemmaKey : qwenKey;
|
||||
|
||||
org.springframework.ai.openai.api.OpenAiApi openAiApi = org.springframework.ai.openai.api.OpenAiApi.builder()
|
||||
.baseUrl(liteLlmBaseUrl)
|
||||
.apiKey(new org.springframework.ai.model.SimpleApiKey(apiKey))
|
||||
.build();
|
||||
|
||||
org.springframework.ai.openai.OpenAiChatModel dynamicChatModel = org.springframework.ai.openai.OpenAiChatModel.builder()
|
||||
.openAiApi(openAiApi)
|
||||
.build();
|
||||
|
||||
activeChatClient = ChatClient.builder(dynamicChatModel)
|
||||
.defaultSystem("You are AX HUB Assistant, a highly capable enterprise AI agent. You must use the provided tools to answer user questions when necessary. Always answer politely in Korean.")
|
||||
.build();
|
||||
|
||||
chatOptions = org.springframework.ai.openai.OpenAiChatOptions.builder()
|
||||
.model(selectedModel)
|
||||
.temperature(0.2)
|
||||
.maxTokens(16384)
|
||||
.build();
|
||||
} else {
|
||||
// 기존 방식: application.yml 에 설정된 기본 Bean (OpenRouter 등) 사용
|
||||
activeChatClient = chatClientBuilder
|
||||
.defaultSystem("You are AX HUB Assistant, a highly capable enterprise AI agent. You must use the provided tools to answer user questions when necessary. Always answer politely in Korean.")
|
||||
.build();
|
||||
|
||||
chatOptions = org.springframework.ai.openai.OpenAiChatOptions.builder()
|
||||
.model(selectedModel)
|
||||
.build();
|
||||
}
|
||||
// 2. OpenRouter 무료 모델 처리 (Spring AI 빌드된 ChatClient 및 MCP 툴 호출 완벽 지원!)
|
||||
ChatClient activeChatClient = chatClientBuilder
|
||||
.defaultSystem("You are AX HUB Assistant, a highly capable enterprise AI agent. You must use the provided tools to answer user questions when necessary. Always answer politely in Korean.")
|
||||
.build();
|
||||
|
||||
Flux<String> responseStream = activeChatClient.prompt()
|
||||
.user(message)
|
||||
.toolCallbacks(callbacks.toArray(new ToolCallback[0]))
|
||||
.options(chatOptions)
|
||||
.tools((Object[]) callbacks.toArray(new ToolCallback[0])) // Spring AI 2.0 uses tools()
|
||||
.options(org.springframework.ai.openai.OpenAiChatOptions.builder()
|
||||
.model(selectedModel))
|
||||
.stream()
|
||||
.content();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.shinhanlife.dat.mcg.presentation;
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
import io.shinhanlife.dat.mcg.service.KillSwitchService;
|
||||
import io.shinhanlife.dap.mcg.service.KillSwitchService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.presentation
|
||||
* @package io.shinhanlife.dap.mcg.presentation
|
||||
* @className KillSwitchController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -36,9 +36,9 @@ public class KillSwitchController {
|
||||
*/
|
||||
@PostMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> toggleKillSwitch(
|
||||
@PathVariable("type") String type,
|
||||
@PathVariable("target") String target,
|
||||
@RequestParam("state") boolean state) {
|
||||
@PathVariable String type,
|
||||
@PathVariable String target,
|
||||
@RequestParam boolean state) {
|
||||
|
||||
killSwitchService.toggleKillSwitch(type, target, state);
|
||||
|
||||
@@ -55,8 +55,8 @@ public class KillSwitchController {
|
||||
*/
|
||||
@DeleteMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> removeKillSwitch(
|
||||
@PathVariable("type") String type,
|
||||
@PathVariable("target") String target) {
|
||||
@PathVariable String type,
|
||||
@PathVariable String target) {
|
||||
|
||||
killSwitchService.removeKillSwitch(type, target);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.presentation;
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.presentation
|
||||
* @package io.shinhanlife.dap.mcg.presentation
|
||||
* @className McpRouterController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -16,15 +16,14 @@ package io.shinhanlife.dat.mcg.presentation;
|
||||
* </pre>
|
||||
*/
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.shinhanlife.dat.lib.adapter.dto.JsonRpcRequest;
|
||||
import io.shinhanlife.dat.lib.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dat.lib.adapter.dto.Params;
|
||||
import io.shinhanlife.dat.mcg.config.GatewayFallbackProperties;
|
||||
import io.shinhanlife.dat.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.registry.InMemoryRegistryService;
|
||||
import io.shinhanlife.dat.mcg.service.ExecuteService;
|
||||
import io.shinhanlife.dat.lib.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcRequest;
|
||||
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dap.lib.adapter.dto.Params;
|
||||
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -50,20 +49,19 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
@Tag(name = "MCP Router API", description = "AI Agent의 요청을 받아 Adapter 시스템으로 라우팅하는 게이트웨이 API")
|
||||
public class McpRouterController {
|
||||
|
||||
private final InMemoryRegistryService registryService;
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final ExecuteService executeService;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final GatewayFallbackProperties gatewayFallbackProperties;
|
||||
private final RestClient restClient;
|
||||
|
||||
public McpRouterController(InMemoryRegistryService registryService,
|
||||
public McpRouterController(RedisRegistryService redisRegistryService,
|
||||
ExecuteService executeService,
|
||||
SecurityProperties securityProperties,
|
||||
ObjectMapper objectMapper,
|
||||
GatewayFallbackProperties gatewayFallbackProperties,
|
||||
McpGatewayProperties mcpGatewayProperties) {
|
||||
this.registryService = registryService;
|
||||
GatewayFallbackProperties gatewayFallbackProperties) {
|
||||
this.redisRegistryService = redisRegistryService;
|
||||
this.executeService = executeService;
|
||||
this.securityProperties = securityProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
@@ -74,15 +72,7 @@ public class McpRouterController {
|
||||
factory.setConnectTimeout(1000); // 연결 시도 타임아웃 1초
|
||||
factory.setReadTimeout(1000); // 응답 대기 타임아웃 1초
|
||||
|
||||
String apiKey = mcpGatewayProperties.toolServerApiKey();
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
apiKey = "tool-server-key";
|
||||
}
|
||||
|
||||
this.restClient = RestClient.builder()
|
||||
.requestFactory(factory)
|
||||
.defaultHeader("X-Tool-Server-API-Key", apiKey)
|
||||
.build();
|
||||
this.restClient = RestClient.builder().requestFactory(factory).build();
|
||||
}
|
||||
|
||||
@PostMapping("/tools/call")
|
||||
@@ -123,16 +113,11 @@ public class McpRouterController {
|
||||
}
|
||||
|
||||
private List<ToolMetadata> fetchAllActiveTools() {
|
||||
List<ToolMetadata> activeTools = new ArrayList<>();
|
||||
try {
|
||||
activeTools.addAll(registryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
.collect(Collectors.toList()));
|
||||
} catch (Exception exception) {
|
||||
log.warn("Registry is unavailable. Fetching tools from configured fallback Tool Pods instead.");
|
||||
}
|
||||
|
||||
List<ToolMetadata> activeTools = redisRegistryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<String> knownTools = activeTools.stream()
|
||||
.map(ToolMetadata::getUid)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -151,10 +136,9 @@ public class McpRouterController {
|
||||
|
||||
if (localTools != null) {
|
||||
for (ToolMetadata t : localTools) {
|
||||
if (Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getUid())) {
|
||||
if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getUid())) {
|
||||
activeTools.add(t);
|
||||
knownTools.add(t.getUid());
|
||||
registryService.saveTool(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +149,6 @@ public class McpRouterController {
|
||||
return activeTools;
|
||||
}
|
||||
|
||||
/** Report 화면처럼 내부 메타데이터 전체가 필요한 Gateway 컴포넌트에 활성 Tool 목록을 제공합니다. */
|
||||
List<ToolMetadata> activeToolsForReport() {
|
||||
return fetchAllActiveTools();
|
||||
}
|
||||
|
||||
@GetMapping("/tools/list")
|
||||
public ResponseEntity<JsonRpcResponse> listTools(
|
||||
@RequestParam(value = "categoryKey", required = false) String categoryKey) {
|
||||
@@ -182,40 +161,9 @@ public class McpRouterController {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<Map<String, Object>> mappedTools = activeTools.stream().map(t -> {
|
||||
Map<String, Object> toolMap = new HashMap<>();
|
||||
toolMap.put("name", t.getName());
|
||||
toolMap.put("description", t.getDescription());
|
||||
toolMap.put("inputSchema", t.getParametersSchema());
|
||||
toolMap.put("outputSchema", t.getOutputSchema());
|
||||
toolMap.put("title", t.getDisplayName());
|
||||
|
||||
Map<String, Object> annotations = new HashMap<>();
|
||||
annotations.put("readOnlyHint", t.getReadOnlyHint() != null ? t.getReadOnlyHint() : false);
|
||||
annotations.put("destructiveHint", t.getDestructiveHint() != null ? t.getDestructiveHint() : true);
|
||||
annotations.put("idempotentHint", t.getIdempotentHint() != null ? t.getIdempotentHint() : false);
|
||||
toolMap.put("annotations", annotations);
|
||||
|
||||
Map<String, Object> meta = new HashMap<>();
|
||||
meta.put("version", t.getSemver());
|
||||
meta.put("category_key", t.getCategoryKey());
|
||||
meta.put("tags", t.getTags());
|
||||
meta.put("owner_org", t.getOwnerOrg());
|
||||
meta.put("legacy_interface_id", t.getMciServiceId());
|
||||
meta.put("display_description", t.getDisplayDescription());
|
||||
meta.put("when_to_use", t.getWhenToUse());
|
||||
meta.put("when_not_to_use", t.getWhenNotToUse());
|
||||
meta.put("io_limits", t.getIoLimits());
|
||||
meta.put("example_queries", t.getExampleQueries());
|
||||
meta.put("required_env_keys", t.getRequiredEnvKeys());
|
||||
|
||||
toolMap.put("_meta", meta);
|
||||
return toolMap;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
JsonRpcResponse response = new JsonRpcResponse();
|
||||
response.setId(UUID.randomUUID().toString());
|
||||
response.setResult(Map.of("tools", mappedTools));
|
||||
response.setResult(Map.of("tools", activeTools));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
@@ -287,15 +235,23 @@ public class McpRouterController {
|
||||
|
||||
@PostMapping("/registry/register")
|
||||
public ResponseEntity<String> registerTool(@RequestBody ToolMetadata meta) {
|
||||
registryService.saveTool(meta);
|
||||
redisRegistryService.saveTool(meta);
|
||||
return ResponseEntity.ok("Registered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/deregister")
|
||||
public ResponseEntity<String> deregisterTool(@RequestBody String uid) {
|
||||
registryService.removeTool(uid);
|
||||
redisRegistryService.removeTool(uid);
|
||||
return ResponseEntity.ok("Deregistered");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@PostMapping("/registry/heartbeat")
|
||||
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
|
||||
boolean success = redisRegistryService.refreshHeartbeat(uid);
|
||||
if (success) {
|
||||
return ResponseEntity.ok("Heartbeat updated");
|
||||
} else {
|
||||
return ResponseEntity.status(404).body("Tool not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package io.shinhanlife.dap.mcg.presentation;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.presentation
|
||||
* @className ScaffoldingController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.lib.util.PodScaffolder;
|
||||
import io.shinhanlife.dap.lib.util.ToolScaffolder;
|
||||
import io.shinhanlife.dap.lib.util.ToolSourceUpdater;
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/scaffold")
|
||||
public class ScaffoldingController {
|
||||
|
||||
@PostMapping("/pod")
|
||||
public String scaffoldPod(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-oth");
|
||||
if (!moduleName.startsWith("dap-tool-")) moduleName = "dap-tool-" + moduleName;
|
||||
String port = req.getOrDefault("port", "8085");
|
||||
String shortName = moduleName.replace("dap-tool-", "").replace("-", "");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool")
|
||||
public String scaffoldTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String baseName = req.get("baseName");
|
||||
String interfaceId = req.get("interfaceId");
|
||||
String description = req.get("description");
|
||||
String group = req.getOrDefault("categoryKey", req.getOrDefault("group", "COMMON"));
|
||||
String routingType = req.getOrDefault("routingType", "HTTP");
|
||||
String moduleName = req.getOrDefault("moduleName", "dap-was-oth");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
String clientSystemCode = req.get("clientSystemCode");
|
||||
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register, clientSystemCode);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool/update")
|
||||
public String updateTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String toolName = req.get("toolName");
|
||||
String domainGroup = req.getOrDefault("categoryKey", req.get("domainGroup"));
|
||||
String description = req.get("description");
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
Boolean requiresApproval = req.containsKey("requiresApproval") ? Boolean.parseBoolean(req.get("requiresApproval")) : null;
|
||||
|
||||
ToolSourceUpdater.updateToolSource(toolName, domainGroup, description, register, requiresApproval);
|
||||
return "성공";
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/modules")
|
||||
public List<String> listModules() {
|
||||
try {
|
||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
if (sourceDir == null) sourceDir = System.getProperty("user.dir");
|
||||
|
||||
File dir = new File(sourceDir);
|
||||
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("dap-tool-") && !f.getName().equals("dap-was-lib"));
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
return List.of("dap-was-oth", "dap-tool-hr", "dap-was-sms");
|
||||
}
|
||||
|
||||
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
return List.of("dap-was-oth", "dap-tool-hr", "dap-was-sms");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.trace;
|
||||
package io.shinhanlife.dap.mcg.redis;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.trace
|
||||
* @package io.shinhanlife.dap.mcg.redis
|
||||
* @className McpMonitorEventService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -0,0 +1,264 @@
|
||||
package io.shinhanlife.dap.mcg.redis;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.redis
|
||||
* @className RedisToolTraceService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.mcg.security.McpRequestContext;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class RedisToolTraceService {
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisToolTraceService.class);
|
||||
private static final String KEY_PREFIX = "mcp:request:";
|
||||
private static final String RECENT_KEY = "mcp:request:recent";
|
||||
|
||||
private final McpGatewayProperties properties;
|
||||
private final ObjectProvider<StringRedisTemplate> redisProvider;
|
||||
private final ObjectMapper json;
|
||||
private final McpMonitorEventService monitorEvents;
|
||||
private final Map<String, AttemptState> attemptStates = new ConcurrentHashMap<>();
|
||||
|
||||
public RedisToolTraceService(McpGatewayProperties properties,
|
||||
ObjectProvider<StringRedisTemplate> redisProvider,
|
||||
ObjectMapper json,
|
||||
McpMonitorEventService monitorEvents) {
|
||||
this.properties = properties;
|
||||
this.redisProvider = redisProvider;
|
||||
this.json = json;
|
||||
this.monitorEvents = monitorEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent request has entered the MCP tool gateway.
|
||||
*/
|
||||
public void started(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments) {
|
||||
save(context, metadata, arguments, "STARTED", 0, "", 0, 0, 0,
|
||||
"Agent request entered MCP", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP is attempting to call the target Tool server.
|
||||
*/
|
||||
public void attemptStarted(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
int attempt, int maxAttempts) {
|
||||
save(context, metadata, arguments, "ATTEMPTING", 0, "", attempt, maxAttempts, 0,
|
||||
"Calling tool server", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP will retry the Tool call after backoff.
|
||||
*/
|
||||
public void retryWaiting(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
int failedAttempt, int maxAttempts, long backoffMillis, String failureType) {
|
||||
save(context, metadata, arguments, "RETRY_WAITING", 0, failureType, failedAttempt, maxAttempts, backoffMillis,
|
||||
"Retry will be attempted after backoff", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool execution has finished.
|
||||
*/
|
||||
public void finished(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
long elapsedMillis, boolean success, String failureType, String responseText) {
|
||||
save(context, metadata, arguments, success ? "SUCCESS" : "FAILED", elapsedMillis, failureType, 0, 0, 0,
|
||||
success ? "Tool call completed" : "Tool call failed", responseText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent live trace entries. One current entry is kept per request id.
|
||||
*/
|
||||
public List<String> recent() {
|
||||
if (!properties.redisTraceEnabled()) {
|
||||
return List.of("Redis trace is disabled. Set MCP_REDIS_TRACE_ENABLED=true.");
|
||||
}
|
||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
||||
if (redis == null) {
|
||||
return List.of("RedisTemplate is not available.");
|
||||
}
|
||||
try {
|
||||
List<String> requestIds = redis.opsForList().range(RECENT_KEY, 0, 49);
|
||||
if (requestIds == null || requestIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return requestIds.stream()
|
||||
.map(requestId -> redis.opsForValue().get(KEY_PREFIX + requestId))
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.toList();
|
||||
} catch (Exception error) {
|
||||
log.warn("Redis trace read failed. message={}", error.getMessage());
|
||||
return List.of("Redis trace read failed: " + error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Historical accumulation is intentionally disabled.
|
||||
*
|
||||
* The MCP monitor is used to observe the current Agent request flow only,
|
||||
* so this method returns the same live entries as recent().
|
||||
*/
|
||||
public List<String> history() {
|
||||
return recent();
|
||||
}
|
||||
|
||||
private void save(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
||||
long backoffMillis, String message, String responseText) {
|
||||
String stateKey = stateKey(context, metadata);
|
||||
if (attempt > 0 && maxAttempts > 0) {
|
||||
attemptStates.put(stateKey, new AttemptState(attempt, maxAttempts));
|
||||
}
|
||||
String payload;
|
||||
try {
|
||||
payload = tracePayload(context, metadata, arguments, status, elapsedMillis, failureType, attempt,
|
||||
maxAttempts, backoffMillis, message, responseText);
|
||||
monitorEvents.publish(payload);
|
||||
if ("SUCCESS".equals(status) || "FAILED".equals(status)) {
|
||||
attemptStates.remove(stateKey);
|
||||
}
|
||||
} catch (Exception error) {
|
||||
log.warn("MCP monitor event publish failed. requestId={} tool={} message={}",
|
||||
context.requestId(), metadata.getName(), error.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.redisTraceEnabled()) {
|
||||
return;
|
||||
}
|
||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
||||
if (redis == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String requestId = context.requestId();
|
||||
Duration ttl = Duration.ofSeconds(properties.redisTraceTtlSeconds());
|
||||
redis.opsForValue().set(KEY_PREFIX + requestId, payload, ttl);
|
||||
redis.opsForList().remove(RECENT_KEY, 0, requestId);
|
||||
redis.opsForList().leftPush(RECENT_KEY, requestId);
|
||||
redis.opsForList().trim(RECENT_KEY, 0, 49);
|
||||
redis.expire(RECENT_KEY, ttl);
|
||||
} catch (Exception error) {
|
||||
log.warn("Redis trace save failed. requestId={} tool={} message={}",
|
||||
context.requestId(), metadata.getName(), error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String tracePayload(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
||||
long backoffMillis, String message, String responseText) throws Exception {
|
||||
AttemptState state = attemptStates.get(stateKey(context, metadata));
|
||||
int displayAttempt = attempt > 0 ? attempt : state == null ? 0 : state.attempt();
|
||||
int displayMaxAttempts = maxAttempts > 0 ? maxAttempts : state == null ? 0 : state.maxAttempts();
|
||||
int retryCount = Math.max(0, displayAttempt - 1);
|
||||
Map<String, Object> trace = new LinkedHashMap<>();
|
||||
trace.put("requestId", context.requestId());
|
||||
trace.put("traceGroupId", context.traceGroupId());
|
||||
trace.put("agentId", context.agentId());
|
||||
trace.put("userId", context.userId());
|
||||
trace.put("clientAddress", context.clientAddress());
|
||||
trace.put("toolName", metadata.getName());
|
||||
trace.put("operationType", metadata.getOperationType() != null ? metadata.getOperationType().name() : "READ");
|
||||
trace.put("status", status);
|
||||
trace.put("message", message == null ? "" : message);
|
||||
trace.put("failureType", failureType == null ? "" : failureType);
|
||||
trace.put("attempt", displayAttempt);
|
||||
trace.put("maxAttempts", displayMaxAttempts);
|
||||
trace.put("retryCount", retryCount);
|
||||
trace.put("backoffMillis", backoffMillis);
|
||||
trace.put("elapsedMillis", elapsedMillis);
|
||||
trace.put("idempotencyKeyHash", hashText(arguments.path("idempotencyKey").asText("")));
|
||||
|
||||
List<String> argNames = new ArrayList<>();
|
||||
arguments.fieldNames().forEachRemaining(argNames::add);
|
||||
trace.put("argumentNames", argNames);
|
||||
|
||||
trace.put("arguments", arguments.toString());
|
||||
trace.put("responseSummary", responseSummary(responseText));
|
||||
trace.put("timestamp", Instant.now().toString());
|
||||
return json.writeValueAsString(trace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Trace에는 Tool 응답 원문을 저장하지 않고 size/hash/상태/카운트만 저장합니다.
|
||||
*/
|
||||
Map<String, Object> responseSummary(String responseText) {
|
||||
Map<String, Object> summary = new LinkedHashMap<>();
|
||||
if (responseText == null || responseText.isBlank()) {
|
||||
summary.put("bytes", 0);
|
||||
summary.put("sha256", "");
|
||||
return summary;
|
||||
}
|
||||
byte[] bytes = responseText.getBytes(StandardCharsets.UTF_8);
|
||||
summary.put("bytes", bytes.length);
|
||||
summary.put("sha256", sha256(bytes));
|
||||
try {
|
||||
JsonNode root = json.readTree(responseText);
|
||||
summary.put("success", root.path("success").asBoolean(false));
|
||||
summary.put("status", root.path("status").asText(""));
|
||||
summary.put("toolName", root.path("toolName").asText(""));
|
||||
summary.put("pageSize", root.path("pageSize").asInt(0));
|
||||
summary.put("pageCount", root.path("pageCount").asInt(0));
|
||||
summary.put("returnedCount", root.path("returnedCount").asInt(0));
|
||||
summary.put("totalCount", root.path("totalCount").asLong(0));
|
||||
summary.put("hasMore", root.path("hasMore").asBoolean(false));
|
||||
summary.put("nextCursor", root.path("nextCursor").asText(""));
|
||||
summary.put("message", root.path("message").asText(""));
|
||||
summary.put("resultRef", root.path("resultRef").asText(""));
|
||||
} catch (Exception ignored) {
|
||||
summary.put("parseable", false);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
private String sha256(byte[] bytes) {
|
||||
try {
|
||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes));
|
||||
} catch (Exception error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String hashText(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
return sha256(value.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String stateKey(McpRequestContext context, ToolMetadata metadata) {
|
||||
return context.requestId() + ":" + metadata.getName();
|
||||
}
|
||||
|
||||
private record AttemptState(int attempt, int maxAttempts) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package io.shinhanlife.dap.mcg.registry;
|
||||
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.mcg.registry
|
||||
* @className RedisRegistryService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RedisRegistryService {
|
||||
|
||||
private final RedisTemplate<String, ToolMetadata> redisTemplate;
|
||||
private static final String KEY_PREFIX = "mcp:tool:";
|
||||
private static final Duration DEFAULT_TTL = Duration.ofSeconds(45); // 실무 하트비트 주기 반영
|
||||
|
||||
/**
|
||||
* 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료)
|
||||
*/
|
||||
public void saveTool(ToolMetadata meta) {
|
||||
String key = KEY_PREFIX + meta.getUid();
|
||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||
redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL);
|
||||
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getUid());
|
||||
}
|
||||
|
||||
/**
|
||||
* 하트비트 갱신 (TTL 초기화)
|
||||
*/
|
||||
public boolean refreshHeartbeat(String uid) {
|
||||
String key = KEY_PREFIX + uid;
|
||||
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
||||
if (Boolean.TRUE.equals(exists)) {
|
||||
log.debug(" [RedisRegistry] 하트비트 갱신: {}", uid);
|
||||
return true;
|
||||
} else {
|
||||
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public List<String> getAvailablePods(String uid) {
|
||||
ToolMetadata tool = getTool(uid);
|
||||
|
||||
if (tool != null && tool.getPodUrl() != null) {
|
||||
return List.of(tool.getPodUrl());
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
/**
|
||||
* 실행 시 툴 정보 조회 (Tool Execution 시 참조)
|
||||
*/
|
||||
public ToolMetadata getTool(String uid) {
|
||||
return redisTemplate.opsForValue().get(KEY_PREFIX + uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 툴 이름으로 정보 조회
|
||||
*/
|
||||
public ToolMetadata getToolByName(String name) {
|
||||
List<ToolMetadata> allTools = getAllTools();
|
||||
for (ToolMetadata tool : allTools) {
|
||||
if (name.equals(tool.getName())) {
|
||||
return tool;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 모든 활성 툴 목록 조회
|
||||
*/
|
||||
public List<ToolMetadata> getAllTools() {
|
||||
Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*");
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ToolMetadata> tools = redisTemplate.opsForValue().multiGet(keys);
|
||||
return tools != null ? tools.stream().filter(Objects::nonNull).collect(Collectors.toList()) : List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 툴 명시적 제거 (Deregister)
|
||||
*/
|
||||
public void removeTool(String uid) {
|
||||
redisTemplate.delete(KEY_PREFIX + uid);
|
||||
log.info(" [RedisRegistry] 툴 삭제 완료: {}", uid);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className CircuitBreaker
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className CircuitBreakerMonitorController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className CircuitBreakerService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,7 +15,7 @@ package io.shinhanlife.dat.mcg.resilience;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Clock;
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className CircuitBreakerSnapshot
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className CircuitBreakerState
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className FailureType
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className RetryPolicy
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.resilience;
|
||||
package io.shinhanlife.dap.mcg.resilience;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.resilience
|
||||
* @package io.shinhanlife.dap.mcg.resilience
|
||||
* @className ToolExecutionException
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.security;
|
||||
package io.shinhanlife.dap.mcg.security;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.security
|
||||
* @package io.shinhanlife.dap.mcg.security
|
||||
* @className McpRequestContext
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.security;
|
||||
package io.shinhanlife.dap.mcg.security;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.security
|
||||
* @package io.shinhanlife.dap.mcg.security
|
||||
* @className McpRequestContextResolver
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.security;
|
||||
package io.shinhanlife.dap.mcg.security;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.security
|
||||
* @package io.shinhanlife.dap.mcg.security
|
||||
* @className ToolAuthorizationService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,11 +15,11 @@ package io.shinhanlife.dat.mcg.security;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dat.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dat.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dat.lib.dto.OperationType;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.lib.dto.OperationType;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.service;
|
||||
package io.shinhanlife.dap.mcg.service;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.service
|
||||
* @package io.shinhanlife.dap.mcg.service
|
||||
* @className ExecuteService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -17,20 +17,20 @@ package io.shinhanlife.dat.mcg.service;
|
||||
*/
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dat.mcg.resilience.RetryPolicy;
|
||||
import io.shinhanlife.dat.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dat.lib.dto.OperationType;
|
||||
import io.shinhanlife.dat.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dat.mcg.guardrail.GuardrailService;
|
||||
import io.shinhanlife.dat.mcg.security.McpRequestContext;
|
||||
import io.shinhanlife.dat.mcg.security.McpRequestContextResolver;
|
||||
import io.shinhanlife.dat.mcg.audit.AuditLogService;
|
||||
import io.shinhanlife.dat.mcg.resilience.CircuitBreaker;
|
||||
import io.shinhanlife.dat.mcg.resilience.CircuitBreakerService;
|
||||
import io.shinhanlife.dat.mcg.security.ToolAuthorizationService;
|
||||
import io.shinhanlife.dat.mcg.trace.InMemoryToolTraceService;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.resilience.FailureType;
|
||||
import io.shinhanlife.dap.mcg.resilience.RetryPolicy;
|
||||
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.lib.dto.OperationType;
|
||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.mcg.guardrail.GuardrailService;
|
||||
import io.shinhanlife.dap.mcg.security.McpRequestContext;
|
||||
import io.shinhanlife.dap.mcg.security.McpRequestContextResolver;
|
||||
import io.shinhanlife.dap.mcg.audit.AuditLogService;
|
||||
import io.shinhanlife.dap.mcg.resilience.CircuitBreaker;
|
||||
import io.shinhanlife.dap.mcg.resilience.CircuitBreakerService;
|
||||
import io.shinhanlife.dap.mcg.security.ToolAuthorizationService;
|
||||
import io.shinhanlife.dap.mcg.redis.RedisToolTraceService;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.util.Map;
|
||||
@@ -43,12 +43,12 @@ import org.springframework.web.client.RestClient;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.shinhanlife.dat.mcg.tool.large.LargeToolResponseService;
|
||||
import io.shinhanlife.dat.mcg.tool.large.PaginationRequestValidator;
|
||||
import io.shinhanlife.dat.mcg.tool.result.ToolExecutionResultFormatter;
|
||||
import io.shinhanlife.dat.mcg.tool.result.ToolExecutionResult;
|
||||
import io.shinhanlife.dat.mcg.guardrail.ToolResponseGuardrailService;
|
||||
import io.shinhanlife.dat.mcg.transport.ToolInvoker;
|
||||
import io.shinhanlife.dap.mcg.tool.large.LargeToolResponseService;
|
||||
import io.shinhanlife.dap.mcg.tool.large.PaginationRequestValidator;
|
||||
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResultFormatter;
|
||||
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResult;
|
||||
import io.shinhanlife.dap.mcg.guardrail.ToolResponseGuardrailService;
|
||||
import io.shinhanlife.dap.mcg.transport.ToolInvoker;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -62,7 +62,7 @@ public class ExecuteService {
|
||||
private final AuditLogService auditLogService;
|
||||
private final CircuitBreakerService circuitBreakerService;
|
||||
private final ToolAuthorizationService authorizationService;
|
||||
private final InMemoryToolTraceService traceService;
|
||||
private final RedisToolTraceService redisTrace;
|
||||
private final McpGatewayProperties properties;
|
||||
private final LargeToolResponseService largeResponses;
|
||||
private final PaginationRequestValidator paginationValidator;
|
||||
@@ -79,7 +79,7 @@ public class ExecuteService {
|
||||
AuditLogService auditLogService,
|
||||
CircuitBreakerService circuitBreakerService,
|
||||
ToolAuthorizationService authorizationService,
|
||||
InMemoryToolTraceService traceService,
|
||||
RedisToolTraceService redisTrace,
|
||||
McpGatewayProperties properties,
|
||||
LargeToolResponseService largeResponses,
|
||||
PaginationRequestValidator paginationValidator,
|
||||
@@ -94,7 +94,7 @@ public class ExecuteService {
|
||||
this.auditLogService = auditLogService;
|
||||
this.circuitBreakerService = circuitBreakerService;
|
||||
this.authorizationService = authorizationService;
|
||||
this.traceService = traceService;
|
||||
this.redisTrace = redisTrace;
|
||||
this.properties = properties;
|
||||
this.largeResponses = largeResponses;
|
||||
this.paginationValidator = paginationValidator;
|
||||
@@ -133,7 +133,7 @@ public class ExecuteService {
|
||||
|
||||
long startedAt = System.nanoTime();
|
||||
auditLogService.toolStarted(context, toolName, argumentsNode);
|
||||
traceService.started(context, metadata, argumentsNode);
|
||||
redisTrace.started(context, metadata, argumentsNode);
|
||||
|
||||
try {
|
||||
authorizationService.authorize(context, metadata);
|
||||
@@ -174,13 +174,13 @@ public class ExecuteService {
|
||||
finalResult.put("original_size", originalSize);
|
||||
|
||||
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
||||
traceService.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
||||
|
||||
return finalResult;
|
||||
} catch (ToolExecutionException error) {
|
||||
long elapsedMillis = elapsedMillis(startedAt);
|
||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
||||
traceService.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
||||
|
||||
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||
errorResult.put("status", "error");
|
||||
@@ -194,7 +194,7 @@ public class ExecuteService {
|
||||
} catch (Exception error) {
|
||||
long elapsedMillis = elapsedMillis(startedAt);
|
||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
||||
traceService.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
||||
|
||||
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||
errorResult.put("status", "error");
|
||||
@@ -225,7 +225,7 @@ public class ExecuteService {
|
||||
ToolExecutionException lastError = null;
|
||||
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
||||
try {
|
||||
traceService.attemptStarted(context, metadata, arguments, attempt, retryPolicy.maxAttempts());
|
||||
redisTrace.attemptStarted(context, metadata, arguments, attempt, retryPolicy.maxAttempts());
|
||||
circuitBreaker.beforeCall();
|
||||
Object result = executeOnce(context, metadata, arguments, payload);
|
||||
circuitBreaker.recordSuccess();
|
||||
@@ -237,7 +237,7 @@ public class ExecuteService {
|
||||
throw error;
|
||||
}
|
||||
long backoffMillis = retryPolicy.backoffMillis(attempt);
|
||||
traceService.retryWaiting(context, metadata, arguments, attempt, retryPolicy.maxAttempts(), backoffMillis,
|
||||
redisTrace.retryWaiting(context, metadata, arguments, attempt, retryPolicy.maxAttempts(), backoffMillis,
|
||||
error.failureType().name());
|
||||
sleepBeforeRetry(metadata.getName(), attempt, backoffMillis, error.failureType());
|
||||
}
|
||||
@@ -261,11 +261,6 @@ public class ExecuteService {
|
||||
Map<String, String> headers = new java.util.HashMap<>();
|
||||
headers.put("trace-id", context.requestId());
|
||||
headers.put("request-id", java.util.UUID.randomUUID().toString());
|
||||
String apiKey = properties.toolServerApiKey();
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
apiKey = "tool-server-key";
|
||||
}
|
||||
headers.put("X-Tool-Server-API-Key", apiKey);
|
||||
|
||||
ObjectNode pageArguments = paginationValidator.normalize(metadata, arguments);
|
||||
LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId());
|
||||
@@ -342,18 +337,13 @@ public class ExecuteService {
|
||||
if (!retryAllowedByOperation(metadata, arguments)) {
|
||||
return RetryPolicy.disabled();
|
||||
}
|
||||
return new RetryPolicy(retryMaxAttempts(metadata, properties.retryMaxAttempts()), properties.retryInitialBackoffMillis(),
|
||||
return new RetryPolicy(properties.retryMaxAttempts(), properties.retryInitialBackoffMillis(),
|
||||
properties.retryBackoffMultiplier(), properties.retryMaxBackoffMillis());
|
||||
}
|
||||
|
||||
static int retryMaxAttempts(ToolMetadata metadata, int gatewayDefault) {
|
||||
Integer toolValue = metadata.getRetryMaxAttempts();
|
||||
return toolValue != null && toolValue > 0 ? toolValue : gatewayDefault;
|
||||
}
|
||||
|
||||
private boolean retryAllowedByOperation(ToolMetadata metadata, ObjectNode arguments) {
|
||||
if (metadata.getOperationType() == OperationType.READ) {
|
||||
return true;
|
||||
return metadata.getRetryEnabled() != null ? metadata.getRetryEnabled() : true;
|
||||
}
|
||||
return arguments.hasNonNull("idempotencyKey") && !arguments.path("idempotencyKey").asText("").isBlank();
|
||||
}
|
||||
@@ -1,30 +1,35 @@
|
||||
package io.shinhanlife.dat.mcg.service;
|
||||
package io.shinhanlife.dap.mcg.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.service
|
||||
* @package io.shinhanlife.dap.mcg.service
|
||||
* @className KillSwitchService
|
||||
* @description AX HUB 시스템 킬 스위치 서비스 (인메모리)
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 0986406 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KillSwitchService {
|
||||
|
||||
// 인메모리 맵으로 변경
|
||||
private final Map<String, String> killSwitchMap = new ConcurrentHashMap<>();
|
||||
// 기본 제공되는 StringRedisTemplate 사용
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public void checkAgent(String tenantId) {
|
||||
String key = "mcp:kill:agent:" + tenantId;
|
||||
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 에이전트 접근 시도: {}", tenantId);
|
||||
throw new SecurityException(" 비상 차단: 해당 테넌트(" + tenantId + ")의 접근이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
@@ -34,7 +39,7 @@ public class KillSwitchService {
|
||||
if (toolName == null || toolName.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:tool:" + toolName;
|
||||
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 툴 실행 시도: {}", toolName);
|
||||
throw new SecurityException(" 비상 차단: 해당 툴(" + toolName + ")의 실행이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
@@ -44,7 +49,7 @@ public class KillSwitchService {
|
||||
if (integrationType == null || integrationType.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:route:" + integrationType;
|
||||
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 라우트 통신 시도: {}", integrationType);
|
||||
throw new SecurityException(" 비상 차단: 해당 레거시 라우트(" + integrationType + ")로의 통신이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
@@ -53,14 +58,14 @@ public class KillSwitchService {
|
||||
// 관리자 API용 토글 메서드
|
||||
public void toggleKillSwitch(String type, String target, boolean state) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
killSwitchMap.put(key, String.valueOf(state));
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(state));
|
||||
log.info(" [KillSwitch] 상태 변경: {} -> {} (차단 상태: {})", type, target, state);
|
||||
}
|
||||
|
||||
// 관리자 API용 삭제 메서드 (맵 항목 삭제)
|
||||
// 관리자 API용 삭제 메서드 (Redis 키 자체를 완전 삭제)
|
||||
public void removeKillSwitch(String type, String target) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
killSwitchMap.remove(key);
|
||||
stringRedisTemplate.delete(key);
|
||||
log.info(" [KillSwitch] 차단 키 완전 삭제: {} -> {}", type, target);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.shinhanlife.dat.mcg.service;
|
||||
package io.shinhanlife.dap.mcg.service;
|
||||
|
||||
import io.shinhanlife.dat.lib.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.dat.mcg.registry.InMemoryRegistryService;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.config.GatewayFallbackProperties;
|
||||
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.service
|
||||
* @package io.shinhanlife.dap.mcg.service
|
||||
* @className ToolPlanner
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -30,7 +30,7 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class ToolPlanner {
|
||||
|
||||
private final InMemoryRegistryService InMemoryRegistryService;
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final GatewayFallbackProperties fallbackProperties;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ToolPlanner {
|
||||
|
||||
// 2. RedisRegistry에서 해당 툴의 메타데이터 조회
|
||||
// (실제로는 이 메타데이터가 실행 계획의 핵심이 됩니다)
|
||||
var toolMetadata = InMemoryRegistryService.getToolByName(toolName);
|
||||
var toolMetadata = redisRegistryService.getToolByName(toolName);
|
||||
|
||||
if (toolMetadata == null) {
|
||||
log.warn(" [Planner] 등록되지 않은 툴 요청: {}. Fallback 라우팅 규칙을 확인합니다.", toolName);
|
||||
@@ -58,7 +58,7 @@ public class ToolPlanner {
|
||||
String fallbackPodUrl = fallbackProperties.getDefaultUrl();
|
||||
if (fallbackProperties.getRoutes() != null) {
|
||||
for (Map.Entry<String, String> entry : fallbackProperties.getRoutes().entrySet()) {
|
||||
if (toolName.startsWith(entry.getKey() + "_")) {
|
||||
if (toolName.contains(entry.getKey())) {
|
||||
fallbackPodUrl = entry.getValue();
|
||||
break;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public class ToolPlanner {
|
||||
}
|
||||
|
||||
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
||||
if (tenantId != null && !tenantId.equalsIgnoreCase("system") && toolMetadata.getCategoryKey() != null) {
|
||||
if (tenantId != null && toolMetadata.getCategoryKey() != null) {
|
||||
String normalizedTenantId = tenantId.toLowerCase();
|
||||
List<String> allowedDomains = securityProperties.getTenantDomains().get(normalizedTenantId);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.shinhanlife.dat.mcg.sync;
|
||||
package io.shinhanlife.dap.mcg.sync;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.sync
|
||||
* @package io.shinhanlife.dap.mcg.sync
|
||||
* @className CustomWebMvcSseServerTransportProvider
|
||||
* @description AX HUB MCP Gateway SSE 전송 제공자 - SSE 기반의 MCP 서버 트랜스포트를 구현하는 클래스
|
||||
* @author 0986406
|
||||
@@ -210,37 +210,22 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
|
||||
@Override
|
||||
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.debug("Sending message to SSE stream: {}", message.getClass().getName());
|
||||
log.info("Sending message to SSE stream: " + message.getClass().getName());
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(message);
|
||||
log.debug("Serialized message: {}", json);
|
||||
log.info("Serialized message: " + json);
|
||||
if (this.emitter != null) {
|
||||
this.emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("message").data(json));
|
||||
log.debug("Message successfully sent to SSE emitter");
|
||||
log.info("Message successfully sent to SSE emitter");
|
||||
|
||||
// Custom 프로토콜: 1회 요청당 1응답 후 종료 (스트림을 닫아버림)
|
||||
// 클라이언트가 한 번의 POST 후 응답을 받고 연결을 끊기 때문
|
||||
// this.emitter.complete(); // MCP 표준 클라이언트 지원을 위해 스트림 강제 종료 제거
|
||||
// this.emitter.complete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getClass().getSimpleName().contains("AsyncRequestNotUsableException") ||
|
||||
(e.getCause() != null && e.getCause() instanceof java.io.IOException) ||
|
||||
(e instanceof IllegalStateException && e.getMessage() != null && e.getMessage().contains("already completed"))) {
|
||||
log.debug("SSE emitter is disconnected (expected in Warm Pool): {}", e.getMessage());
|
||||
} else {
|
||||
log.error("Error sending message to SSE emitter", e);
|
||||
}
|
||||
|
||||
log.error("Error sending message to SSE emitter", e);
|
||||
if (this.emitter != null) {
|
||||
try {
|
||||
this.emitter.completeWithError(e);
|
||||
} catch (Exception ignored) {
|
||||
// ignore already completed
|
||||
} finally {
|
||||
// 죽은 Emitter를 null로 초기화하여 다음 5초 주기 때 재시도하지 않도록 방지
|
||||
// 클라이언트가 재연결하면 setEmitter(새 Emitter)를 통해 덮어씌워짐
|
||||
this.emitter = null;
|
||||
}
|
||||
this.emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.sync;
|
||||
package io.shinhanlife.dap.mcg.sync;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.sync
|
||||
* @package io.shinhanlife.dap.mcg.sync
|
||||
* @className DynamicMcpController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import io.shinhanlife.dat.mcg.sync.CustomWebMvcSseServerTransportProvider;
|
||||
import io.shinhanlife.dap.mcg.sync.CustomWebMvcSseServerTransportProvider;
|
||||
|
||||
@RestController
|
||||
public class DynamicMcpController {
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.sync;
|
||||
package io.shinhanlife.dap.mcg.sync;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.sync
|
||||
* @package io.shinhanlife.dap.mcg.sync
|
||||
* @className DynamicMcpServerManager
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -18,8 +18,8 @@ package io.shinhanlife.dat.mcg.sync;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||
import io.modelcontextprotocol.server.McpServer;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.sync.CustomWebMvcSseServerTransportProvider;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.sync.CustomWebMvcSseServerTransportProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -51,17 +51,10 @@ public class DynamicMcpServerManager {
|
||||
getOrCreateServer("common");
|
||||
getOrCreateServer("hr");
|
||||
getOrCreateServer("payment");
|
||||
// Tool category names are not Pod names. Keep existing external category
|
||||
// endpoints available while the SMS/OTH Pods transition to SAL/CUS.
|
||||
getOrCreateServer("sms");
|
||||
getOrCreateServer("oth");
|
||||
getOrCreateServer("sal");
|
||||
getOrCreateServer("email");
|
||||
getOrCreateServer("cus");
|
||||
getOrCreateServer("pro");
|
||||
getOrCreateServer("sys");
|
||||
getOrCreateServer("oth");
|
||||
getOrCreateServer("sample");
|
||||
getOrCreateServer("smp"); // smp 카테고리도 기본적으로 항상 열어두도록 추가
|
||||
}
|
||||
|
||||
private McpSyncServer getOrCreateServer(String categoryKey) {
|
||||
@@ -75,7 +68,7 @@ public class DynamicMcpServerManager {
|
||||
CustomWebMvcSseServerTransportProvider transport = new CustomWebMvcSseServerTransportProvider(ssePath, msgPath, objectMapper);
|
||||
|
||||
McpSyncServer newServer = McpServer.sync(transport)
|
||||
.serverInfo("dat-was-" + key, "1.0.0")
|
||||
.serverInfo("DAP-Gateway-" + key, "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.sync;
|
||||
package io.shinhanlife.dap.mcg.sync;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.sync
|
||||
* @package io.shinhanlife.dap.mcg.sync
|
||||
* @className RegistryMcpToolSpecificationFactory
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -15,12 +15,11 @@ package io.shinhanlife.dat.mcg.sync;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.lib.mcp.ToolMetadataMcpMapper;
|
||||
import io.shinhanlife.dat.mcg.service.ExecuteService;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.shinhanlife.dat.mcg.tool.result.ToolExecutionResult;
|
||||
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -49,7 +48,17 @@ public class RegistryMcpToolSpecificationFactory {
|
||||
* Registry Entry 하나를 MCP SDK의 stateless sync Tool specification으로 변환합니다.
|
||||
*/
|
||||
public McpServerFeatures.SyncToolSpecification create(ToolMetadata entry) {
|
||||
McpSchema.Tool tool = ToolMetadataMcpMapper.toTool(entry);
|
||||
McpSchema.Tool tool = McpSchema.Tool.builder()
|
||||
.name(entry.getName())
|
||||
.description(description(entry))
|
||||
.inputSchema(inputSchema(entry))
|
||||
.annotations(McpSchema.ToolAnnotations.builder()
|
||||
.readOnlyHint(Boolean.TRUE.equals(entry.getReadOnlyHint()))
|
||||
.destructiveHint(Boolean.TRUE.equals(entry.getDestructiveHint()))
|
||||
.idempotentHint(Boolean.TRUE.equals(entry.getIdempotentHint()))
|
||||
.openWorldHint(Boolean.TRUE.equals(entry.getOpenWorldHint()))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
@@ -138,20 +147,6 @@ public class RegistryMcpToolSpecificationFactory {
|
||||
return schema;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private McpSchema.JsonSchema toJsonSchema(Map<String, Object> schema) {
|
||||
return new McpSchema.JsonSchema(
|
||||
String.valueOf(schema.getOrDefault("type", "object")),
|
||||
schema.get("properties") instanceof Map<?, ?> properties
|
||||
? (Map<String, Object>) properties : Map.of(),
|
||||
schema.get("required") instanceof List<?> required
|
||||
? (List<String>) required : List.of(),
|
||||
schema.get("additionalProperties") instanceof Boolean additionalProperties
|
||||
? additionalProperties : Boolean.TRUE,
|
||||
schema.get("$defs") instanceof Map<?, ?> defs ? (Map<String, Object>) defs : Map.of(),
|
||||
schema.get("definitions") instanceof Map<?, ?> definitions
|
||||
? (Map<String, Object>) definitions : Map.of());
|
||||
}
|
||||
private String description(ToolMetadata entry) {
|
||||
return entry.getDescription() == null || entry.getDescription().isBlank()
|
||||
? entry.getName() + " Tool"
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.shinhanlife.dat.mcg.sync;
|
||||
package io.shinhanlife.dap.mcg.sync;
|
||||
|
||||
import io.shinhanlife.dat.mcc.dto.ToolMetadata;
|
||||
import io.shinhanlife.dat.mcg.registry.InMemoryRegistryService;
|
||||
import io.shinhanlife.dap.lib.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -19,7 +19,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.sync
|
||||
* @package io.shinhanlife.dap.mcg.sync
|
||||
* @className RegistryMcpToolSynchronizer
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -38,18 +38,18 @@ public class RegistryMcpToolSynchronizer {
|
||||
|
||||
private final ObjectProvider<McpSyncServer> mcpServerProvider;
|
||||
private final DynamicMcpServerManager dynamicMcpServerManager;
|
||||
private final InMemoryRegistryService InMemoryRegistryService;
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final RegistryMcpToolSpecificationFactory specificationFactory;
|
||||
private final Set<String> managedToolNames = new LinkedHashSet<>();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
public RegistryMcpToolSynchronizer(ObjectProvider<McpSyncServer> mcpServerProvider,
|
||||
DynamicMcpServerManager dynamicMcpServerManager,
|
||||
InMemoryRegistryService InMemoryRegistryService,
|
||||
RedisRegistryService redisRegistryService,
|
||||
RegistryMcpToolSpecificationFactory specificationFactory) {
|
||||
this.mcpServerProvider = mcpServerProvider;
|
||||
this.dynamicMcpServerManager = dynamicMcpServerManager;
|
||||
this.InMemoryRegistryService = InMemoryRegistryService;
|
||||
this.redisRegistryService = redisRegistryService;
|
||||
this.specificationFactory = specificationFactory;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class RegistryMcpToolSynchronizer {
|
||||
public void synchronize() {
|
||||
lock.lock();
|
||||
try {
|
||||
List<ToolMetadata> activeEntries = InMemoryRegistryService.getAllTools()
|
||||
List<ToolMetadata> activeEntries = redisRegistryService.getAllTools()
|
||||
.stream().filter(ToolMetadata::getVisible).collect(Collectors.toList());
|
||||
|
||||
// 1. Dynamic MCP Server 동기화 (카테고리별)
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.shinhanlife.dat.mcg.tool.large;
|
||||
package io.shinhanlife.dap.mcg.tool.large;
|
||||
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dat.mcg.tool.large
|
||||
* @package io.shinhanlife.dap.mcg.tool.large
|
||||
* @className AgentResponseBudgetService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 0986406
|
||||
@@ -18,7 +18,7 @@ package io.shinhanlife.dat.mcg.tool.large;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import io.shinhanlife.dat.mcg.config.AgentResponseBudgetProperties;
|
||||
import io.shinhanlife.dap.mcg.config.AgentResponseBudgetProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user